可用窗口的計算是理解TCP滑動窗口的關鍵,要了解可用窗口的計算,我們需要理解三個指針——SND.UNA、SND.NXT和RCV.NXT。
上面的圖表是從發送方的角度拍攝的快照。我們可以將數據分為4組:
- 已發送并已確認的字節(藍色)
- 已發送但尚未確認的字節(黃色)
- 未發送但接收方準備好接收的字節(綠色)
- 未發送且接收方未準備好接收的字節(灰色)
第3類也稱為可用窗口,因為這是發送方可以使用的窗口。
發送窗口包括黃色和綠色部分。這些字節要么已經被發送,要么可以被發送。
1*OqqxQKu4ZGasXzIlUZ9lyw.png
可用窗口在發送方發送了21-25字節并使用了可用窗口中的所有字節時可能為空。發送窗口保持不變。
1*JdTCgvYpVPRDcLyVb8Rwsg.png
當發送方接收到16-19字節的確認時,發送窗口向右滑動4個字節。隊列中的接下來的字節會有一個更新的可用窗口。
1*9zFu_scvenahSK6m-khSjw.png
一些定義可以幫助我們更好地理解本文后面的復雜情況:
- SND.WND,表示發送窗口
- SND.UNA,表示發送未確認指針,指向發送窗口的第一個字節
- SND.NXT,表示發送下一個指針,指向可用窗口的第一個字節
1*IYBc3_OiPWAZ7JCvIaktkA.png
基于這些定義,我們可以用以下公式表示可用窗口的大小。
接收窗口
1*SbgJAvyVKyXYvPLuoBbGtA.png
接收窗口分為3個類別:
- 已接收并已確認的字節
- 尚未接收但發送方允許發送的字節
- 尚未接收且發送方可能不允許發送的字節
第2類被稱為接收窗口,也可以稱為RCV.WND。
與發送窗口類似,有一個指針RCV.NXT,表示接收窗口的第一個字節。
1*u3KoxvVK-rrM1g4gX6fbZQ.png
接收窗口并非靜態。如果服務器運行得高效,接收窗口可以擴展。否則,它可能會縮小。
接收方通過在TCP段頭中的窗口字段中指示大小來傳達其接收窗口。當發送方收到它時,這個窗口大小就成為了可用窗口。
發送和接收段需要時間。因此,接收窗口在特定時刻不等于可用窗口。
簡化的示例
讓我們模擬一次請求和響應,以更好地理解滑動窗口的工作原理。
有兩個修改簡化了我們的計算。
- 我們忽略了最大段大小(MSS)。MSS根據所選的網絡路由而變化。
- 我們使接收窗口等于可用窗口,而且在整個過程中都保持不變。
1*sAF4A2TyNeItzw2yK0xI9g.png
上面是一個顯示了10個步驟示例的圖表。
客戶端請求一個資源,服務器以三個段響應它:
- 50字節的頭部
- 80字節的正文部分1
- 100字節的正文部分2
每一方都可以同時是發送方和接收方。
我們假設客戶端的發送窗口(SND.WND)為300字節,接收窗口(RCV.WND)為150字節。因此,服務器的SND.WND為150字節,RCV.WND為300字節。
1*cU9TEaEoezDwvw__3G1DFw.png
這是客戶端的起始狀態。
我們假設它之前已經從服務器接收了300字節,因此RCV.NXT指向301。
由于它還沒有發送任何內容,SND.UNA和SND.NXT都指向1。
1*IYBc3_OiPWAZ7JCvIaktkA.png
根據這個公式,客戶端的可用窗口大小是1 + 300 - 1 = 300。
1*kfa6ZdhSR_VJggJ2abUSAQ.png
這是服務器的起始狀態,反映了另一側的狀態。
因為它已經發送了300字節,SND.UNA和SND.NXT都指向301。
由于客戶端還沒有發送任何請求,RCV.NXT指向1。
服務器的可用窗口是301 + 150 - 301 = 150。
現在,第1步開始了。
客戶端發送了第一個100字節的請求。在這一刻,窗口發生了變化。
- 這100字節已發送但尚未確認。因此,SND.NXT向右滑動了100字節。
- 其他指針保持不變。
可用窗口變為1 + 300 - 101 = 200。
1*ug0laVIMWQ3HGG-kPjZ0KA.png
在第2步,我們關注了服務器。
- 當服務器接收到請求時,RCV.NXT向右滑動了100字節。
- 然后發送了帶有ACK的50字節響應。這50字節已發送但尚未確認,所以SND.NXT向右移動了50個字節。
- SND.UNA保持不變。
可用窗口變為301 + 150 - 351 = 100。
1*GAZqLwbVGj2yqUnm5DyI4w.png
移動到客戶端。
- 當客戶端接收到50字節的響應時,RCV.NXT向右滑動了50字節。
- 當它收到前面發送的100字節的ACK時,SND.UNA向右滑動。
- 由于客戶端沒有發送任何數據,SND.NXT保持不變。
可用窗口變為101 + 300 - 101 = 300。
1*50UitYxS9XW3N4XV2Z7tOg.png
再次移動到服務器的一端。
可用窗口是100字節。服務器可以發送80字節的段。
- SND.NXT向右滑動了80個字節。
- 由于前面的50字節尚未確認,SND.UNA保持不變。
- 由于服務器未接收任何數據,RCV.NXT保持不變。
可用窗口變為301 + 150 - 431 = 20。
1*soNJeyvqRj0zqDDrtBL9Fg.png
客戶端接收了文件的第一部分并立即發送了ACK。
- 當客戶端接收到80字節的數據時,RCV.NXT向右滑動。
- 其他指針保持不變。
可用窗口保持在300。
1*QJKwuY3HvslR9601ZRWCxA.png
此時,服務器在發送第2步時接收到ACK時。
- 當服務器發送50字節的響應時,SND.UNA向右滑動了50個字節。
- 其他指針保持不變。
1*thuJ7lCYqreqxsL8nMz_ag.png
在第4步中,服務器發送了文件的第一個80字節部分,并再次收到了ACK確認。
- SND.UNA(已確認序列號)向右移動了80個字節。
- 其他指針保持不變。
可用窗口的計算變為431 + 150 - 431 = 150。
1*8sS5S0OkW0I2Vbp40nkkZQ.png
在第8步,服務器發送了文件的第二部分,共100字節。
- SND.NXT(下一個要發送的序列號)向右移動了100個字節。
- 其他指針保持不變。
可用窗口的計算變為431 + 150 - 531 = 50。
1*qXP9BCX80vPpkplc5utP6Q.png
接下來,輪到客戶端。
- RCV.NXT(下一個要接收的序列號)在客戶端接收100字節后向右移動了100個字節。
- 其他指針保持不變。
可用窗口保持不變。
1*LkQ7tG-_1XQROjOZ3vTC9g.png
最后,服務器接收了前一個響應的ACK。
- SND.UNA向右移動了100個字節。
- 其他指針保持不變。
可用窗口的計算變為531 + 150 - 531 = 150。
當窗口發生變化
在之前,我們假設發送窗口和接收窗口保持不變。但在實際情況中,這個假設是不正確的,因為兩個窗口中的字節存在于操作系統緩沖區中,而緩沖區中的可用空間可以調整。當我們的應用程序無法快速讀取緩沖區中的字節時,可用空間會減小。
讓我們看看窗口發生變化的情況,以及它如何影響可用窗口。
1*qyjkUdkAdsfrkRkqVlPClw.png
為了簡化,本例重點關注客戶端的可用窗口。在這個示例中,客戶端始終是發送方,服務器是接收方。
1*u3KoxvVK-rrM1g4gX6fbZQ.png
當服務器發送ACK時,它還包括了更新后的窗口大小。
1*pkiC_TWGpIZF3aSPOz6lcA.png
一開始,客戶端發送了一個150字節的請求。
- 這150字節已發送但尚未得到確認。
- 可用窗口縮小為150字節。
- 發送窗口保持在300字節。
1*zs4VuHChJJ-7vWFmXyr8Ug.png
當服務器接收請求時,應用程序讀取了前50字節,剩下的100字節仍然在緩沖區中,從接收窗口中占用了100字節的可用空間。因此,接收窗口縮小到了200字節。
接下來,服務器發送了一個帶有更新后的200字節接收窗口的ACK。
1*SZDl6q22CB6kzY3P-CCHFA.png
客戶端接收ACK并將其發送窗口大小更新為200。
此時,可用窗口與發送窗口相同,因為所有150字節都已得到確認。
1*6gKYyaDUdOQSEGfHh6SWdA.png
再次,客戶端發送了另一個200字節的請求,使用了可用窗口中的所有可用空間。
1*uJiRzHmdV4kT8lPW62bz0g.png
在服務器接收了這200字節之后,應用程序仍然運行緩慢,總共只讀取了70字節,將280字節留在緩沖區中。這導致接收窗口再次縮小,現在只剩下20字節。
在ACK消息中,服務器與客戶端分享了更新后的窗口大小。
1*xnUjR-R45hPoGO7qhvCHKg.png
再次,客戶端在收到ACK后將其發送窗口更新為20字節,可用窗口也變為20字節。
在這種情況下,如果沒有更多來自服務器的消息,客戶端將停止發送大于20字節的請求,直到在后續消息中收到另一個窗口更新。
那么,如果沒有更多消息來自服務器,我們會被困在20字節的可用窗口嗎?
不會。為了避免這種情況,客戶端的TCP定期檢測窗口大小。
一旦釋放更多的空間,
可用窗口就會擴大,可以發送更多的數據。
主要內容
- 可用窗口的計算是理解TCP滑動窗口的關鍵。
- 要了解可用窗口的計算,我們需要理解3個指針——SND.UNA、SND.NXT和RCV.NXT。
- 假設窗口大小永遠不會改變可以幫助我們理解整個過程。