天天看點

apue和unp的學習之旅09——套接字選項

//-----------------------------------1.getsockopt和setsockopt--------------------------------------

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void* optval, socklen_t* optlen);

int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);

其中sockfd必須是打開的套接字描述符,level(級别)指定系統中解釋選項的代碼或為通用套接字代碼,或為某個特定于協定的代碼(例如ipv4,ipv6,tcp或sctp)。

optval是指向某個變量(*optval)的指針,setsockopt從*optval中取得選項待設定的新值,getsockopt則把已經獲得的選項目前值儲存放到optval中,*optval的大小由最後一個參數指定,它對于setsockopt是一個值參數,對于getsockopt是一個值結果參數。

套接字選項粗粗地分為兩大基本類型,一是啟用或禁止某個特性的二進制選項(稱為标志選項),二是取得并傳回我們可以設定或檢查的特定值的選項(稱為值選項)。

當為标志選項時,*optval是一個整數,*optval中傳回的值為0表示相應選項被啟用,不為0表示相應選項被啟用,類似地,setsockopt函數需要一個不為0的*optval值來啟用選項,一個為0的*optval值來禁止選項。

當為值選項時,那麼該選項是用于在使用者程序與系統之間傳遞所指定資料類型的值。

//------------------------------------2.套接tcp狀态-------------------------------------------

針對套接字的狀态,什麼時候設定或擷取選項有時序上的考慮,,例如,下面的套接字選項是由已連接配接套接字從監聽字繼承來的,so_debug,so_dontrout,so_keepalive,so_linger,so_oobinline,so_rcvbuf,so_rcvlowat,so_sndbuf,so_sndlowat,tcp_maxseg和tcp_nodelay.這對tcp是很重要的,因為accpet一直要到tcp層完成三路握手後才會給伺服器傳回已連接配接套接字。如果我們想在三路握手完成時確定這些套接字選項中的某一個是給已連接配接套接字設定的,那麼我們必須先給監聽套接字設定該選項。

//-----------------------------------3.一些通用套接字選項-------------------------------------

因為套接字選項有很多,不可能每個都記得下,是以隻能選取一些常用且重要的來記住,其他的就當字典似的需要時來查。

so_dontroute套接字選項:

本選項規定外出的分組将繞過底層協定的正常路由機制,舉例子來說,在ipv4情況下外出分組将被定向到适當的本地接口,也就是由其目的位址的網絡和子網部分确定的本地接口。如果這樣的本地接口無法由目的位址确定(譬如說目的地主機不在一個點對點鍊路的另一端,也不在一個共享的網絡上),那麼傳回enetunreach錯誤。路由守護程序(routed和gated)經常使用本選項來繞過路由表(路由表不正确的情況下),以強制将分組從特定接口送出。

so_keepalive套接字選項:

給一個tcp套接字設定保持存活選項後,如果2小時内在該套接字的任一方向上都沒有資料交換,tcp就自動給對端發送一個保持存活探測分節,這是一個對端必須響應的tcp分節,它會導緻如下三種情況之一:

1).對端以期望的ack響應,應用程序得不到通知,因為一切正常,在又經過仍無動靜的2小時後,tcp将發出另外也給探測分節。

2).對端以rst響應,它告知本端tcp:對端已崩潰且已經重新啟動,該套接字的待處理錯誤被置econnreset,套接字本身則被關閉。

3).對端對保持存活探測分節沒有任何響應,源自berkeley的tcp将另外發送8個探測分節,兩兩相隔75秒,試圖得到一個響應,tcp在發出第一個探測分節後11分15秒内若沒有得到任何響應則放棄。該套接字的待處理錯誤就被置為etimeout,套接字本身則被關閉。然而如果該套接字收到一個icmp錯誤作為某個探測分節的響應,那就傳回響應的錯誤,套接字本身也呗關閉,這種情形下待處理的錯誤即是ehostunreach,說明對端主機可能并沒有崩潰,隻是不可達,原因可能是網絡發生故障,或者是對端主機已經崩潰,而最後一跳的路由器也已經檢測到它的崩潰。

下圖對一個tcp連接配接的另一端發生某些事件時我們可以采用的各種檢測辦法做了彙總:

apue和unp的學習之旅09——套接字選項

so_linger套接字選項:

本選項指定close函數對面向連接配接的協定(例如tcp和sctp,但不是udp)如何操作,預設操作是close立即傳回,但是如果有資料殘留在套接字發送緩沖區中,系統将試着把這些資料發送給對端。

so_linger套接字選項使得我們可以改變這個預設設定,本選項要求在使用者程序與核心間傳遞如下結構,它在頭檔案<sys/socket.h>中定義:

struct  linger {

     int  l_onoff;

     int  l_linger;

};

對setsockopt的調用将根據其中2個結構成員的值形成下列3種情形之一:

1).如果l_onoff為0,那麼關閉本選項,l_linger的值也被忽略,先前讨論的tcp預設設定生效,級close立即傳回。

2).如果l_onoff為非0值且l_linger為0,那麼當close某個連接配接時tcp将中止該連接配接。這就是說tcp将丢棄保留在套接字發送緩沖區中的任何資料,并發送一個rst給對端,而沒有通常的四分組連接配接終止序列,這麼一來避免了tcp的time_wait狀态,然而存在以下可能性:即在2msl秒内建立該連結的另一個化身,導緻來自剛被終止的連接配接上的舊的重複分節被不正确地遞送到新的化身上。

3).如果l_onoff為非0值且l_linger也為非0值,那麼當套接字關閉時,核心将拖延一段時間,這就是說如果在套接字發送緩沖區中仍殘留有資料,那麼程序将被投入睡眠,直到所有資料都已發送完且均被對方确認或延滞時間到。如果套接字被設定為非阻塞型,那麼它将不等待close完成,即時延滞時間為非0也是如此。當使用so_linger選項的這個特性時,應用程序檢查close的傳回值是非常重要的,因為如果在資料發送完并被确認前延滞時間到的話,close将傳回ewouldblock錯誤,且套接字發送緩沖區中的任何殘留資料都被丢棄。

下圖彙總了shutdown的兩種可能調用和對close的三種可能調用,以及它們對tcp套接字的影響:

apue和unp的學習之旅09——套接字選項

so_rcvbuf和so_sndbuf選項:

    每個套接字都有一個發送緩沖區和一個接收緩沖區,對于tcp來說,套接字接收緩沖區中可用空間的大小限定了tcp通告對端的視窗大小。tcp套接字接收緩沖區不可能溢出,因為不允許對端發出超過本端所通告視窗大小的資料,這就是tcp的流量控制,如果對端無視視窗大小而發出了超過本端所通告視窗大小的資料,本端tcp将丢棄它們。然而對于udp來說,當接收到的資料報裝不進套接字接收緩沖區時,該資料報就被丢棄。

    套接字緩沖區的大小總是由新建立的已連接配接套接字從監聽套接字繼承而來,意味着,對于客戶,so_recvbuf選項必須在調用connect之前設定;對于伺服器,必須在調用listen之前給監聽套接字設定。

    tcp套接字緩沖區的大小應該至少是相應連接配接的mss值的4倍,這一點的依據是tcp快速恢複算法的工作機制,tcp發送端使用3個重複的确認來檢測某個分節是否丢失,發現某個分節丢失後,接收端将給新收到的每個分節發送一個重複的确認,如果視窗大小不足以存放4個這樣的分節,那就不可能連發三個重複的确認,進而無法激活快速恢複算法。

    為避免潛在緩沖區空間的浪費,tcp套接字緩沖區大小還必須是相應連接配接的mss值的偶數倍,有些實作題應用程序處理這個細節問題,在連接配接建立之後,向上舍入套接字緩沖區大小,使用預設的4.4bsd大小8192舉例來說,假設以太網的mss值為1460,在連接配接建立時收發兩個套接字接收緩沖區的大小将被向上舍入成8760(6*1460),這個要求并非必須,隻不過套接字緩沖區中的mss整數倍大小以外的空間不會被使用。

so_rcvlowat和so_sndlowat選項:

    每個套接字還有一個接收低水位标記和一個發送低水位标記,它們由select函數使用,這兩個套接字選項允許我們修改這2個低水位标記。

    接收低水位标記是讓select傳回“可讀”時套接字緩沖區中所需的資料量,對于tcp和udp和sctp套接字來說,器預設值為1。發送低水位标記是讓select傳回“可寫”時套接字發送緩沖區中所需要的空間,對于tcp套接字,其預設值通常為2048。

so_rcvtimeo和so_sndtimeo選項:

    這2個選項允許我們給套接字的接收和發送設定一個逾時值,注意通路它們的getsockopt和setsockopt函數的參數是指向timeval結構的指針,與select所用參數相同,與select所用參數相同。我們可通過設定其0s和0us來禁止逾時,預設都是靜禁止的。

so_reuseaddr和so_reuseport選項:

1).so_reuseaddr允許啟動一個監聽伺服器并捆綁其衆所周知端口,即使以前建立的将該端口用作它們的本地端口的連接配接仍然存在

a)啟動一個監聽伺服器

b)連接配接請求到達,派生一個子程序來處理這個客戶

c)監聽伺服器終止,但是子程序繼續為現有連接配接上的客戶提供服務

d)重新開機監聽伺服器

如果伺服器在socket 和 bind兩個調用之間設定了so_reuseaddr套接字選項,那麼bind将成功。

2).so_reuseaddr允許在同一個端口上啟動同一伺服器的多個執行個體,隻要每個執行個體捆綁一個不同的ip位址即可。

//------------------------------------4.tcp的兩個套接字選項----------------------------------------

tcp_maxseg套接字選項:

    本選項允許我們擷取或設定tcp連接配接的最大分節大小(mss),傳回值是我們的tcp可以發送給對端的最大資料量,它通常是由對端使用syn分節通告的mss,除非我們的tcp選擇使用一個比對端通告的mss小些的值。如果該值在相應套接字的連接配接建立之前取得,那麼傳回值是從未對端收到mss選項的情況下所用的預設值,還得注意的是,如果用上譬如說時間戳選項的話,那麼實際用于連接配接中的最大分節大小可能小于本地套接字選項的傳回值,因為時間戳選項在每個分節中要占用12位元組的tcp選項容量。

    如果tcp支援路徑mtu發現功能,那麼它将發送的每個分節的最大資料量還可能在連接配接存活期内改變,如果到對端的路徑發生變動,該值就會有所調準。

tcp_nodelay套接字選項:

    開啟本選項将禁止tcp的nagle算法,預設情況下該算法是啟動的。   

    nagle算法的目的在于減少廣域網(wlan)上小分組的數目,該算法指出:如果某個給定連接配接上有待确認的資料(outstanding data),那麼原本應該作為使用者寫操作之響應的在該連接配接上立即發送相應小分組的行為就不會發生,直到現有資料被确認為止。這裡小分組的定義就是小于mss的任何分組,tcp總是盡可能地發送最大大小的分組,nagle算法的目的在于防止一個連接配接在任何時刻有多個小分組待确認。

    假設nagle算法是禁止的,我們将得到如下圖的12個分組。

apue和unp的學習之旅09——套接字選項

但是如果nagle算法是開啟的(預設情形),将得到如下的8個分組。

apue和unp的學習之旅09——套接字選項

(假設使用者每250ms輸入一個字元,上圖忽略了客戶對伺服器的ack)第一個字元獨自作為一個分組,然而下2個字元沒有立即發送,因為該連接配接上有一個小分組待确認。在時刻600ms處收到對端對第一個分組的ack後(該ack由第一個字元的回顯捎帶),這2個字元才發送。在該分組在時刻1200ms處被确認之前,沒有其他小分組被發送,nagle始終保持一個連接配接在任何時刻有多個有多個分組待确認。(待确認資料是指未決資料,也就是已發送,但還在等待對端确認的資料)。

nagle算法常常與另外一個tcp算法聯合使用:ack延滞算法,該算法使得tcp在接收到資料後不立即發送一個ack,而是等待一小段時間(典型值為50~200ms),然後才發送一個ack。tcp期待在這一小段時間内自身有資料發送回對端,被延滞的ack就可以由這些資料捎帶,進而省下一個tcp分節。這種情形對于rlogin和telnet客戶來說通常可行,因為它們的伺服器一般都回顯客戶發送來的每個字元,這樣對用戶端字元的ack完全可以在伺服器對該字元回顯中捎帶傳回。然而對于其伺服器不在相反方向産生資料以便攜帶ack的客戶來說,ack延滞算法存在問題,客戶會感覺到明顯的延遲,因為客戶tcp要等到伺服器的ack延滞定時器逾時才繼續給伺服器發送資料。這些客戶需要一種禁止nagle算法的方法,tcp_nodely選項就能起到這個作用。

//----------------------------------------5.fcntl函數------------------------------------------

#include <fcntl.h>

int fcntl(int fd, int cmd, .../* int arg */);

fcntl函數提供了與網絡程式設計相關的如下特性:

1).非阻塞式i/o:通過使用f_setfl指令設定o_nonblock檔案狀态标志,我們可以把一個套接字設定為非阻塞型。

2).信号驅動式i/o:通過使用f_setfl指令設定o_async檔案狀态标志,我們可以把一個套接字設定成一旦其狀态發生變化,核心就産生一個sigio信号。

3).f_setown指令允許我們指定用于接收sigio和sigurg信号的套接字屬主(程序id或程序組id),其中sigio信号是套接字被設定為信号驅動式i/o型産生的,sigurg信号是在新的帶外資料到達套接字時候産生的,f_getown指令傳回套接字的目前屬主。不得不提的是,信号sigio和信号sigurg與其他信号的不同之處在于:這2個信号僅僅在已使用f_setown指令的整數類型arg參數既可以是一個整數,指出接收信号的程序id,也可以是也給負整數,其絕對值指出接收信号的程序組id。f_getown指令把套接字屬主作為fcntl函數的傳回值傳回,它既可以是程序id(一個正的傳回值)也可以是程序組id(一個除-1外的負值),指定接收信号的套接字屬主為一個程序或一個程序組的差别在于:前者僅僅導緻單個程序接收信号,而後者則導緻整個程序組中的所有程序接收。使用socket函數新建立的套接字并沒有屬主,然而如果一個套接字是從一個監聽套接字建立來的,那麼該套接字屬主講由已連接配接套接字從監聽套接字繼承而來。