拔掉網線後, 原本的 TCP 連線還存在嗎?

小林coding發表於2022-02-14

大家好,我是小林。

今天,聊一個有趣的問題:拔掉網線幾秒,再插回去,原本的 TCP 連線還存在嗎?

可能有的同學會說,網線都被拔掉了,那說明物理層被斷開了,那在上層的傳輸層理應也會斷開,所以原本的 TCP 連線就不會存在的了。就好像, 我們撥打有線電話的時候,如果某一方的電話線被拔了,那麼本次通話就徹底斷了。

真的是這樣嗎?

上面這個邏輯就有問題。問題在於,錯誤的認為拔掉網線這個動作會影響傳輸層,事實上並不會影響。

實際上,TCP 連線在 Linux 核心中是一個名為 struct socket 的結構體,該結構體的內容包含 TCP 連線的狀態等資訊。當拔掉網線的時候,作業系統並不會變更該結構體的任何內容,所以 TCP 連線的狀態也不會發生改變。

我在我的電腦上做了個小實驗,我用 ssh 終端連線了我的雲伺服器,然後我通過斷開 wifi 的方式來模擬拔掉網線的場景,此時檢視 TCP 連線的狀態沒有發生變化,還是處於 ESTABLISHED 狀態。

圖片圖片

通過上面這個實驗結果,我們知道了,拔掉網線這個動作並不會影響 TCP 連線的狀態。

接下來,要看拔掉網線後,雙方做了什麼動作。

所以, 針對這個問題,要分場景來討論:

  • 拔掉網線後,有資料傳輸;
  • 拔掉網線後,沒有資料傳輸;

拔掉網線後,有資料傳輸

在客戶端拔掉網線後,服務端向客戶端傳送的資料包文會得不到任何的響應,在等待一定時長後,服務端就會觸發超時重傳機制,重傳未得到響應的資料包文。

如果在服務端重傳報文的過程中,客戶端剛好把網線插回去了,由於拔掉網線並不會改變客戶端的 TCP 連線狀態,並且還是處於 ESTABLISHED 狀態,所以這時客戶端是可以正常接收服務端發來的資料包文的,然後客戶端就會回 ACK 響應報文。

此時,客戶端和服務端的 TCP 連線依然存在的,就感覺什麼事情都沒有發生。

但是,如果如果在服務端重傳報文的過程中,客戶端一直沒有將網線插回去,服務端超時重傳報文的次數達到一定閾值後,核心就會判定出該 TCP 有問題,然後通過 Socket 介面告訴應用程式該 TCP 連線出問題了,於是服務端的 TCP 連線就會斷開。

而等客戶端插回網線後,如果客戶端向服務端傳送了資料,由於服務端已經沒有與客戶端相同四元祖的 TCP 連線了,因此服務端核心就會回覆 RST 報文,客戶端收到後就會釋放該 TCP 連線。

此時,客戶端和服務端的 TCP 連線都已經斷開了。

那 TCP 的資料包文具體重傳幾次呢?

在 Linux 系統中,提供了一個叫 tcp_retries2 配置項,預設值是 15,如下圖:

圖片圖片

這個核心引數是控制,在 TCP 連線建立的情況下,超時重傳的最大次數。

不過 tcp_retries2 設定了 15 次,並不代表 TCP 超時重傳了 15 次才會通知應用程式終止該 TCP 連線,核心還會基於「最大超時時間」來判定。

每一輪的超時時間都是倍數增長的,比如第一次觸發超時重傳是在 2s 後,第二次則是在 4s 後,第三次則是 8s 後,以此類推。

圖片圖片

核心會根據 tcp_retries2 設定的值,計算出一個最大超時時間。

在重傳報文且一直沒有收到對方響應的情況時,先達到「最大重傳次數」或者「最大超時時間」這兩個的其中一個條件後,就會停止重傳,然後就會斷開 TCP 連線。

拔掉網線後,沒有資料傳輸

針對拔掉網線後,沒有資料傳輸的場景,還得看是否開啟了 TCP keepalive 機制 (TCP 保活機制)。

如果沒有開啟 TCP keepalive 機制,在客戶端拔掉網線後,並且雙方都沒有進行資料傳輸,那麼客戶端和服務端的 TCP 連線將會一直保持存在。

而如果開啟了 TCP keepalive 機制,在客戶端拔掉網線後,即使雙方都沒有進行資料傳輸,在持續一段時間後,TCP 就會傳送探測報文:

  • 如果對端是正常工作的。當 TCP 保活的探測報文傳送給對端, 對端會正常響應,這樣 TCP 保活時間會被重置,等待下一個 TCP 保活時間的到來。
  • 如果對端主機崩潰,或對端由於其他原因導致報文不可達。當 TCP 保活的探測報文傳送給對端後,石沉大海,沒有響應,連續幾次,達到保活探測次數後,TCP 會報告該 TCP 連線已經死亡

所以,TCP 保活機制可以在雙方沒有資料互動的情況,通過探測報文,來確定對方的 TCP 連線是否存活。

TCP keepalive 機制具體是怎麼樣的?

這個機制的原理是這樣的:

定義一個時間段,在這個時間段內,如果沒有任何連線相關的活動,TCP 保活機制會開始作用,每隔一個時間間隔,傳送一個探測報文,該探測報文包含的資料非常少,如果連續幾個探測報文都沒有得到響應,則認為當前的 TCP 連線已經死亡,系統核心將錯誤資訊通知給上層應用程式。

在 Linux 核心可以有對應的引數可以設定保活時間、保活探測的次數、保活探測的時間間隔,以下都為預設值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200:表示保活時間是 7200 秒(2小時),也就 2 小時內如果沒有任何連線相關的活動,則會啟動保活機制
  • tcp_keepalive_intvl=75:表示每次檢測間隔 75 秒;
  • tcp_keepalive_probes=9:表示檢測 9 次無響應,認為對方是不可達的,從而中斷本次的連線。

也就是說在 Linux 系統中,最少需要經過 2 小時 11 分 15 秒才可以發現一個「死亡」連線。

圖片圖片

注意,應用程式若想使用 TCP 保活機制需要通過 socket 介面設定 SO_KEEPALIVE 選項才能夠生效,如果沒有設定,那麼就無法使用 TCP 保活機制。

TCP keepalive 機制探測的時間也太長了吧?

對的,是有點長。

TCP keepalive 是 TCP 層(核心態) 實現的,它是給所有基於 TCP 傳輸協議的程式一個兜底的方案。

實際上,我們應用層可以自己實現一套探測機制,可以在較短的時間內,探測到對方是否存活。

比如,web 服務軟體一般都會提供 keepalive_timeout 引數,用來指定 HTTP 長連線的超時時間。如果設定了 HTTP 長連線的超時時間是 60 秒,web 服務軟體就會啟動一個定時器,如果客戶端在完後一個 HTTP 請求後,在 60 秒內都沒有再發起新的請求,定時器的時間一到,就會觸發回撥函式來釋放該連線。

圖片圖片

總結

客戶端拔掉網線後,並不會直接影響 TCP 連線狀態。所以,拔掉網線後,TCP 連線是否還會存在,關鍵要看拔掉網線之後,有沒有進行資料傳輸。

有資料傳輸的情況:

  • 在客戶端拔掉網線後,如果服務端傳送了資料包文,那麼在服務端重傳次數沒有達到最大值之前,客戶端就插回了網線,那麼雙方原本的 TCP 連線還是能正常存在,就好像什麼事情都沒有發生。
  • 在客戶端拔掉網線後,如果服務端傳送了資料包文,在客戶端插回網線之前,服務端重傳次數達到了最大值時,服務端就會斷開 TCP 連線。等到客戶端插回網線後,向服務端傳送了資料,因為服務端已經斷開了與客戶端相同四元組的 TCP 連線,所以就會回 RST 報文,客戶端收到後就會斷開 TCP 連線。至此, 雙方的 TCP 連線都斷開了。

沒有資料傳輸的情況:

  • 如果雙方都沒有開啟 TCP keepalive 機制,那麼在客戶端拔掉網線後,如果客戶端一直不插回網線,那麼客戶端和服務端的 TCP 連線狀態將會一直保持存在。
  • 如果雙方都開啟了 TCP keepalive 機制,那麼在客戶端拔掉網線後,如果客戶端一直不插回網線,TCP keepalive 機制會探測到對方的 TCP 連線沒有存活,於是就會斷開 TCP 連線。而如果在 TCP 探測期間,客戶端插回了網線,那麼雙方原本的 TCP 連線還是能正常存在。

除了客戶端拔掉網線的場景,還有客戶端「當機和殺死程式」的兩種場景。

第一個場景,客戶端當機這件事跟拔掉網線是一樣無法被服務端的感知的,所以如果在沒有資料傳輸,並且沒有開啟 TCP keepalive 機制時,,服務端的 TCP 連線將會一直處於 ESTABLISHED 連線狀態,直到服務端重啟程式。

所以,我們可以得知一個點。在沒有使用 TCP 保活機制,且雙方不傳輸資料的情況下,一方的 TCP 連線處在 ESTABLISHED 狀態時,並不代表另一方的 TCP 連線還一定是正常的。

第二個場景,殺死客戶端的程式後,客戶端的核心就會向服務端傳送 FIN 報文,與客戶端進行四次揮手

所以,即使沒有開啟 TCP keepalive,且雙方也沒有資料互動的情況下,如果其中一方的程式發生了崩潰,這個過程作業系統是可以感知的到的,於是就會傳送 FIN 報文給對方,然後與對方進行 TCP 四次揮手。

完!

相關文章