windows下socket編程:區分shutdown()及closesocket()

以下描述主要是針對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。之後再也不會等到網絡事件。

 

轉自http://blog.csdn.net/Bad_Sheep/archive/2011/01/21/6157738.aspx


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章