拔掉網線幾秒,再插回去,原本的 TCP 連接還存在嗎?
可能有的同學會說,網線都被拔掉了,那說明物理層被斷開了,那在上層的傳輸層理應也會斷開,所以原本的 TCP 連接就不會存在的了。就好像,我們撥打有線電話的時候,如果某一方的電話線被拔了,那麼本次通話就彻底斷了。
其實這樣的認知是錯誤的,上面這個邏輯就有問題。問題在於,錯誤的認為拔掉網線這個動作會影響傳輸層,事實上並不會影響。
實際上,TCP 連接在 Linux 內核中是一個名為 struct socket
的結構體,該結構體的內容包含 TCP 連接的狀態等信息。當拔掉網線的時候,操作系統並不會變更該結構體的任何內容,所以 TCP 連接的狀態也不會發生改變。
可以通過比如 ssh 與遠端伺服器建立 TCP 連接,然後斷開本機網路來模擬拔掉網線的場景,然後通過以下命令查看 TCP 連接的狀態,可以發現還是 ESTABLISHED 狀態:
# linux
# netstat: https://wangchujiang.com/linux-command/c/netstat.html
# or ss: https://wangchujiang.com/linux-command/c/ss.html
#
# grep: https://wangchujiang.com/linux-command/c/grep.html
sudo netstat -anp tcp | grep tcp | grep [dst_ip]
# windows
# netstat: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/netstat
# or TCPView: https://learn.microsoft.com/en-us/sysinternals/downloads/tcpview
#
# findstr: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/findstr
netstat -anp tcp | findstr [dst_ip]
現在,我們知道拔掉網線的動作並不會影響 TCP 連接的狀態。主要是看拔掉網線後,雙方做了什麼動作,針對這個問題,要分場景來討論,即拔掉網線後,有無數據傳輸。
拔掉網線後,有數據傳輸#
在客戶端拔掉網線後,服務端向客戶端發送的數據報文會得不到任何的響應,在等待一定時長後,服務端就會觸發超時重傳機制,重傳未得到響應的數據報文。
如果在服務端重傳報文的過程中,客戶端剛好把網線插回去了,由於拔掉網線並不會改變客戶端的 TCP 連接狀態,並且還是處於 ESTABLISHED 狀態,所以這時客戶端是可以正常接收服務端發來的數據報文的,然後客戶端就會回 ACK 響應報文。此時,客戶端和服務端的 TCP 連接依然存在的,就感覺什麼事情都沒有發生。
但是,如果在服務端重傳報文的過程中,客戶端一直沒有將網線插回去,服務端超時重傳報文的次數達到一定閾值後,內核就會判定出該 TCP 有問題,然後通過 Socket 接口告訴應用程序該 TCP 連接出問題了,於是服務端的 TCP 連接就會斷開。而等客戶端插回網線後,如果客戶端向服務端發送了數據,由於服務端已經沒有與客戶端相同四元組的 TCP 連接了,因此服務端內核就會回復 RST 報文,客戶端收到後就會釋放該 TCP 連接。此時,客戶端和服務端的 TCP 連接都已經斷開了。
Q:那 TCP 的數據報文具體重傳幾次呢?
A:在 Linux 系統中,提供了一個叫 tcp_retries2 配置項(/proc/sys/net/ipv4/tcp_retries2),默認值是 15,這個內核參數是控制在 TCP 連接建立的情況下,超時重傳的最大次數。
不過 tcp_retries2 設置了 15 次,並不代表 TCP 超時重傳了 15 次才會通知應用程序終止該 TCP 連接,內核會根據 tcp_retries2 設置的值,計算出一個 timeout(如果 tcp_retries2 =15,那麼計算得到的 timeout = 924600 ms),如果重傳間隔超過這個 timeout,則認為超過了閾值,就會停止重傳,然後就會斷開 TCP 連接。
在發生超時重傳的過程中,每一輪的超時時間(RTO)都是倍數增長的,比如如果第一輪 RTO 是 200 毫秒,那麼第二輪 RTO 是 400 毫秒,第三輪 RTO 是 800 毫秒,以此類推。
而 RTO 是基於 RTT(一個包的往返時間) 來計算的,如果 RTT 較大,那麼計算出來的 RTO 就越大,那麼經過幾輪重傳後,很快就達到了上面的 timeout 值了。
舉個例子,如果 tcp_retries2 =15,那麼計算得到的 timeout = 924600 ms,如果重傳總間隔時長達到了 timeout 就會停止重傳,然後就會斷開 TCP 連接:
- 如果 RTT 比較小,那麼 RTO 初始值就約等於下限 200ms,也就是第一輪的超時時間是 200 毫秒,由於 timeout 總時長是 924600 ms,表現出來的現象剛好就是重傳了 15 次,超過了 timeout 值,從而斷開 TCP 連接。
- 如果 RTT 比較大,假設 RTO 初始值計算得到的是 1000 ms,也就是第一輪的超時時間是 1 秒,那麼根本不需要重傳 15 次,重傳總間隔就會超過 924600 ms。
最小 RTO 和最大 RTO 是在 Linux 內核中定義好了:
#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5))
Linux 2.6+ 使用 1000 毫秒的 HZ,因此TCP_RTO_MIN
約為 200 毫秒,TCP_RTO_MAX
約為 120 秒。
如果tcp_retries
設置為15
,且 RTT 比較小,那麼 RTO 初始值就約等於下限 200ms,這意味著它需要 924.6 秒才能將斷開的 TCP 連接通知給上層(即應用程序),每一輪的 RTO 增長關係如下表格:
拔掉網線後,沒有數據傳輸#
針對拔掉網線後,沒有數據傳輸的場景,還得看是否開啟了 TCP keepalive 機制 (TCP 保活機制)。
如果沒有開啟 TCP keepalive 機制,在客戶端拔掉網線後,並且雙方都沒有進行數據傳輸,那麼客戶端和服務端的 TCP 連接將會一直保持存在。
而如果開啟了 TCP keepalive 機制,在客戶端拔掉網線後,即使雙方都沒有進行數據傳輸,在持續一段時間後,TCP 就會發送探測報文: