天天看點

TCP 才不傻!

大家好,我是小林。

之前收到個讀者的問題,對于 TCP 三次握手和四次揮手的一些疑問:

  • 第一次握手,如果用戶端發送的SYN一直都傳不到被伺服器,那麼用戶端是一直重發SYN到永久嗎?用戶端停止重發SYN的時機是什麼?
  • 第三次握手,如果伺服器永遠不會收到ACK,伺服器就永遠都留在 Syn-Recv 狀态了嗎?退出此狀态的時機是什麼?
  • 第三次揮手,如果用戶端永遠收不到 FIN,ACK,用戶端永遠停留在 Fin-Wait-2狀态了嗎?退出此狀态時機是什麼時候呢?
  • 第四次揮手,如果伺服器永遠收不到 ACK,伺服器永遠停留在 Last-Ack 狀态了嗎?退出此狀态的時機是什麼呢?
  • 如果用戶端 在 2SML内依舊沒收到 FIN,ACK,會關閉連結嗎?伺服器那邊怎麼辦呢,是怎麼關閉連結的呢?

可以看到,這些問題都是關于 TCP 是如何處理這些異常場景的,我們在學 TCP 連接配接建立和斷開的時候,總是以為這些過程能如期完成。

可惜理想很豐滿,現實很骨感,事實預料呀。

TCP 當然不傻,對以上這些異常場景都是有做處理的。

這次就針對讀者問的這一系列問題,來詳細說說 TCP 是怎麼處理這些異常的?

這些異常場景共分為兩大類,第一類是 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,是服務端發起建立 TCP 連接配接的封包;

是以,如果第二次握手丢了,就會發送比較有意思的事情,具體會怎麼樣呢?

因為第二次握手封包裡是包含對用戶端的第一次握手的 ACK 确認封包,是以,如果用戶端遲遲沒有收到第二次握手,那麼用戶端就覺得可能自己的 SYN 封包(第一次握手)丢失了,于是用戶端就會觸發逾時重傳機制,重傳 SYN 封包。

然後,因為第二次握手中包含服務端的 SYN 封包,是以當用戶端收到後,需要給服務端發送 ACK 确認封包(第三次握手),服務端才會認為該 SYN 封包被用戶端收到了。

那麼,如果第二次握手丢失了,服務端就收不到第三次握手,于是服務端這邊會觸發逾時重傳機制,重傳 SYN-ACK 封包。

在 Linux 下,SYN-ACK 封包的最大重傳次數由

tcp_synack_retries

核心參數決定,預設值是 5。

是以,當第二次握手丢失了,用戶端和服務端都會重傳:

  • 用戶端會重傳 SYN 封包,也就是第一次握手,最大重傳次數由

    tcp_syn_retries

    核心參數決定。;
  • 服務端會重傳 SYN-AKC 封包,也就是第二次握手,最大重傳次數由

    tcp_synack_retries

    核心參數決定。

第三次握手丢失了,會發生什麼?

用戶端收到服務端的 SYN-ACK 封包後,就會給服務端回一個 ACK 封包,也就是第三次握手,此時用戶端狀态進入到

ESTABLISH

因為這個第三次握手的 ACK 是對第二次握手的 SYN 的确認封包,是以當第三次握手丢失了,如果服務端那一方遲遲收不到這個确認封包,就會觸發逾時重傳機制,重傳 SYN-ACK 封包,直到收到第三次握手,或者達到最大重傳次數。

注意,ACK 封包是不會有重傳的,當 ACK 丢失了,就由對方重傳對應的封包。

TCP 四次揮手期間的異常

我們再來看看 TCP 四次揮手的過程。

TCP 才不傻!

第一次揮手丢失了,會發生什麼?

當用戶端(主動關閉方)調用 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 聰明着很!

關注公衆号:「小林coding」 ,回複「我要學習」即可免費獲得「伺服器 Linux C/C++ 」成長路程(書籍資料 + 思維導圖)