TCP 中的兩個細節點

程式設計師cxuan發表於2021-05-25

文章來源 cxuan 的自己公眾號:TCP 的兩個細節點

公眾號很多硬核文章,求大家關注下呀~ 下面開始我們本篇文章。

TCP 超時和重傳

沒有永遠不出錯誤的通訊,這句話表明著不管外部條件多麼完備,永遠都會有出錯的可能。所以,在 TCP 的正常通訊過程中,也會出現錯誤,這種錯誤可能是由於資料包丟失引起的,也可能是由於資料包重複引起的,甚至可能是由於資料包失序 引起的。

TCP 的通訊過程中,會由 TCP 的接收端返回一系列的確認資訊來判斷是否出現錯誤,一旦出現丟包等情況,TCP 就會啟動重傳操作,重傳尚未確認的資料。

TCP 的重傳有兩種方式,一種是基於時間,一種是基於確認資訊,一般通過確認資訊要比通過時間更加高效。

所以從這點就可以看出,TCP 的確認和重傳,都是基於資料包是否被確認為前提的。

TCP 在傳送資料時會設定一個定時器,如果在定時器指定的時間內未收到確認資訊,那麼就會觸發相應的超時或者基於計時器的重傳操作,計時器超時通常被稱為重傳超時(RTO)

但是有另外一種不會引起延遲的方式,這就是快速重傳

TCP 在每次重傳一次報文後,其重傳時間都會加倍,這種"間隔時間加倍"被稱為二進位制指數補償(binary exponential backoff) 。等到間隔時間加倍到 15.5 min 後,客戶端會顯示

Connection closed by foreign host.

TCP 擁有兩個閾值來決定如何重傳一個報文段,這兩個閾值被定義在 RFC[RCF1122] 中,第一個閾值是 R1,它表示願意嘗試重傳的次數,閾值 R2 表示 TCP 應該放棄連線的時間。R1 和 R2 應至少設為三次重傳和 100 秒放棄 TCP 連線。

這裡需要注意下,對連線建立報文 SYN 來說,它的 R2 至少應該設定為 3 分鐘,但是在不同的系統中,R1 和 R2 值的設定方式也不同。

在 Linux 系統中,R1 和 R2 的值可以通過應用程式來設定,或者是修改 net.ipv4.tcp_retries1 和 net.ipv4.tcp_retries2 的值來設定。變數值就是重傳次數。

tcp_retries2 的預設值是 15,這個充實次數的耗時大約是 13 - 30 分鐘,這只是一個大概值,最終耗時時間還要取決於 RTO ,也就是重傳超時時間。tcp_retries1 的預設值是 3 。

對於 SYN 段來說,net.ipv4.tcp_syn_retries 和 net.ipv4.tcp_synack_retries 這兩個值限制了 SYN 的重傳次數,預設是 5,大約是 180 秒。

Windows 作業系統下也有 R1 和 R2 變數,它們的值被定義在下方的登錄檔中

HKLM\System\CurrentControlSet\Services\Tcpip\Parameters
HKLM\System\CurrentControlSet\Services\Tcpip6\Parameters

其中有一個非常重要的變數就是 TcpMaxDataRetransmissions,這個 TcpMaxDataRetransmissions 對應 Linux 中的 tcp_retries2 變數,預設值是 5。這個值的意思表示的是 TCP 在現有連線上未確認資料段的次數。

快速重傳

我們上面提到了快速重傳,實際上快速重傳機制是基於接收端的反饋資訊來觸發的,它並不受重傳計時器的影響。所以與超時重傳相比,快速重傳能夠有效的修復丟包情況。當 TCP 連線的過程中接收端出現亂序的報文(比如 2 - 4 - 3)到達時,TCP 需要立刻生成確認訊息,這種確認訊息也被稱為重複 ACK

當失序報文到達時,重複 ACK 要做到立刻返回,不允許延遲傳送,此舉的目的是要告訴傳送方某段報文失序到達了,希望傳送方指出失序報文段的序列號。

還有一種情況也會導致重複 ACK 發給傳送方,那就是當前報文段的後續報文傳送至接收端,由此可以判斷當前傳送方的報文段丟失或者延遲到達。因為這兩種情況導致的後果都是接收方沒有收到報文,但是我們卻無法判斷到底是報文段丟失還是報文段沒有送達。因此 TCP 傳送端會等待一定數目的重複 ACK 被接受來決定資料是否丟失並觸發快速重傳。一般這個判斷的數量是 3,這段文字表述可能無法清晰理解,我們舉個例子。

如上圖所示,報文段 1 成功接收並被確認為 ACK 2,接收端的期待序號為 2,當報文段 2 丟失後,報文段 3。失序到達,但是與接收端的期望不匹配,所以接收端會重複傳送冗餘 ACK 2。

這樣,在超時重傳定時器到期之前,接收收到連續三個相同的 ACK 後,傳送端就知道哪個報文段丟失了,於是傳送方會重發這個丟失的報文段,這樣就不用等待重傳定時器的到期,大大提高了效率。

SACK

在標準的 TCP 確認機制中,如果傳送方傳送了 0 - 10000 序號之間的資料,但是接收方只接收到了 0 -1000, 3000 - 10000 之間的資料,而 1000 - 3000 之間的資料沒有到達接收端,此時傳送方會重傳 1000 - 10000 之間的資料,實際上這是沒有必要的,因為 3000 後面的資料已經被接收了。但是傳送方無法感知這種情況的存在。

如何避免或者說解決這種問題呢?

為了優化這種情況,我們有必要讓客戶端知道更多的訊息,在 TCP 報文段中,有一個 SACK 選項欄位,這個欄位是一種選擇性確認(selective acknowledgment)機制,這個機制能告訴 TCP 客戶端,用我們的俗語來解釋就是:“我這裡最多允許接收 1000 之後的報文段,但是我卻收到了 3000 - 10000 的報文段,請給我 1000 - 3000 之間的報文段”。

但是,這個選擇性確認機制的是否開啟還受一個欄位的影響,這個欄位就是 SACK 允許選項欄位,通訊雙方在 SYN 段或者 SYN + ACK 段中新增 SACK 允許選項欄位來通知對端主機是否支援 SACK,如果雙方都支援的話,後續在 SYN 段中就可以使用 SACK 選項了。

這裡需要注意下:SACK 選項欄位只能出現在 SYN 段中。

偽超時和重傳

在某些情況下,即使沒有出現報文段的丟失也可能會引發報文重傳。這種重傳行為被稱為 偽重傳(spurious retransmission) ,這種重傳是沒有必要的,造成這種情況的因素可能是由於偽超時(spurious timeout),偽超時的意思就是過早的判定超時發生。造成偽超時的因素有很多,比如報文段失序到達,報文段重複,ACK 丟失等情況。

檢測和處理偽超時的方法有很多,這些方法統稱為檢測演算法和響應演算法。檢測演算法用於判斷是否出現了超時現象或出現了計時器的重傳現象。一旦出現了超時或者重傳的情況,就會執行響應演算法撤銷或者減輕超時帶來的影響,下面是幾種演算法,此篇文章暫不深入這些實現細節

  • 重複 SACK 擴充套件- DSACK
  • Eifel 檢測演算法
  • 前移 RTO 恢復 - F-RTO
  • Eifel 響應演算法

包失序和包重複

上面我們討論的都是 TCP 如何處理丟包的問題,我們下面來討論一下包失序和包重複的問題。

包失序

資料包的失序到達是網際網路中極其容易出現的一種情況,由於 IP 層並不能保證資料包的有序性,每個資料包的傳送都可能會選擇當前情況傳輸速度最快的鏈路,所以很有可能出現傳送了 A - > B -> C 的三個資料包,到達接收端的資料包順序是 C -> A -> B 或者 B -> C -> A 等等。這就是包失序的一種現象。

在包傳輸中,主要分為兩種鏈路:正向鏈路(SYN)和反向鏈路(ACK)

如果失序發生在正向鏈路,TCP 是無法正確判斷資料包是否丟失的,資料的丟失和失序都會導致接收端收到無序的資料包,造成資料之間的空缺。如果這種空缺不夠大的話,這種情況影響不大;但是如果空缺比較大的話,可能會導致偽重傳。

如果失序發生在反向鏈路,就會使 TCP 的視窗前移,然後收到重複而應該被丟棄的 ACK,導致傳送端出現不必要的流量突發,影響可用網路頻寬。

回到我們上面討論的快速重傳,由於快速重傳是根據重複 ACK 推斷出現丟包而啟動的,它不用等到重傳計時器超時。由於 TCP 接收端會對接收到的失序報文立刻返回 ACK,所以網路中任何一個失序到達的報文都可能會造成重複 ACK。假設一旦收到 ACK,就會啟動快速重傳機制,當 ACK 數量激增,就會導致大量不必要的重傳發生,所以快速重傳應該達到重複閾值(dupthresh) 再觸發。但是在網際網路中,嚴重的失序並不常見,因此 dupthresh 的值可以設定的儘量小,一般來說 3 就能處理絕大部分情況。

包重複

包重複也是網際網路中出現很少的一種情況,它指的是在網路傳輸過程中,包可能會出現傳輸多次的情況,當重傳生成時,TCP 可能會出現混淆。

包的重複可以使接收端生成一系列的重複 ACK,這種情況可以使用 SACK 協商來解決。

我自己肝了六本 PDF,全網傳播超過10w+ ,微信搜尋「程式設計師cxuan」關注公眾號後,在後臺回覆 cxuan ,領取全部 PDF,這些 PDF 如下

六本 PDF 連結

相關文章