如果A機器與B機器網絡connect成功後從未互發過資料,此時其中一機器突然斷電,則另外一台機器與斷電的機器之間的網絡連接配接處于哪種狀态?
筆者實測如下:
虛拟機A:
CentOS 7,192.168.133.131,TCP Server
虛拟機B:
CentOS 7,192.168.133.128,TCP Client
1、建立連接配接時:
A機器
[root@localhost ~]# netstat -nalp|grep 1883
tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 48261/epltest
tcp 0 0 192.168.133.131:1883 192.168.133.128:39170 ESTABLISHED 48261/epltest
B機器
[root@localhost firecat]# netstat -nalp|grep 1883
tcp 0 0 192.168.133.128:39170 192.168.133.131:1883 ESTABLISHED 7470/./eplClient
2、B機器斷電時,A機器檢視狀态如下:
tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 48261/epltest
結論:斷電後,繼續保持ESTABLISHED的網絡狀态。
3、筆者繼續實測,讓A斷電,B保持,結論還是一樣,ESTABLISHED。
4、如果A不變,B機器kill client程序,則B機器會出現TIME_WAIT的網絡狀态。
[root@localhost firecat]# kill -9 7459
tcp 0 0 192.168.133.128:60114 192.168.133.131:1883 TIME_WAIT -
5、我們如果手動拔掉網線,在沒有心跳機制的前提下,現象也是一樣的。
6、具體原因如下:
什麼是半開連接配接?
當用戶端與伺服器建立起正常的TCP連接配接後,如果客戶主機掉線(網線斷開)、電源掉電、或系統崩潰,伺服器程序将永遠不會知道(通過我們常用的select,epoll監測不到斷開或錯誤事件),如果不主動處理或重新開機系統的話對于服務端來說會一直維持着這個連接配接,任憑服務端程序如何望穿秋水,也永遠再等不到用戶端的任何回應。這種情況就是半開連接配接,浪費了伺服器端可用的檔案描述符。
如何處理?
熟悉套接字通用選項的朋友一定已經有了想法。TCP套接字不是有個保持存活選項SO_KEEPALIVE嘛,如果在兩個小時之内在該套接字的任何一個方向上都沒資料交換,TCP就自動給對端發送一個保持存活探測分節,如果此TCP探測分節的響應為RST,說明對端已經崩潰且已經重新啟動,該套接字的待處理錯誤被置為ECONNRESET,套接字本身則被關閉。如果沒有對此TCP探測分節的任何響應,該套接字的處理錯誤就被置為ETIMEOUT,套接字本身則被關閉。
确實,這個選項确實可以處理我們前面遇到的TCP半開連接配接的問題,但是預設兩小時間隔探測的實時性是不是差了些呢?當然,我們可以通過修改核心參數改小時間間隔,完美了吧?但是必須注意的是大多數核心是基于整個核心維護這些時間參數的,而不是基于每個套接字維護的,是以如果把無活動周期從兩小時改為(比如)2分鐘,那将影響到該主機上所有開啟了此選項的套接字。我想大家都不會願意承擔伺服器端的這種不确定性吧。另外,心跳除了說明應用程式還活着(程序存在,網絡暢通),更重要的是表明應用程式能正常工作。而SO_KEEPALIVE由作業系統負責探查,即便是程序死鎖或有其他異常,作業系統也會正常收發TCP keepalive消息,而對方無法得知這一異常。
沒關系,其實我們可以在應用層模拟SO_KEEPALIVE的方式,用心跳包來模拟保活探測分節。由于伺服器通常要承擔成千上萬的并發連接配接,是以肯定是由用戶端在應用層進行心跳來模拟保活探測分節,用戶端多次收不到伺服器的響應時可終止此TCP連接配接,而服務端可監測用戶端的心跳包,若在一定時間間隔内未收到任何來自用戶端的心跳包則可以終止此TCP連接配接,這樣就有效避免了TCP半開連接配接的情況。