TCP 之滑動窗口原理

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

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

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

發送端窗口

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

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

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

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

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

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

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

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

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

接收端窗口

接收窗口有三種:

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

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

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

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

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

示例(大小不變)

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

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

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

我們假設客戶端的發送窗口 (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 字節請求。

此刻,窗戶發生了變化。

可用窗口更改爲 1 + 300 - 101 = 200。

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

可用窗口大小變爲 301 + 150 - 351 = 100。

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

可用窗口更改爲 101 + 300 - 101 = 300。

再次移動到服務器端。

可用窗口爲 100 字節。服務器可以發送 80 字節的段。

可用窗口更改爲 301 + 150 - 431 = 20。

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

可用窗口大小仍爲 300。

此時,服務器在發送 50 字節的回覆時收到了第 2 步的 ACK。

可用窗口大小變爲 351 + 150 - 431 = 70。

當服務器發送數據 1 即 80 字節部分時,再次收到第 4 步的另一個 ACK。

可用窗口大小變爲 431 + 150 - 431 = 150。

在第 8 步,服務器數據 2,大小爲 100 字節。

可用窗口大小變爲 431 + 150 - 531 = 50。

繼續轉到客戶端。

可用窗口大小保持不變。

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

可用窗口大小變爲 531 + 150 - 531 = 150。

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

示例(大小變化的窗口)

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

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

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

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

當服務器發送 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。

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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Ac8ttsDWddqMfI8U2cT7fw