大家好,我是小林。
今天,聊一個有趣的問題:拔掉網線幾秒,再插回去,原本的 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 四次揮手。
完!
關注公衆号:「小林coding」 ,回複「我要學習」即可免費獲得「伺服器 Linux C/C++ 」成長路程(書籍資料 + 思維導圖)