1. 程式人生 > >傳輸層學習之五(TCP的SACK,F-RTO)

傳輸層學習之五(TCP的SACK,F-RTO)

一、SACK選項

預設情況下TCP採取的是累積確認機制,這時如果發生了報文亂序到達,接收方只會重複確認最後一個按序到達的報文段,為此傳送方的處理只能是重複按序到達接收方的報文段之後的那個報文段,因而它無法準確知道哪些報文段到達了,哪些沒有到達。
考慮以下情景,傳送方的視窗狀態如下:
  如上圖所示,主機A通過TCP傳送10個長度都為100位元組的報文段給主機B,其序號分別為0,100,200,300,400,500,600,00,800,900(圖中以數字0-9代替)。主機B收到了序號為0和序號為100的報文段,但是序號為200,300,400的丟失了。之後主機B又收到了報文段500,600,700,800,900。當收到後續這些分組時,主機B只能對報文段100繼續進行確認,即傳送確認號為200的ACK。主機A收到這樣的確認時,能重傳的唯一報文段就是報文段200,在報文段200的確認被收到之前傳送視窗是無法移動的,這時狀況就會很糟糕,傳送方每重傳一個丟失的報文段,接收方就確認一個(由於是累積確認的,所以情況可能更糟,接收方每次都在等待一段時間後才傳送確認),然後傳送視窗向前移動一個,這個過程一直持續到報文段200,300,400都被接收方到。顯然這種工作方式效率很低,降低了吞吐量。
SACK是TCP選項,它使得接收方能告訴傳送方哪些報文段丟失,哪些報文段重傳了,哪些報文段已經提前收到等資訊。根據這些資訊TCP就可以只重傳哪些真正丟失的報文段。
需要注意的是隻有收到失序的分組時才會可能會發送SACK,TCP的ACK還是建立在累積確認的基礎上的。也就是說如果收到的報文段與期望收到的報文段的序號相同就會發送累積的ACK,SACK只是針對失序到達的報文段的。
SACK包括了兩個TCP選項,一個選項用於標識是否支援SACK,是在TCP連線建立時時傳送;另一種選項則包含了具體的SACK資訊。

1.SACK允許選項

該選項格式如下所示:

它的工作機制類似於視窗擴大選項和事件戳選項,只能應用於SYN報文段,在連線建立階段,主動發起連線的一方在它的SYN中指定選項。只有在它從另一方的SYN中收到了這個選項之後,FAK機制才會被使能。

2.SACK選項

SACK選項的格式如下圖所示:
該選項長度可變,但由於整個TCP選項長度不超過40位元組,所以實際最多不超過4組邊界值。
該選項引數告訴傳送哪些報文段是已經接收到並快取的不連續的報文段,傳送方可根據此資訊檢查究竟是哪個塊丟失,從而傳送相應的報文段。
  1. LeftEdgeofBlock:不連續塊的第一個報文段的序列號
  2. RightEdgeofBlock:不連續塊的最後一個報文段的序列號之後的序列號。

3.SACK的產生

SACK由接收方產生並通告給傳送方,其產生有以下幾種情形: 

3.1中間有丟包或延遲時的SACK

如果接收方接收到的報文段序號大於所期待的序號,說明有報文段被丟棄或者出現了大的延遲,此時可以傳送SACK通知傳送方。
為反映接收方的接收快取和網路傳輸情況,SACK中的第一個塊必須描述是那個報文段觸發了該SACK的傳送,接收方應該儘可能地在SACK選項部分中填寫儘可能多的塊資訊,讓傳送方能瞭解當前網路傳輸情況的最新資訊。

3.2對重複傳送包的SACK(D-SACK)

RFC2883中對SACK進行了擴充套件。SACK中的資訊描述的是收到的報文段,這些報文段可能是正常接收的,也可能是重複接收的,通過對SACK進行擴充套件,D-SACK可以在SACK選項中描述它重複收到的報文段。但是需要注意的是D-SACK只用於報告接收端收到的最後一個報文與已經接收了的報文的重複部分,比如(來自RFC):

 Transmitted       Received                              ACK Sent

 Segment            Segment                               (Including SACK Blocks)
 500-999             500-999                               1000
 1000-1499         (data packet dropped)
 1500-1999         (delayed)
 2000-2499         (data packet dropped)
 2500-2999         (delayed)
 3000-3499         (data packet dropped)
 3500-3999         3500-3999                           1000, SACK=3500-4000
 1000-1499         (data packet dropped)
 1500-2999         1500-1999                           1000, SACK=1500-2000, 3500-4000
                              2000-2499                           1000, SACK=2000-2500, 1500-2000, 3500-4000
                              1500-2999                           1000, SACK=1500-2000, 1500-3000, 3500-4000

可以看出最後一個SACK的第一個塊是一個D-SACK,它包含的範圍是1500-2000,而不是1500-2500。

在D-SACK使用了SACK同樣的工作過程,它只是SACK的一個協議擴充套件。當使用D-SACK時,選項中的第一個欄位的含義是“重複收到的報文段的序號”。其規則:

  • length後的第一個block(也就是第一個4位元組)將包含重複收到的報文段的序號。
  • 如果D-SACK報告的報文段的序號在累積確認的報文段之後(並且不是期望收到的報文段,因為這是SACK工作的基礎,也是D-SACK的基礎),則在這個SACK選項中跟在D-SACK之後的第一個非D-SACK塊將按照SACK的格式描述該報文段(被D-SACK確認的報文段,但是它所報告的可能比D-SACK報告的大,比如例子中的最後一個S-ACK)
  • 跟在D-SACK之後的SACK將按照SACK的方式工作。
  • 如果有多個被重複接收的報文段,則D-SACK只包含其中第一個。
如果收到了D-SACK,就表明接收方接收到了重複的報文段,這可能意味著網路中出現了複製或者是傳送方過早重傳了,或者是ACK丟失導致了不必要的重傳。

4.傳送方對SACK的響應

TCP的傳送方(實際上也就是連線的雙方,因為TCP是全雙工的)需要維護一個未被確認的重傳報文段佇列,報文段未被確認前是不能釋放的。重傳送佇列中的每個報文段都有一個標誌位“SACKed”標識該報文段是否被SACK過,對於已經被SACK過的塊,在重傳時將被跳過。
傳送方根據接收到的SACK的資訊設定重傳佇列中的報文段的sacked標記。
當支援D-SACK時,傳送方通過如下方式來判斷是一個SACK還是一個D-SACK:
  • 如果SACK中的第一個block指定的序列號小於被累積確認的最後一個序列號,則是一個D-SACK
  • 如果SACK中的第一個block指定的序列號落在了其後的block描述的某個範圍之內(因為SACK的格式是用兩個值描述一個報文段的序號範圍),則就是一個D-SACK。

二、F-RTO(ForwardRTORecovery)

根據TCP採取的擁塞控制機制,如果出現了超時,則就會進入擁塞避免演算法,而且對於超時的情形會執行“慢啟動”,這會極大的降低TCP的吞吐量。
但是由於TCP是建立在IP之上的,IP是無連線的,不同的IP資料報可能走不同的路徑,因而屬於一個TCP連線的不同的報文段可能走的是不同的路徑,這意味著它們可能亂序到達。因此超時可能是因為報文段在IP中走了一條低速路徑,並不是真的出現了丟包。考慮以下情景,兩個主機A和B之間進行TCP通訊,A要傳送TCP報文段a,b,c,d,e到B:
  1. A傳送報文段a,這時候IP網路選擇的路徑是path1,它是一個“正常”的路徑
  2. IP網路路由發生變化,A和B之間的路徑變成了path2,它是一個“低速”路徑
  3. A傳送報文段b,這時候IP網路使用新的“低速”路徑path2
  4. IP網路路由發生變化,A和B之間的路徑又變回了path1,它是一個“正常”路徑
  5. A傳送報文段c,這時候IP網路選擇的路徑是path1
  6. B收到報文段a,並對它進行確認
  7. A傳送報文段d,並使用路徑path1
  8. A傳送報文段e,並使用路徑path1
  9. B收到報文段c,報文亂序,不進行確認
  10. A發現報文段b超時,並開始執行擁塞演算法
  11. B收到報文段b,並對報文段b和c進行確認
  12. B收到報文段d,並對它進行確認
  13. B收到報文段e,並對它進行確認
這個簡單的場景中,很明顯報文不是丟棄了,只是因為IP網路的路由變動出現了延時抖動,從而出現了一個虛假的超時。在實際網路中,延時抖動是不可避免的,因而應該要避免這種抖動導致TCP執行擁塞避免演算法。TCP採用了F-RTO來解決該問題。
F-RTO的基本思想是判斷RTO是否正常,從而決定是否執行擁塞避免演算法。方法是觀察RTO之後的兩個ACK。如果ACK不是冗餘ACK,並且確認的包不是重傳的,會認為RTO是虛假的就不執行擁塞避免演算法。