如下描述主要是針對windows平臺下的TCP socket而言。
首先須要區分一下關閉socket和關閉TCP鏈接的區別,關閉TCP鏈接是指TCP協議層的東西,就是兩個TCP端之間交換了一些協議包(FIN,RST等),具體的交換過程能夠看TCP協議,這裏不詳細描述了。而關閉socket是指關閉用戶應用程序中的socket句柄,釋放相關資源。可是當用戶關閉socket句柄時會隱含的觸發TCP鏈接的關閉過程。
TCP鏈接的關閉過程有兩種,一種是優雅關閉(graceful close),一種是強制關閉(hard close或abortive close)。所謂優雅關閉是指,若是發送緩存中還有數據未發出則其發出去,而且收到全部數據的ACK以後,發送FIN包,開始關閉過程。而強制關閉是指若是緩存中還有數據,則這些數據都將被丟棄,而後發送RST包,直接重置TCP鏈接。
下面說一下shutdown及closesocket函數。
shutdown函數的原型是:
int shutdown(
SOCKET s,
int how
);
該函數用於關閉TCP鏈接,單並不關閉socket句柄。其第二個參數能夠取三個值:SD_RECEIVE,SD_SEND,SD_BOTH。
SD_RECEIVE代表關閉接收通道,在該socket上不能再接收數據,若是當前接收緩存中仍有未取出數據或者之後再有數據到達,則TCP會向發送端發送RST包,將鏈接重置。
SD_SEND代表關閉發送通道,TCP會將發送緩存中的數據都發送完畢並收到全部數據的ACK後向對端發送FIN包,代表本端沒有更多數據發送。這個是一個優雅關閉過程。
SD_BOTH則表示同時關閉接收通道和發送通道。
closesocket函數的原型是:
int closesocket(
SOCKET s
);
該函數用於關閉socket句柄,並釋放相關資源。前面說過,關閉socket句柄時會隱含觸發TCP鏈接的關閉過程,那麼closesocket觸發的是一個優雅關閉過程仍是強制關閉過程呢?這個與一個socket選項有關:SO_LINGER 選項,該選項的設置值決定了closesocket的行爲。該選項的參數值是linger結構,其定義是:
typedef struct linger {
u_short l_onoff;
u_short l_linger;
} linger;
當l_onoff值設置爲0時,closesocket會當即返回,並關閉用戶socket句柄。若是此時緩衝區中有未發送數據,則系統會在後臺將這些數據發送完畢後關閉TCP鏈接,是一個優雅關閉過程,可是這裏有一個反作用就是socket的底層資源會被保留直到TCP鏈接關閉,這個時間用戶應用程序是沒法控制的。
當l_onoff值設置爲非0值,而l_linger也設置爲0,那麼closesocket也會當即返回並關閉用戶socket句柄,可是若是此時緩衝區中有未發送數據,TCP會發送RST包重置鏈接,全部未發數據都將丟失,這是一個強制關閉過程。
當l_onoff值設置爲非0值,而l_linger也設置爲非0值時,同時若是socket是阻塞式的,此時若是緩衝區中有未發送數據,若是TCP在l_linger代表的時間內將全部數據發出,則發完後關閉TCP鏈接,這時是優雅關閉過程;若是若是TCP在l_linger代表的時間內沒有將全部數據發出,則會丟棄全部未發數據而後TCP發送RST包重置鏈接,此時就是一個強制關閉過程了。
另外還有一個socket選項SO_DONTLINGER,它的參數值是一個bool類型的,若是設置爲true,則等價於SO_LINGER中將l_onoff設置爲0。
注意SO_LINGER和SO_DONTLINGER選項隻影響closesocket的行爲,而與shutdown函數無關,shutdown老是會當即返回的。
因此爲了保證建議的最好的關閉方式是這樣的:
發送完了全部數據後:
(1)調用shutdown(s, SD_SEND),若是本端同時也接收數據時則執行第二步,不然跳到第4步。
(2)繼續接收數據,
(3)收到FD_CLOSE事件後,調用recv函數直到recv返回0或-1(保證收到全部數據),
(4)調用closesocket,關閉socket句柄。
在實際編程中,咱們常常也不調用shutdown,而是直接調用closesocket,利用closesocket隱含觸發TCP鏈接關閉過程的特性。此時的過程就是:
當發送完全部數據後:
(1)若是本端同時也接受數據則則執行第二步,不然跳到第4步。
(2)繼續接收數據,
(3)收到FD_CLOSE事件後,調用recv函數直到recv返回0或-1(保證收到全部數據),
(4)調用closesocket,關閉socket句柄。
可是此時爲了保證數據不丟失,則須要設置SO_DONTLINGER選項,不過windows平臺下這個也是默認設置。
通過實驗發現,發送端應用程序即使是異常退出或被kill掉進程,操做系統也不會丟棄發送緩衝區中的未發送數據,而是會在後臺將這些數據發送出去。可是這是在socket的發送緩存不爲0的前提下,當socket的發送緩存設置爲0(經過SO_SNDBUF選項)時比較特殊,此時不論socket是不是阻塞的,send函數都會被阻塞直到傳入的用戶緩存中的數據都被髮送出去並被確認,由於此時在驅動層沒有分配緩存存放用戶數據,而是直接使用的應用層的用戶緩存,因此必須阻塞直到數據都發出,不然可能會形成系統崩潰。
另外,若是是接收端的應用程序異常退出或被kill掉進程,而且接收緩存中還有數據沒有取出的話,那麼接收端的TCP會向發送端發送RST包,重置鏈接,由於後續數據已經沒法被提交應用層了。
最後這裏說一個感受是windows的bug,就是作這樣的一個測試:
在一端線listen一個socket,而後在另外一端connect,connect成功後,listen端會檢測到網絡事件觸發,在listen端accept以前,將connect端kill掉,而後繼續運行listen端,listen端任然會accept成功,且在accept出來的socket發送數據也能成功。發送完以後在等網絡事件,此時又會等待成功,可是調用WSAEnumNetworkEvents得出的事件標識倒是0。以後不再會等到網絡事件