計算機網絡協議第十章,TCP協議之滑動窗口

       本章圍繞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-36Ack報文段中的滑動窗口值在變小(或者乾脆爲0),說明接收方TCP協議棧已經確認接收這部分數據,但是應用程序還未讀取到該數據,這種情況下,滑動窗口的右邊界就不會向右移動,也就是說不會做張開運動。此處假設是進一步說明滑動窗口的張開運動是依賴於接收方上報的滑動窗口值,而不是依賴Ack確認哪些數據報文已經被接收。

因此這種滑動窗口的合攏和張開運動是受接收方所控制,因爲其依賴於接收方的Ack確認和接收方滑動窗口值的變化。


滑動窗口之案例

案例背景

有一個信令服務Server,有一個客戶端Client。client分別運行在兩種設備上,並且分別爲window xp和window 2003的操作系統。
現象是其中xp運行client能夠正常工作,2003運行的client出現一會連接一會斷開的現象,而且反覆如此無法正常工作。

問題分析

Server和window 2003的client 三步握手和註冊信令都完成,但是後續的信令卻遲遲無法得到回覆,因此server端超時過後會主動斷開連接,這就是解釋了爲什麼反覆斷開連接的原因。
但是爲什麼相同的程序在window 2003上運行卻數據遲遲無法回覆呢?

下面抓取window 2003 client的三步握手報文的SYN,ACK分節,是server端回覆的報文。

可以看出window scale=12, 滑動窗口的實際值應該是需要乘以2的12次方,就是乘以4096




上圖是11號包的具體內容,我們看到windowsize對應的二進制數據是0x0010,然後乘以4096(2的12次方),也就是說服務端的滑動窗口大小是16*4096=65536計算得出的。

 但是我們看到13號報文中等待5秒鐘才發送16個字節,而且這16個字節並不是完整業務的迴應數據,只是數據開頭的16字節,到此我們可以假設window2003的協議棧應該是沒有正確的解析服務器[syn,ack]端中的windowscale,認爲服務的滑動窗口大小是16字節,因此等待了5秒才發送16字節的報文。通過排查後續所有報文都有這個規律。



問題總結

該問題是由於window 2003的網絡內核對TCP windowscale選項沒有處理導致的,認爲服務端滑動窗口的只有16個字節,而不是16*4096個字節,因此纔等待了5秒之後才發送16個字節的報文,因此導致了服務端對數據報文遲遲沒有響應。

linux中通過/proc/sys/net/ipv4/tcp_window_scaling 參數可以取消window scaling的機制,經過測試發送通信正常,也再次印證了上面的結論。

在socket編程中是無法設置window scaliing的,這個參數又是如何控制的呢?
引用TCP/IP卷一中的20.4小節原文:“插口API允許進程設置發送和接受緩存的大小,接受緩存的大小是該連接上所能夠通告的最大窗口大小”。通過實驗我們如果在connect調用之前設置RCV_BUFF選項,在調用connect會影響Syn段的滑動窗的大小,如果RCV_BUFF值大於65535肯定會使用windowscale選項。
其實也很好理解connect調用會觸發SYN分節,因此之前設置纔會影響滑動窗口的初始值,而滑動窗口只有16個bit,因此如果大於65535肯定需要使用window scale選項才能使滑動窗口大於65535,這也是windowscale選項被設計出來的目的。

小結

本章討論了滑動窗口設計的目的,傳遞滑動窗口的方式,滑動窗口計算方式,發送方滑動窗口運動方式幾個部分。通過以上幾個部分的描述,希望能夠讓入門者有所收穫。通過一個實際工作中遇到的問題,希望能夠幫助對TCP滑動窗口的理解。


參考

《TCP/IP詳解-協議》卷一     W.Richard Stevens

修訂

初稿                                       2015-3-28               Simon



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章