你們好,我是小林。服務器
以前收到個讀者的問題,對於 TCP 三次握手和四次揮手的一些疑問:app
第一次握手,若是客戶端發送的SYN一直都傳不到被服務器,那麼客戶端是一直重發SYN到永久嗎?客戶端中止重發SYN的時機是什麼?tcp
第三次握手,若是服務器永遠不會收到ACK,服務器就永遠都留在 Syn-Recv 狀態了嗎?退出此狀態的時機是什麼?函數
第三次揮手,若是客戶端永遠收不到 FIN,ACK,客戶端永遠停留在 Fin-Wait-2狀態了嗎?退出此狀態時機是何時呢?spa
第四次揮手,若是服務器永遠收不到 ACK,服務器永遠停留在 Last-Ack 狀態了嗎?退出此狀態的時機是什麼呢?操作系統
若是客戶端 在 2SML內依舊沒收到 FIN,ACK,會關閉連接嗎?服務器那邊怎麼辦呢,是怎麼關閉連接的呢?code
能夠看到,這些問題都是關於 TCP 是如何處理這些異常場景的,咱們在學 TCP 鏈接創建和斷開的時候,老是覺得這些過程能如期完成。orm
惋惜理想很豐滿,現實很骨感,事實預料呀。blog
TCP 固然不傻,對以上這些異常場景都是有作處理的。進程
此次就針對讀者問的這一系列問題,來詳細說說 TCP 是怎麼處理這些異常的?
這些異常場景共分爲兩大類,第一類是 TCP 三次握手期間的異常,第二類是 TCP 四次揮手期間的異常。
咱們先來看看 TCP 三次握手的過程。
image.png當客戶端想和服務端創建 TCP 鏈接的時候,首先第一個發的就是 SYN 報文,而後進入到 SYN_SENT
狀態。
在這以後,若是客戶端遲遲收不到服務端的 SYN-ACK 報文(第二次握手),就會觸發超時重傳機制。
不一樣版本的操做系統可能超時時間不一樣,有的 1 秒的,也有 3 秒的,這個超時時間是寫死在內核裏的,若是想要更改則須要從新編譯內核,比較麻煩。
當客戶端在 1 秒後沒收到服務端的 SYN-ACK 報文後,客戶端就會重發 SYN 報文,那到底重發幾回呢?
在 Linux 裏,客戶端的 SYN 報文最大重傳次數由 tcp_syn_retries
內核參數控制,這個參數是能夠自定義的,默認值通常是 5。
一般,第一次超時重傳是在 1 秒後,第二次超時重傳是在 2 秒,第三次超時重傳是在 4 秒後,第四次超時重傳是在 8 秒後,第五次是在超時重傳 16 秒後。沒錯,每次超時的時間是上一次的 2 倍。
當第五次超時重傳後,會繼續等待 32 秒,若是服務端仍然沒有迴應 ACK,客戶端就再也不發送 SYN 包,而後斷開 TCP 鏈接。
因此,總耗時是 1+2+4+8+16+32=63 秒,大約 1 分鐘左右。
當服務端收到客戶端的第一次握手後,就會回 SYN-ACK 報文給客戶端,這個就是第二次握手,此時服務端會進入 SYN_RCVD
狀態。
第二次握手的 SYN-ACK
報文其實有兩個目的 :
因此,若是第二次握手丟了,就會發送比較有意思的事情,具體會怎麼樣呢?
由於第二次握手報文裏是包含對客戶端的第一次握手的 ACK 確認報文,因此,若是客戶端遲遲沒有收到第二次握手,那麼客戶端就以爲可能本身的 SYN 報文(第一次握手)丟失了,因而客戶端就會觸發超時重傳機制,重傳 SYN 報文。
而後,由於第二次握手中包含服務端的 SYN 報文,因此當客戶端收到後,須要給服務端發送 ACK 確認報文(第三次握手),服務端纔會認爲該 SYN 報文被客戶端收到了。
那麼,若是第二次握手丟失了,服務端就收不到第三次握手,因而服務端這邊會觸發超時重傳機制,重傳 SYN-ACK 報文。
在 Linux 下,SYN-ACK 報文的最大重傳次數由 tcp_synack_retries
內核參數決定,默認值是 5。
所以,當第二次握手丟失了,客戶端和服務端都會重傳:
tcp_syn_retries
內核參數決定。;tcp_synack_retries
內核參數決定。客戶端收到服務端的 SYN-ACK 報文後,就會給服務端回一個 ACK 報文,也就是第三次握手,此時客戶端狀態進入到 ESTABLISH
狀態。
由於這個第三次握手的 ACK 是對第二次握手的 SYN 的確認報文,因此當第三次握手丟失了,若是服務端那一方遲遲收不到這個確認報文,就會觸發超時重傳機制,重傳 SYN-ACK 報文,直到收到第三次握手,或者達到最大重傳次數。
注意,ACK 報文是不會有重傳的,當 ACK 丟失了,就由對方重傳對應的報文。
咱們再來看看 TCP 四次揮手的過程。
image.png當客戶端(主動關閉方)調用 close 函數後,就會向服務端發送 FIN 報文,試圖與服務端斷開鏈接,此時客戶端的鏈接進入到 FIN_WAIT_1
狀態。
正常狀況下,若是能及時收到服務端(被動關閉方)的 ACK,則會很快變爲 FIN_WAIT2
狀態。
若是第一次揮手丟失了,那麼客戶端遲遲收不到被動方的 ACK 的話,也就會觸發超時重傳機制,重傳 FIN 報文,重發次數由 tcp_orphan_retries
參數控制。
當客戶端重傳 FIN 報文的次數超過 tcp_orphan_retries
後,就再也不發送 FIN 報文,直接進入到 close
狀態。
當服務端收到客戶端的第一次揮手後,就會先回一個 ACK 確認報文,此時服務端的鏈接進入到 CLOSE_WAIT
狀態。
在前面咱們也提了,ACK 報文是不會重傳的,因此若是服務端的第二次揮手丟失了,客戶端就會觸發超時重傳機制,重傳 FIN 報文,直到收到服務端的第二次揮手,或者達到最大的重傳次數。
這裏提一下,當客戶端收到第二次揮手,也就是收到服務端發送的 ACK 報文後,客戶端就會處於 FIN_WAIT2
狀態,在這個狀態須要等服務端發送第三次揮手,也就是服務端的 FIN 報文。
對於 close 函數關閉的鏈接,因爲沒法再發送和接收數據,因此FIN_WAIT2
狀態不能夠持續過久,而 tcp_fin_timeout
控制了這個狀態下鏈接的持續時長,默認值是 60 秒。
這意味着對於調用 close 關閉的鏈接,若是在 60 秒後尚未收到 FIN 報文,客戶端(主動關閉方)的鏈接就會直接關閉。
當服務端(被動關閉方)收到客戶端(主動關閉方)的 FIN 報文後,內核會自動回覆 ACK,同時鏈接處於 CLOSE_WAIT
狀態,顧名思義,它表示等待應用進程調用 close 函數關閉鏈接。
此時,內核是沒有權利替代進程關閉鏈接,必須由進程主動調用 close 函數來觸發服務端發送 FIN 報文。
服務端處於 CLOSE_WAIT 狀態時,調用了 close 函數,內核就會發出 FIN 報文,同時鏈接進入 LAST_ACK 狀態,等待客戶端返回 ACK 來確認鏈接關閉。
若是遲遲收不到這個 ACK,服務端就會重發 FIN 報文,重發次數仍然由 tcp_orphan_retrie
s 參數控制,這與客戶端重發 FIN 報文的重傳次數控制方式是同樣的。
當客戶端收到服務端的第三次揮手的 FIN 報文後,就會回 ACK 報文,也就是第四次揮手,此時客戶端鏈接進入 TIME_WAIT
狀態。
在 Linux 系統,TIME_WAIT 狀態會持續 60 秒後纔會進入關閉狀態。
而後,服務端(被動關閉方)沒有收到 ACK 報文前,仍是處於 LAST_ACK 狀態。
若是第四次揮手的 ACK 報文沒有到達服務端,服務端就會重發 FIN 報文,重發次數仍然由前面介紹過的 tcp_orphan_retries
參數控制。
是吧,TCP 聰明着很!