如何正確關閉 tcp 連接?

* 主動關閉
調用 ::shutdown(sockfd, SHUT_WR) 主動關閉 socket 後,是否有必要 ::close(sockfd)

* 被動關閉
::read() 返回0的時候,是否有必要 ::close(sockfd)

在 muduo 庫中,TcpConnection 里沒有找調用 ::close(sockfd) 的代碼,反而發現下面的注釋:
// we don"t close fd, leave it to dtor, so we can find leaks easily.
我在 ~TcpConnection() 中主動 ::close(sockfd) 後,程序運行也是正常的。

所以如何正確關閉 tcp 連接? muduo 庫為什麼沒有主動調用 ::close(sockfd) 的代碼?僅僅為了 find leaks 嗎? 還是有其他我沒了解到的用途?

參考鏈接:
muduo/TcpConnection.cc at master · chenshuo/muduo · GitHub 413 行
http://stackoverflow.com/questions/28056056/handling-ssl-shutdown-correctly


shutdown(both)還需要close,不然你就泄漏了一個或幾個handle或fd以及相關資源。

read返回0表示你收到了對方發來的fin,這可能是對方調用shutdown(send),也可能是對方調用了close()。從tcp協議本身來說,你是無法知道對方到底是調用了close()還是調用了shutdown(send)的,os的tcp協議棧也不知道。因此此時是否要close取決於你的應用。通常來說如果對方調用的是close,那麼你也可以close。否則你不能close,例如對方發送一個包給你並shutdown write然後調用recv,這時候你還可以返回一個或多個包,連接此時處於半關閉狀態,可以一直持續。這麼做的客戶端不多(connect, send, shtudown(send), recv();),但的確有,而且是完全合法的。如果通訊雙方都是你自己的代碼,那麼你知道是哪種情況。如果你不能了解對方的代碼,甚至你是個proxy,兩邊代碼你都不了解,那麼通常來說你不能close。

很多server/proxy的實現為當read返回0就close,這種實現是錯誤的,這個實現無法兼容剛才我說的那種情況。對於proxy來說,正確的做法是透傳雙方的行為。因此,當你read(client_side_socket)返回0時,你應該對另外一端調用shutdown(server_side_socket, send),這樣伺服器就會read返回0,你透明的傳遞了這個行為。那麼作為proxy,你什麼時候才能close呢?client_socket和server_socket上read都返回了0,或者有任何一方返回了-1時你可以close。當然你也可以考慮設置一個超時時間,如果線路上超過5分鐘沒有數據你就斷開,但這是另一個維度的問題。

關於close,要注意的是默認情況下它是一個非同步的過程。作為proxy來說,如果你想避免大量close_wait,那麼你可以在close之前shutdown,然後啟動一個5s的delaytask,在delaytask里設置超時時間為0的so_linger,然後close,對socket進行hard close。這時候close是同步的,如果此時不能優雅關閉,那麼系統會立刻強制關閉。

我沒有看muduo的代碼。如果你在2處close同一個socket,這是高危行為。因為2次close之間很可能會有一個新socket產生並且值和你第一次close的那個一樣。你第二次close就會錯誤的close了一個不屬於你的socket。這種錯誤非常難查,因此絕對不要幹這種事。


socket_ 是作為TcpConnection的scoped_ptr 成員變數存在的,而Socket的析構函數會自己調用close。

猜測:
因此,只要你應用層使用TcpConnection沒有忘記釋放掉,那就不會存在socket沒有釋放的情況。這麼做,你netstat看到的socket和TcpConnection是對應的,否則TcpConnection中關掉了socket,但是TcpConnection本身沒有釋放,這種leaks就不容易排查了,這也是為啥作者寫這句注釋「we don"t close fd, leave it to dtor, so we can find leaks easily.」


你「在 ~TcpConnection() 中主動 ::close(sockfd)」是錯誤的。
你可以跟蹤一下 muduo 在哪裡調用 ::close()。


read到EOF的時候表示對方關閉連接了,通常對伺服器來說不要主動關閉連接,因為那會讓TCP進入到time-wait 狀態,從這個狀態到最後的close狀態,需要很長時間,如果太多time wait狀態,可能會影響新連接的建立。相對應的是讓client去關閉連接,這樣伺服器會進入close-wait狀態,伺服器再關閉連接,這樣連接很快就可已重用了。當然,如果發現TCP連接很多長時間處於close-wait狀態,那麼說明伺服器有句柄泄露(沒有調用close)


read為0的時候有可能對方真的只發了0個數據。我習慣得到-1再檢查出錯碼來關閉


推薦閱讀:

同一網段內的兩台主機通信是否需要路由器?
假設網路中出現了一個全是FFFF的IP數據包,那這個數據包將會如何被處理?
Websocket需要像TCP Socket那樣進行邏輯數據包的分包與合包嗎?

TAG:計算機網路 | Socket | TCPIP |