以下描述主要是針對windows平臺下的TCP socket而言。 首先需要區(qū)分一下關閉socket和關閉TCP連接的區(qū)別,關閉TCP連接是指TCP協(xié)議層的東西,就是兩個TCP端之間交換了一些協(xié)議包(FIN,RST等),具體的交換過程可以看TCP協(xié)議,這里不詳細描述了。而關閉socket是指關閉用戶應用程序中的socket句柄,釋放相關資源。但是當用戶關閉socket句柄時會隱含的觸發(fā)TCP連接的關閉過程。 TCP連接的關閉過程有兩種,一種是優(yōu)雅關閉(graceful close),一種是強制關閉(hard close或abortive close)。所謂優(yōu)雅關閉是指,如果發(fā)送緩存中還有數據未發(fā)出則其發(fā)出去,并且收到所有數據的ACK之后,發(fā)送FIN包,開始關閉過程。而強制關閉是指如果緩存中還有數據,則這些數據都將被丟棄,然后發(fā)送RST包,直接重置TCP連接。 下面說一下shutdown及closesocket函數。 shutdown函數的原型是: int shutdown( SOCKET s, int how ); 該函數用于關閉TCP連接,但并不關閉socket句柄。其第二個參數可以取三個值:SD_RECEIVE,SD_SEND,SD_BOTH。 SD_RECEIVE表明關閉接收通道,在該socket上不能再接收數據,如果當前接收緩存中仍有未取出數據或者以后再有數據到達,則TCP會向發(fā)送端發(fā)送RST包,將連接重置。 SD_SEND表明關閉發(fā)送通道,TCP會將發(fā)送緩存中的數據都發(fā)送完畢并在收到所有數據的ACK后向對端發(fā)送FIN包,表明本端沒有更多數據發(fā)送。這個是一個優(yōu)雅關閉過程。 SD_BOTH則表示同時關閉接收通道和發(fā)送通道。 closesocket函數的原型是: int closesocket( SOCKET s ); 該函數用于關閉socket句柄,并釋放相關資源。前面說過,關閉socket句柄時會隱含觸發(fā)TCP連接的關閉過程,那么closesocket觸發(fā)的是一個優(yōu)雅關閉過程還是強制關閉過程呢? 這個與一個socket選項有關:SO_LINGER 選項,該選項的設置值決定了closesocket的行為。該選項的參數值是linger結構,其定義是: typedef struct linger { u_short l_onoff; u_short l_linger; } linger; 當setsockopt函數設置了SO_LINGER時,有下列三種情況:
msdn中還有一條提醒:不推薦在非阻塞套接字中使用SO_LINGER SO_DONTLINGER 選項為TRUE或FALSE表示禁用SO_LINGER或不禁用SO_LINGER,
注意SO_LINGER和SO_DONTLINGER選項只影響closesocket的行為,而與shutdown函數無關,shutdown總是會立即返回的。 所以建議的最好的關閉方式是這樣的: 發(fā)送完了所有數據后: (1)調用shutdown(s, SD_SEND),如果本端同時也接收數據時則執(zhí)行第二步,否則跳到第4步。 (2)繼續(xù)接收數據, (3)收到FD_CLOSE事件后,調用recv函數直到recv返回0或-1(保證收到所有數據), (4)調用closesocket,關閉socket句柄。 在實際編程中,我們經常也不調用shutdown,而是直接調用closesocket,利用closesocket隱含觸發(fā)TCP連接關閉過程的特性。此時的過程就是: 當發(fā)送完所有數據后: (1)如果本端同時也接受數據則執(zhí)行第二步,否則跳到第4步。 (2)繼續(xù)接收數據, (3)收到FD_CLOSE事件后,調用recv函數直到recv返回0或-1(保證收到所有數據), (4)調用closesocket,關閉socket句柄。 但是此時為了保證數據不丟失,則需要設置SO_DONTLINGER選項,不過windows平臺下這個也是默認設置。 經過實驗發(fā)現,發(fā)送端應用程序即便是異常退出或被kill掉進程,操作系統(tǒng)也不會丟棄發(fā)送緩沖區(qū)中的未發(fā)送數據,而是會在后臺將這些數據發(fā)送出去。但是這是在socket的發(fā)送緩存不為0的前提下,當socket的發(fā)送緩存設置為0(通過SO_SNDBUF選項)時比較特殊,此時不論socket是否是阻塞的,send函數都會被阻塞直到傳入的用戶緩存中的數據都被發(fā)送出去并被確認,因為此時在驅動層沒有分配緩存存放用戶數據,而是直接使用的應用層的用戶緩存,所以必須阻塞直到數據都發(fā)出,否則可能會造成系統(tǒng)崩潰。 另外,如果是接收端的應用程序異常退出或被kill掉進程,并且接收緩存中還有數據沒有取出的話,那么接收端的TCP會向發(fā)送端發(fā)送RST包,重置連接,因為后續(xù)數據已經無法被提交應用層了。 最后這里說一個感覺是windows的bug,就是做這樣的一個測試: 在一端線listen一個socket,然后在另一端connect,connect成功后,listen端會檢測到網絡事件觸發(fā),在listen端accept之前,將connect端kill掉,然后繼續(xù)運行l(wèi)isten端,listen端任然會accept成功,且在accept出來的socket發(fā)送數據也能成功。發(fā)送完之后在等網絡事件,此時又會等待成功,但是調用WSAEnumNetworkEvents得出的事件標識卻是0。之后再也不會等到網絡事件。 |
|