天天看點

SOCKET選項

在了解SOCKET選項之前請先了解TCP/IP協定的基本知識,如三次握手,四次揮手,11種狀态之間是如何遷移,調用socket各個API會讓TCP層進行什麼操作等。

API頭檔案: sys/socket.h>

擷取指定socket連接配接的選項

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

設定指定socket連接配接的選項

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

example:

int sockfd = socket(AF_INET,SOCK_STREAM,0);
int recvbuf = 0;
int len = sizeof( recvbuf );
getsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, ( socklen_t* )&len);
printf("old recvbuf = %d",recvbuf);
  
recvbuf = 1024*30;
if(0 != setsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof( recvbuf) ))
{
  printf("setsockopt SO_RCVBUF error“);
}           

使用setsockopt()的時間:伺服器要求在listen()之前,用戶端要求在connect()之前,因為有些選項諸如SO_RCVBUF的資訊,是包含在三次握手中,是以必須在三次握手前完成設定,伺服器端accept産生的socket連接配接,會自動繼承listen前設定的大部分選項,如SO_LINGER、SO_SNFBUF、SO_RCVBUF。

參數:

sock:将要被設定或者擷取選項的套接字。

level:選項所在的協定層。

optname:需要通路的選項名。

optval:對于getsockopt(),指向傳回選項值的緩沖。對于setsockopt(),指向包含新選項值的緩沖。

optlen:對于getsockopt(),作為入口參數時,選項值的最大長度。作為出口參數時,選項值的實際長度。對于setsockopt(),現選項的長度。

傳回說明:

成功執行時,傳回0。失敗傳回-1,errno被設為以下的某個值

EBADF:sock不是有效的檔案描述詞

EFAULT:optval指向的記憶體并非有效的程序空間

EINVAL:在調用setsockopt()時,optlen無效

ENOPROTOOPT:指定的協定層不能識别選項

ENOTSOCK:sock描述的不是套接字

參數詳細說明:

level指定控制套接字的層次.可以取三種值:

1)SOL_SOCKET:通用套接字選項.

2)IPPROTO_IP:IP選項.

3)IPPROTO_TCP:TCP選項. 

optname指定控制的方式(選項的名稱)

optval獲得或者是設定套接字選項的值。

常用的Socket中SOL_SOCKET有以下幾個重要的optname選項。

SO_RESUSEADDR:表示是否允許重用Socket所綁定的本地位址

SO_LINGER:表示當執行Socket的close()方法時,是否立即關閉底層的Socket

SO_SNFBUF:表示發送資料的緩沖區大小

SO_RCVBUF:表示接受資料的緩沖區大小

必知必會的選項:

1.SO_REUSEADDR

伺服器可以設定該選項來強制使用處于TIME_WAIT狀态的socket位址。如果有兩個程序,第一個程序的socket處于TIME_WAIT狀态,第二個程序要使用同樣的位址和端口,隻需要第二個程序的socket有設定該選項即可。

應用場景:如果伺服器主動關閉了應用,沒有設定該選項,重新開機伺服器的該應用,會出現失敗,無法綁定端口,因為端口還沒有釋放。需要等待2MSL時間後才能重新綁定,一般情況下是2-4分鐘。

SO_REUSEADDR濫用容易引發bug,UNP1經典書籍列舉了隻有以下四種情況适用該選項:

1.當有一個有相同本地位址和端口的socket1處于TIME_WAIT狀态時,而你啟動的程式的socket2要占用該位址和端口,你的程式就要用到該選項。另外作為服務daemon一般都會使用SO_REUSEADDR,避免服務意外崩潰而原有socket還未被kernel釋放時,重新開機的daemon仍然可以bind成功。

2.SO_REUSEADDR允許同一port上啟動同一伺服器的多個執行個體。但每個執行個體綁定的IP位址是不能相同的。在有多塊網卡或用IP Alias技術的機器可以測試這種情況。

3.SO_REUSEADDR允許單個程序綁定相同的端口到多個socket上,但每個socket綁定的ip位址不同。這和2很相似,差別請看UNPv1。

4.SO_REUSEADDR允許完全相同的位址和端口的重複綁定。但這隻用于UDP的多點傳播,不用于TCP。

有一個濫用該選項導緻的異常例子,

http://blog.chinaunix.net/uid-23629988-id-217123.html

2.SO_LINGER:

此選項指定函數close對面向連接配接的協定如何操作(如TCP),核心預設close操作是将立即傳回,如果有資料殘留在套接口緩沖區中則系統将試着将這些資料發送給對方。close函數是将目前的fd引用數減1,如果引用數已經為0,則在發送隊列的末尾插入FIN标志,給對端發送FIN封包段。

選項中需要用到的結構體如下

struct linger
{ 
int l_onoff //0=off, nonzero=on(開關) 
int l_linger //linger time(延遲時間) 
}            
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct linger so_linger;
so_linger.l_onoff = TRUE;
so_linger.l_linger = 30;
if(0 != setsockopt(sockfd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof (so_linger)))
{
  printf("setsockopt SO_LINGER error“);
}           

1:預設設定

預設狀态下l_onoff==0,此時忽略l_linger,調用closesocket()會将FIN包放到隊列末尾,然後函數傳回

效果是TCP會将隊列殘留的資料發完,然後發送FIN,執行四次揮手。

2:l_onoff!=0&&l_linger==0

調用closesocket(),會清空未發隊列的資料,然後投遞一個RST,函數傳回,這避免了TIME_WAIT狀态。

3:l_onoff!=0&&l_linger!=0

調用closesocket(),向隊列投遞一個FIN,設定一個計時器,大小為l_linger,機關為秒。

1:收到所有發送出去的資料和FIN的ACK時未逾時:

函數傳回,并執行四次揮手的剩餘步驟。(當應用層調用close時候,實際上伺服器已經處于close_wait狀态,已經完成了四次揮手的前兩個步驟)

2:逾時:

函數傳回SOCKET_ERROR,WINDOW系統下,WSAGetLastError()傳回EWOULDBLOCK,清空隊列,在對方發送FIN後,發送RET

3:備注:

對于1所述情況,我們隻能确定對方TCP已經收到資料,但無法确定對方程序是否已經收到資料。

因為對端接受到FIN後,TCP層會回發一個應答封包,對端從FIN_WAIT1狀态遷移到TIME_WAIT狀态,在2MSL後,從TIME_WAIT狀态遷移到CLOSED狀态。這一部分工作都是TCP在自動執行,不需要上層程序操作,是以我們無法确定對方程序是否已經收到資料。

繼續閱讀