徹底搞通TCP滑動窗口

在我們當初學習網路編程的時候,都接觸過TCP,在TCP中,對於數據傳輸有各種策略,比如滑動窗口、擁塞窗口機制,又比如慢啟動、快速恢復、擁塞避免等。通過本文,我們將了解滑動窗口在TCP中是如何使用的。

滑動窗口實現了TCP流控制。首先明確滑動窗口的範疇:

  • TCP是雙工的協議,會話的雙方都可以同時接收和發送數據。
  • 會話的雙方都各自維護一個發送窗口和一個接收窗口。各自的接收窗口大小取決於應用、系統、硬體的限制(TCP傳輸速率不能大於應用的數據處理速率)。各自的發送窗口則要求取決於對端通告的接收窗口,要求相同。

滑動窗口解決的是流量控制的的問題,就是如果接收端和發送端對數據包的處理速度不同,如何讓雙方達成一致。接收端的快取傳輸數據給應用層,但這個過程不一定是即時的,如果發送速度太快,會出現接收端數據overflow,流量控制解決的是這個問題。

發送端窗口

上圖是發送端滑動窗口的簡圖。 我們可以將數據分為4個部分:

  • 發送和已確認的位元組(藍色部分)
  • 已發送但尚未確認的位元組(黃色部分)
  • 未發送的位元組和接收方準備接收的位元組,即在緩衝區buffer中(綠色部分)
  • 未發送且接收方未準備接收的位元組(灰色部分)

其中第三部分,也就是綠色部分,也稱為可用窗口,因為這是發送方可以使用的窗口。

發送窗口由黃色和綠色部分組成。 這些位元組要麼已經發送,要麼可以發送。

當發送方發送21-25位元組並使用可用窗口中的所有位元組時,可用窗口可能為空,發送窗口保持不變(如下圖)。

當發送方收到第16-19位元組的 ACK 時,發送窗口向右滑動 4 個位元組。 更新的可用窗口可用於隊列中的以下位元組(如下圖)。

為了便於理解,我們後續將窗口名使用簡稱,即:

  • SND.WND,代表發送窗口
  • SND.UNA, 代表Send Unacknowledged指針,指向發送窗口的第一個位元組
  • SND.NXT, 代表Send Next指針,指向可用窗口的第一個位元組

使用簡寫後,如下圖所示:

基於這些定義,我們可以用公式表示可用的窗口大小。

可用窗口(可用窗口)大小 = SND.UNA + SND.WND - SND.NXT

接收端窗口

接收窗口有三種:

  • 1、接收並且已經向發送端發送確認ACK
  • 2、尚未接收但允發送端發送數據
  • 3、尚未接收且不允許發送端發送數據

第二種稱為接收窗口,也稱為RCV.WND。 類似於發送窗口,指針RCV.NXT,代表Receive Next指針,指向接收窗口的第一個位元組。

接收窗口不是靜態的。如果服務端性能高,讀取數據快,接收窗口可能會擴大。 否則,它可能會縮小。

接收方通過在TCP段報頭中的窗口欄位中指示大小來傳達其接收窗口。 當發送方收到它時,這個窗口大小就成為可用窗口。

發送和接收數據需要時間。 因此,接收窗口不等於特定時刻的可用窗口。

下面,為了更好的理解滑動窗口在TCP中的使用,我們將使用一個簡單的例子進行模擬說明。

示例(大小不變)

我們模擬一個請求和響應,以更好地理解滑動窗口的工作原理。 為了模擬起來簡單,我們儘可能的簡化裡面的過程,比如:

  • 我們忽略最大段大小 (MSS)。 MSS 因選擇的網路路由而不同。
  • 使接收窗口等於可用窗口,並且在此過程中兩者保持不變。

上圖示例中,有10個步驟。 客戶端請求資源,伺服器分三段響應:

  • 1、一個 50 位元組的包頭
  • 2、一個 80 位元組的數據1
  • 3、一個 100 位元組的數據2

每一方都可以同時是發送方和接收方。

我們假設客戶端的發送窗口 (SND.WND) 是 300 位元組,接收窗口 (RCV.WND) 是 150 位元組。 因此,伺服器的 SND.WND 為 150 位元組,RCV.WND 為 300 位元組。

上圖客戶端的起始狀態。

我們假設它之前已經從伺服器接收了300個位元組,所以RCV.NXT指向301。由於它還沒有發送任何東西,SND.UNA和SND.NXT都指向1。

可用窗口(可用窗口)大小 = SND.UNA + SND.WND - SND.NXT

根據這個公式,客戶端的可用窗口大小為 1 + 300 – 1 = 300。

這是服務端的起始狀態,鏡像另一端即客戶端的狀態。

因為它已經發送了300個位元組,所以SND.UNA和SND.NXT都指向301。

RCV.NXT指向1,因為客戶端尚未發送任何請求。 伺服器的可用窗口是301 + 150 – 301 = 150。

現在,我們從步驟1開始:

客戶端發送它的第一個100位元組請求。

此刻,窗戶發生了變化。

  • 這 100 個位元組已發送,但尚未收到 ACK。 因此,SND.NXT 向右滑動 100 個位元組。
  • 其他指針保持不變。

可用窗口更改為 1 + 300 – 101 = 200。

在第 2 步,我們的焦點轉移到伺服器上,從服務端的角度來分析。

  • 當伺服器收到請求時,RCV.NXT 向右滑動 100 個位元組。
  • 然後它發送一個帶有 ACK 的 50 位元組回復。 這 50 個位元組已發送,但尚未發送 ACK,因此 SND.NXT 向右移動 50 個位元組。
  • SND.UNA不動。

可用窗口大小變為301 + 150 – 351 = 100。

讓我們現在繼續轉向客戶端。

  • 當收到50位元組的回復時,RCV.NXT向右移動50位元組。
  • SND.UNA 在收到前一個發送的 100 個位元組的 ACK 時向右滑動。
  • SND.NXT保持不變,因為客戶端不發送任何數據。

可用窗口更改為101 + 300 – 101 = 300。

再次移動到伺服器端。

可用窗口為 100 位元組。伺服器可以發送 80 位元組的段。

  • SND.NXT 向右滑動 80 個位元組。
  • SND.UNA 保持不變,因為最後 50 位元組尚未得到確認。
  • RCV.NXT 保持不變,因為伺服器沒有收到任何數據。

可用窗口更改為 301 + 150 – 431 = 20。

客戶端收到文件的第一部分並立即發送ACK。

  • 當客戶端接收到 80 位元組的數據時,RCV.NXT 向右移動。
  • 其他部分不變。

可用窗口大小仍為300。

此時,伺服器在發送 50 位元組的回復時收到了第 2 步的 ACK。

  • SND.UNA 向右移動 50 個位元組。
  • 其他部分保持不變。

可用窗口大小變為351 + 150 – 431 = 70。

當伺服器發送數據1即80位元組部分時,再次收到第4步的另一個ACK。

  • SND.UNA 向右移動 80 個位元組。
  • 其他部分保持不變。

可用窗口大小變為431 + 150 – 431 = 150。

在第 8 步,伺服器數據2,大小為100位元組。

  • SND.NXT向右移動 100 個位元組。
  • 其他部分保持不變。

可用窗口大小變為431 + 150 – 531 = 50。

繼續轉到客戶端。

  • 當客戶端收到 100 位元組時,RCV.NXT 向右移動 100 位元組。
  • 其他部分保持不變。

可用窗口大小保持不變。

最後,伺服器收到前一個響應的 ACK。

  • SND.UNA向右移動100個位元組。
  • 其他部分保持不變。

可用窗口大小變為531 + 150 – 531 = 150。

至此,對於滑動窗口不變的示例,講解完畢,那麼對於滑動窗口大小變化的呢?在TCP中又是如果實現的呢?

示例(大小變化的窗口)

在前面的示例中,我們假設發送窗口和接收窗口保持不變。 這個假設本身在實際中就是不成立的,因為不存在。

兩個窗口中的位元組都存在於作業系統緩衝區中,可以對其進行調整。 例如,當我們的應用程式沒有足夠快地從中讀取位元組時,緩衝區中的可用空間就會縮小。

我們來介紹一下這種情況下的窗口變化,看看它是如何影響可用窗口的。

我們簡化了這種情況以將可用窗口集中在客戶端上。 在這個例子中,客戶端始終是發送方,而伺服器是接收方。

當伺服器發送 ACK 時,它也會在其中包含更新後的窗口大小。

一開始,客戶端發送第一個150位元組的請求。

  • 這 150 個位元組已發送,但尚未發送 ACK。
  • 可用窗口縮小到 150 位元組。

發送窗口保持在300位元組。

當伺服器收到請求時,應用程式讀取前 50 個位元組,還有 100 個位元組仍在緩衝區中,從接收窗口中佔用 100 個位元組的可用空間。 因此,接收窗口縮小到 200 位元組。

接下來,伺服器發送帶有更新的 200 位元組接收窗口的 ACK。

客戶端收到 ACK 並將其發送窗口大小更新為 200。

此時,可用窗口與發送窗口相同,因為所有 150 個位元組都被確認。

客戶端再次發送另一個 200 位元組的請求,使用可用窗口中的所有可用空間。

伺服器接收到 200 位元組後,應用程式仍然運行緩慢,總共只讀取了 70 位元組,並在緩衝區中留下了 280 位元組。

這會導致接收窗口再次縮小。 現在,我們只剩下 20 個位元組了。

在 ACK 消息中,伺服器與客戶端共享更新的窗口大小。

同樣,客戶端在收到 ACK 後將其發送窗口更新為 20 位元組。 可用窗口也變為 20 位元組。

在這種情況下,客戶端停止發送任何大於 20 位元組的請求,直到它收到以下消息中的另一個窗口更新。

如果沒有更多來自伺服器的消息,我們會被困在 20 位元組的可用窗口嗎?

我們不會。 為了避免這種情況,客戶端的 TCP 會定期檢測窗口大小。 一旦釋放更多空間,可用窗口就會擴大,並且可以發送更多數據。

結語

可用窗口的計算是理解TCP滑動窗口的關鍵。

要學習可用窗口的計算,我們需要了解 3 個指針——SND.UNA、SND.NXT 和 RCV.NXT。

假設一個永不改變的窗口大小可以幫助我們了解進度。

Tags: