1. 程式人生 > 其它 >TCP的CLOSE_WAIT 和 TIME_WAIT(轉)

TCP的CLOSE_WAIT 和 TIME_WAIT(轉)

原文:https://xie.infoq.cn/article/e82a53c6d69fb9852e68ce8da

作者:linux大本營

CLOSE_WAIT 和 TIME_WAIT 是如何產生的?大量的 CLOSE_WAIT 和 TIME_WAIT 又有何隱患?本文將通過實踐角度來帶你揭開 CLOSE_WAIT 和 TIME_WAIT 的神祕面紗!遇事不決先祭此圖:

稍微補充一點:TIME_WAIT 到 CLOSED,這一步是超時自動遷移。

兩條豎線分別是表示:

  • 主動關閉(active close)的一方

  • 被動關閉(passive close)的一方

網路上類似的圖有很多,但是有的細節不夠,有的存在誤導。有的會把兩條線分別標記成 Client 和 Server。給讀者造成困惑。對於斷開連線這件事,客戶端和服務端都能作為主動方發起,也就是「主動關閉」可以是客戶端,可以是服務端。而對端相應的就是「被動關閉」。不管誰發起,狀態遷移如上圖。

1. 耗盡的是誰的埠?

首先解答初學者對 socket 的認識存在一個常見的誤區。作為服務端,不管哪個 WAIT 都不會耗盡客戶端的埠!

舉個例子,我在某雲的雲主機上有個 Server 程式:echo_server。我啟動它,監聽 2605 埠。然後我在自己的 MacBook 上用 telnet 去連線它。連上之後,在雲主機上用 netstat -anp 看一下:

[guodong@yuntest]netstat-anp|grep2605
tcp000.0.0.0:26050.0.0.0:*LISTEN3354/./echo_server
tcp0172.12.0.2:2605xx.xx.xx.xx:31559ESTABLISHED3354
/./echo_server

xx.xx.xx.xx 是我 Macbook 的本機 IP

其中有兩條記錄,LISTEN 的表示是我的 echo_server 監聽一個埠。ESTABLISHED 表示已經有一個客戶端連線了。第三列的 IP 埠是我 echo_server 的(這個顯示 IP 是區域網的;第四列顯示的是客戶端的 IP 和埠,也就是我 MacBook。

要說明的是這個埠:31559 是客戶端的。這個是建立連線時的 MacBook 分配的隨機埠。

我們看一下 echo_server 佔用的 fd。使用 ls /proc/3354/fd -l 命令檢視,其中 3354 是 echo_server 的 pid:

0->/dev/pts/6
1->/dev/pts/6
2->/dev/pts/6
3->anon_inode:[eventpoll]
4->socket:[674802865]
5->socket:[674804942]

0,1,2 是三巨頭(標準輸入,輸出,錯誤)自不必言。3 是因為我使用了 epoll,所以有一個 epfd。

4 其實就是我服務端監聽埠開啟的被動套接字;

5 就是客戶端建立連線到時候,分配給客戶端的連線套接字。server 程式只要給 5 這個 fd 寫資料,就相當於返回資料給客戶端。

服務端怎麼會耗盡客戶端的埠號的。這裡消耗的其實是服務端的 fd(也不是埠)!

2. 什麼時候出現 CLOSE_WAIT?

2.1 舉個例子

回到我的 MacBook 終端,檢視一下 2605 有關的連線(Mac 上 netstat 不太好用,只能用 lsof 了):

[guodong@MacBooktest]lsof-iTCP:2605
COMMANDPIDUSERFDTYPEDEVICESIZE/OFFNODENAME
telnet74131guodong3uIPv40x199db390a76b3eb30t0TCP192.168.199.155:50307->yy.yy.yy.yy:nsc-posa(ESTABLISHED)

yy.yy.yy.yy 表示的遠端雲主機的 IP

nsc-posa 其實就是埠 2605,因為 2605 也是某個經典協議(NSC POSA)的預設埠,所以這種網路工具直接顯示成了那個協議的名稱。

客戶端 pid 為 74135。當然,我其實知道我是用 telnet 連線的,只是為了查 pid 的話,ps aux|grep telnet 也可以。

注意:為了測試。我這裡的 echo_server 是寫的有問題的。就是沒有處理客戶端異常斷開的事件。

下面我 kill 掉 telnet(kill -9 74131)。再回到雲主機檢視一下:

[guodong@yuntest]netstat-anp|grep2605
tcp000.0.0.0:26050.0.0.0:*LISTEN3354/./echo_server
tcp1172.12.0.2:2605xx.xx.xx.xx:31559CLOSE_WAIT3354/./echo_server

由於 echo_server 內沒對連線異常進行偵測和處理。所以可以看到原先 ESTABLISHED 的連線變成了 CLOSE_WAIT。並且會持續下去。我們再看一下它開啟的 fd:

0->/dev/pts/6
1->/dev/pts/6
2->/dev/pts/6
3->anon_inode:[eventpoll]
4->socket:[674865719]
5->socket:[674865835]

5 這個 fd 還存在,並且會一直存在。所以當有大量 CLOSE_WAIT 的時候會佔用伺服器的 fd。而一個機器能開啟的 fd 數量是有限的。超過了,因為無法分配 fd,就無法建立新連線啦!

2.2 怎麼避免客戶端異常斷開時的服務端 CLOSE_WAIT?

有一個方法。比如我用了 epoll,那麼我監聽客戶端連線套接字(5)的 EPOLLRDHUP 這個事件。當客戶端意外斷開時,這個事件就會被觸發,觸發之後。我們針對性的對這個 fd(5)執行 close()操作就可以了。改下程式碼,重新模擬一下上述流程,blabla 細節略過。現在我們新 echo_server 啟動。MacBook 的 telnet 連線成功。然後我 kill 掉了 telnet。觀察一下雲主機上的狀況:

[guodong@yuntest]netstat-anp|grep2605
tcp000.0.0.0:26050.0.0.0:*LISTEN7678/./echo_server
tcp1172.12.0.2:2605xx.xx.xx.xx:31559LAST_ACK-

出現了 LAST_ACK。我們看下 fd。命令:ls /proc/7678/fd -l

0->/dev/pts/6
1->/dev/pts/6
2->/dev/pts/6
3->anon_inode:[eventpoll]
4->socket:[674905737]
fd(5)其實已經關閉了。過一會我們重新 netstat 看下:
[guodong@yun test]netstat -anp|grep 2605
tcp    0    0 0.0.0.0:2605    0.0.0.0:*            LISTEN      7678/./echo_server

LAST_ACK 也消失了。為什麼出現 LAST_ACK。翻到開頭,看我那張圖啊!

CLOSE_WAIT 不會自動消失,而 LAST_TACK 會超時自動消失,時間很短,即使在其存續期內,fd 其實也是關閉狀態。實際我這個簡單的程式,測試的時候不會每次都捕捉到 LAST_WAIT。有時候用 netstat 命令檢視的時候,就是最終那副圖了。

3. 什麼時候出現 TIME_WAIT?

看我開篇那個圖就知道了。

現在我 kill 掉我的 echo_server!

[guodong@yuntest]netstat-anp|grep2605
tcp00172.17.0.2:2605xx.xx.xx.xx:51327TIME_WAIT-

雲主機上原先 ESTABLISHED 的那條瞬間變成 TIME_WAIT 了。

這個 TIME_WAIT 也是超時自動消失的。時間是 2MSL。MSL 是多長?

cat/proc/sys/net/ipv4/tcp_fin_timeout

一般是 60。2MSL 也就 2 分鐘。在 2 分鐘之內,對服務端有啥副作用嗎?有,但問題不大。那就是這期間重新啟動 Server 會報端口占用。這個等待,一方面是擔心對方收不到自己的確認,等對方重發 FIN。另一方面 2MSL 是報文的最長生命週期,可以避免 Server 重啟(或其他 Server 綁同樣埠)接收到了上一次的資料。

當然這個 2MLS 的等待,也可以通過給 socket 新增選項(SO_REUSEADDR)的方式來避免。Server 可以立即重啟(這樣 Server 的監控程序就可以放心的重新拉起 Server 啦)。

通常情況下 TIME_WAIT 對服務端影響有限,而大量 CLOSE_WAIT 風險較高,但正確編寫程式碼基本可以避免。為什麼只說通常情況呢?因為生產環境是複雜的,一個服務通常會和多個下游服務用各種各樣的協議進行通訊。TIME_WAIT 和 CLOSE_WAIT 在一些異常條件下,還是會觸發的。

並不是說 TIME_WAIT 就真的無風險,其實無論是 TIME_WAIT 還是 CLOSE_WAIT,永遠記住當你的服務出現這兩種現象的時候,它們只是某個問題導致的結果,而不是問題本身。有些網路教程教你怎麼調大這個或那個的 OS 系統設定,個人感覺只是治標不治本。找到本質原因,避免 TIME_WAIT 和 CLOSE_WAIT 的產生,才是問題解決之道!

把這些瞭解清楚時候,是不是可以輕鬆應對什麼 4 次揮手之類的面試題了?