本章圍繞TCP協議的滑動窗口這一主題展開討論,第一部分主要是對TCP滑動窗口的基礎工作原理進行闡述,第二部分會深入的理解滑動窗口,第三部分會根據一個工作中實際遇到的例子來理解滑動窗口。
背景介紹
TCP協議提供可靠的數據傳輸,因此必須解決網絡傳輸中帶來的丟包和包亂序問題。可想而知,TCP協議棧需要一個接收緩存來處理接收到的數據報文,以保證其包順序和完整性後提供給上層應用系統。TCP協議棧需要清楚網絡實際的數據處理帶寬和數據處理速度,以避免TCP接收緩存佔用過多內存和匹配接收方實際處理能力,因此引入滑動窗口(Sliding Window)已通知發送方“接收方的最大接收能力”。
在閱讀本文之前,希望你有一些TCP基礎知識,瞭解TCP三步握手和TCP其他一些Flag標記的意義,這部分的工作可以閱讀《TCP/IP卷一》。也可以閱讀本博客的相關文章。
滑動窗口基礎
這節主要講述滑動窗口在TCP傳輸過程中如何體現的。
下圖是TCP三次握手的SYN報文:
可以看出滑動窗口的大小是65535字節,這是TCP三步握手中進行必要工作,經過TCP三步握手,雙方能夠感知對方的滑動窗口大小的初始值,用於控制向對方發送最大字節數,避免對方無法處理接收導致的丟包。
上圖還可以看出使用Syn包中使用Window scale選項。Window scale在TCP協議中定義是用於放大滑動窗口數值的途徑。我們知道TCP中滑動窗口大小是有2個字節組成,也就是最大數65535,後來發現最大值不夠用,因此需要Window scale來對其擴充,如果使用Window scale選項後,滑動窗口的實際大小爲window size * 2^window scale。上圖中windowscale爲3,表示8倍。
下圖是TCP傳輸過程中的ACK報文:
前面我們瞭解到TCP三步握手後,傳輸雙方能夠獲取對方的滑動窗口初始值,然而發送方發送一些數據給對方之後,如何再次或者對方的滑動窗口,上圖已經說明是如何傳遞滑動窗口了,TCP通過Ack報文向對方反饋自己的滑動窗口,如果滑動窗口爲0,發送方就應該暫停發送數據,等待對方滑動窗口恢復後在進行發送。可參見《TCP/IP卷一》圖20-3。
滑動窗口深入
下圖描述TCP協議棧有關滑動窗口幾個重要變量。
*LastByteRead指向TCP接收緩存當前讀到的位置。可以理解爲上層應用系統最後一次read到的位置。
*NextByeExpected表示TCP接收緩存最後可讀的位置。可以理解爲最後一個連續、完整的數據報文。
*LastByteRcvd表示收到的最新報文段的位置。前面一段空白部分就是說明前面報文丟掉或者還未達到。
滑動窗口的大小爲:最大接收緩存 - (LastByteRcvd - LastByteRead)。可以簡單這麼理解。
下圖描述發送方滑動窗口的示意圖:
上圖以顏色的方式分爲4個部分,黑色框框就是滑動窗口,其大小由接收方控制。
*第一部分,表示已經發送並且接收方已經回覆Ack的數據。
*第二部分,表示發送但是未收到Ack的數據。
*第三部分,表示還未發送出去的數據,但是接收方還有空間。
*第四部分,表示不能發送,接收方沒有空間。
對比上圖,紅色部分表示新增加的部分表示接收方已經確認接收的數據。因此滑動窗口(黑色框框)的左邊界向右運動,稱爲合攏運動。
第三部分用紅色框框標記的爲新的可用的數據,表示接收方已經釋放相應的接收緩存(可以理解爲上層應用程序read過該數據了),因此產生了新的可以空間。滑動窗口(黑色框框)的右邊界相對上圖也向右移動,稱爲張開運動。可參見《TCP/IP卷一》圖20-5,圖20-6。
假設接收方回覆【31-36】Ack報文段中的滑動窗口值在變小(或者乾脆爲0),說明接收方TCP協議棧已經確認接收這部分數據,但是應用程序還未讀取到該數據,這種情況下,滑動窗口的右邊界就不會向右移動,也就是說不會做張開運動。此處假設是進一步說明滑動窗口的張開運動是依賴於接收方上報的滑動窗口值,而不是依賴Ack確認哪些數據報文已經被接收。
因此這種滑動窗口的合攏和張開運動是受接收方所控制,因爲其依賴於接收方的Ack確認和接收方滑動窗口值的變化。滑動窗口之案例
案例背景
問題分析
上圖是11號包的具體內容,我們看到windowsize對應的二進制數據是0x0010,然後乘以4096(2的12次方),也就是說服務端的滑動窗口大小是16*4096=65536計算得出的。
但是我們看到13號報文中等待5秒鐘才發送16個字節,而且這16個字節並不是完整業務的迴應數據,只是數據開頭的16字節,到此我們可以假設window2003的協議棧應該是沒有正確的解析服務器[syn,ack]端中的windowscale,認爲服務的滑動窗口大小是16字節,因此等待了5秒才發送16字節的報文。通過排查後續所有報文都有這個規律。