以下描述主要是針對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