TCP/IP学习笔记(三)TCP流量控制以及滑动窗口
眾所周知,TCP是有緩沖區的,比如接收緩沖區用于存放已經到達但是還沒有被應用程序及時處理的數據。但是任何緩沖區都是有一定大小的,如果發送方發送數據過快,而接收方處理數據過慢,就會導致接收方的接收緩沖區數據量不斷累積最終塞滿緩沖區。隨后如果再有數據到達就只有一個結果,數據被丟掉
為了解決這一問題,TCP引入了流量控制功能,所謂流量控制,就是讓發送方發送速率不要太快,要讓接收方來得及處理。通過滑動窗口機制,可以很容易實現流量控制
序列號
在介紹流量控制之前,首先需要明確一個概念,就是數據序列號。它的作用是標識數據,數據的每一個字節都有與之對應的序列號,并且在雙方通信的過程中序列號是連續的。通信雙方就是通過序列號來控制數據的發送和接收
以客戶端A和服務器B通信為例,客戶端A發送一個包含100字節數據的報文段,其中數據的第一個字節的序列號為200,那么可以得知這段數據的序列號范圍是[200 : 299],服務器接收到這段數據后回應客戶端一個應答報文段,該報文段中包含了服務器希望下次接受的數據第一個字節的序列號,即300.
通過這樣的一來一往,服務器接收到序列號為[200:299]的這段數據,并且告知客戶端自己接下來需要接收序列號從300開始的數據。而客戶端知道服務器已經接收到[200:299]這段數據,并且準備發送序列號從300開始的數據。
如前所述,TCP內部設有發送緩沖區和接收緩沖區,為了將事情簡化,下面僅考慮客戶端的發送緩沖區以及服務器的接收緩沖區。同時,與發送緩沖區對應的是發送窗口,與接收緩沖區對應的是接收窗口。這兩個滑動窗口在三次握手建立連接后被創建,起點是對方的起始序列號(由SYN同步位被標記為1的報文段確定)。
此外,除了起始序列號,三次握手的過程中還各自通知了窗口大小。如服務器告訴客戶端窗口大小為100,則客戶端初始的滑動窗口大小就為100
100是服務器目前可以接收的序列號個數,也即字節個數
在平時的網絡編程中,應用程序使用send/write等api將數據發送給對端,實際上僅僅是將數據復制到自己的TCP發送緩沖區就返回了,剩下的事情TCP協議棧會自己完成,在合適的時間將數據發送出去。這里合適的時間就是根據發送窗口判斷的。
數據發送時滑動窗口的變化
從發送緩沖區的視角觀察,滑動窗口位于發送緩沖區上,覆蓋的區域表示能被立即發送的數據區域或者已經發送但是沒有收到接收方應答的數據
接收方發送的報文段中包含了發送方應該設置的窗口大小,即接收方提供的窗口。圖片中此時滑動窗口覆蓋了[4:9]字節的區域,表明接收方已經確認了包括第3個字節以內的所有數據,且通告窗口大小為6。該窗口大小表明發送方有多少數據可以立即發送
當接收方確認數據后,發送方的滑動窗口不時地向右移動。窗口的左右沿移動情況有以下三種
- 窗口左沿向右邊沿靠近,稱為窗口合攏。這種現象發生在數據被發送并且被接收方正確接收
- 窗口右沿向右移動,稱為窗口張開。這種現象發生在接收方應用程序讀取了接收緩沖區的數據以允許接收更多的數據,此時發送方可以發送更多的數據
- 窗口右沿向左移動,稱為窗口收縮。但是這種方式不被使用,原因是之前可能已經將數據發送
舉例來說,下圖為客戶端A某一時刻的一個簡化的發送窗口,從圖中可以得知以下信息
- 窗口大小為20,表示服務器還能接收20個字節(序列號),即允許客戶端發送的序列號數量為20
- 序列號31為下一個正要準備發送的數據序列號,能夠發送的序列號范圍是[31:50]
- 序列號小于31的數據已經發送給了服務器并且也已經得到服務器的應答
現在假設客戶端A將序列號為[31:41]的這段數據發送出去,在沒有接收到服務器的應答之前,客戶端A的發送窗口不會改變
其中,P1,P2,P3三個指針的含義為
- 小于P1的部分是已經發送給服務器并且得到服務器回應的數據
- 大于P3的部分是超過服務器接收能力的數據部分,目前不允許發送
- [P1:P3)表示當前的發送窗口(滑動窗口)
- [P1:P3)表示客戶端已經發送給服務器但是仍然沒有得到服務器回應的數據部分
- [P2:P3)表示客戶端可以發送但是還沒有發送的數據部分
接下來假設服務器接收到了客戶端發來的序列號為[31:33]的數據段,并返回給客戶端應答報文段(ACK位被置1),每一個應答報文段都包含了以下幾個重要信息
- 接收到的數據最后一個序列號,此時為33
- 希望接收的下一個序列號,此時為34
- 允許的窗口大小,即服務器還可以接收的數據字節數。假設服務器的承載能力沒有變,那么窗口大小仍為20
客戶端會根據應答報文段中的服務器已接收的數據的最后一個序列號來判斷有多少數據已經被服務器接收,如果為33,表明[31:33]這段數據已經被接收,相應的發送窗口就需要右移
此時客戶端的發送窗口會右移3個序列號(因為已經確定31,32,33這三個序列號的數據被服務器接收),其中
- [P1:P3)仍然表示發送窗口,窗口大小仍為20
- [P1:P2)表示已發送但是沒有收到回應的數據部分,較之前相比少了左邊三個
- [P1:P3)表示可以發送但是尚未發送的數據部分,較之前相比多了右邊三個
超時重傳
TCP協議棧就是按照上述的滑動窗口來控制發送和接收的平衡。不過很多時候網絡并不是都那么流暢,可能就會出現某些數據丟失的情況(如客戶端發送的數據丟失或者服務器發送的應答報文段丟失等),為了解決這個問題,TCP引入超時重傳的機制,即每發送一段序列號的數據,就會為這段數據啟動一個定時器,如果在規定的時間內沒有收到服務器對于這段數據的回應,那么客戶端就會認為數據丟失,便會重新發送,同時重置定時器,如此往復直到接收到服務器的回應(這也正是需要保留已發送但未收到確認的那段數據的原因,因為可能需要重傳)
為了模擬這種情況,現假設服務器接收到的數據并不是按照序列號順序到來的,比如此時服務器接收到37,38,40三個序列號的數據,而其它數據可能在傳輸過程中丟失。那么服務器的接收窗口(注意這里是服務器的接受窗口而非客戶端的發送窗口)為
從服務器的接收窗口可以看到如下信息
- 接受窗口大小為20,正在等待接收序列號為[34:53]的數據
- 已經接收到序列號為37,38,40的數據
按照慣例,服務器在接收到報文段后應該回復給客戶端一個應答報文段,報文段中應該包含目前接收到的最后一個序列號,并指明希望下次接收的數據序列號,然后事實上,服務器發送給客戶端的應答請求中會帶有如下信息
- 目前接收到的最后一個數據序列號為33,而不是40
- 希望下次接收到的數據序列號為34
- 窗口大小為20
沒錯,即使已經接收到了37,38,40三個數據,但是[34:36]這段數據卻沒有收到,這表明接收數據出現亂序或者數據丟失。而服務器不能回應客戶端此時接收到的最后一個序列號為40,因為如果這樣就表明序列號在40之前的數據已經全部接收到,客戶端可以將發送窗口右移到41了,顯然這會導致數據的丟失。
當然,客戶端確實已經發送了序列號[34:41]的這段數據,然而卻遲遲沒有收到服務器對于這段數據的應答。一段時間后,定時器超時,客戶端會重新發送[34:41]這段數據,然后重置定時器,如此往復直到收到服務器對于這段數據的回應
由于定時器是按序列號段設置的,所以重傳也是重傳一整個數據段,即使37,38,40這三個數據已經被服務器接收到
不過在這段期間,由于客戶端A的發送窗口仍然有數據可以發送,那么就不能閑著,客戶端也會將剩余的數據發送給服務器,也為這段數據啟動一個定時器,兩不耽誤
除了數據在發送給服務器的過程中丟失外,服務器回復給客戶端的應答報文段也可能丟失,當然處理方法是一樣的,當定時器超時,客戶端都會重發,因為客戶端根本不會知道數據出現了什么狀況,只管超時重發就好了
重復數據的處理
在上面討論超時重傳機制時,客戶端發送[34:41]這段數據,而只有37,38,40這三個數據成功到達服務器,其他數據都已丟失。所以當定時器超時時,客戶端會重新發送數據,但是發送的仍然是[34:41]這個數據段,因為客戶端無法確定哪幾個數據丟失,哪幾個數據成功到達服務器(服務器返回給客戶端的應答請求表示服務器仍然希望接收到序列號為34的數據),所以客戶端索性就全部重發。
但是這會造成一個問題,當客戶端重發[34:41]這段數據并順利到達服務器時,由于服務器已經接收到37,38,40這三個數據,所以這一段數據存在重復部分。對于重復數據,TCP協議棧的解決辦法就是丟掉。
另外,如果某一個時刻服務器接收的數據序列號不在滑動窗口的范圍內,超出的部分也會被丟掉
流量控制
之前都是從滑動窗的視角觀察數據的發送和接收,沒有考慮窗口的動態變化。事實上,發送窗口的大小是動態變化的,這和接收方的承載能力有關,服務器發送給客戶端的應答報文段實際上就包含了窗口大小的字段
現在假設客戶端A和服務器B在三次握手建立連接后,服務器在SYN同步報文段中告知客戶端窗口大小為400,此時客戶端的發送窗口大小便初始化為400
如圖,客戶端先后發送了[1:100]和[101:200]這兩段數據,當發送序列號為[201:300]的數據段時丟失。可以得知服務器只能接收到200個數據,所以會回應給客戶端
- 接收到的最后一個序列號為200,
- 希望接收的下一個序列號為201
- 當前窗口大小應該為300,表示只能再接收300字節的數據
實際上,并不是服務器接收到一次數據就立刻發送應答報文段,而通常是等待一小段時間一起發送,這么做是為了將多個應答請求合并,或者是讓即將發往對端的數據捎帶上自己,避免了多個小報文段的開銷。上步就是將對[1:100]和[101:200]這兩段回應一起發送
接下來客戶端接收到了[1:200]的應答報文段,將滑動窗口右移,此時發送窗口覆蓋的序列號范圍應該是[201:500],不過由于之前客戶端已經發送了[201:300]的這段數據,只是定時器沒有超時,目前還不知道是否丟失。所以客戶端可以繼續發送[301:400],[401:500]這兩個數據段。
一段時間后[201:300]這段數據的定時器超時,客戶端重發這段數據。服務器接收到[201:500]這段數據,回應給客戶端
- 接收到的最后一個序列號為500
- 希望接收的下一個序列號為501
- 當前窗口大小應該為100,只允許客戶端再發送100字節的數據
得知服務器的承載能力后,客戶端發送[501:600]這100個字節的數據段,隨后服務器回應客戶端,同時表明窗口大小為0,表示不再允許客戶端發送數據(告訴客戶端,我已經裝不下啦,別再發了!)
零窗口探測
實際上不是只有接收到數據時服務器才會回應數據給客戶端,當服務器的接受窗口發生變化時,服務器也會發送給客戶端一個應答報文,報文中的內容包含
- 已接收到的數據的最后一個序列號
- 希望接收的下一個序列號
- 窗口大小
窗口大小用于告訴客戶端或者告訴不要繼續發送數據,或者可以繼續發送數據。
然而考慮如下問題,經過一段時間的數據交互,服務器在應答報文段中告知客戶端窗口大小為0,表示不再允許客戶端發送數據。隨后一小段時間內,服務器處理了部分或全部接收緩沖區的數據,此時接收緩沖區有空閑位置,接收窗口發生變化,服務器發送窗口更新應答報文段告知客戶端可以繼續發送數據。但是由于網絡原因這個應答報文段丟失(由于這不是數據報文段,不需要應答),這就導致死鎖,無法再進行數據交換,即
- 服務器等待客戶端發送數據
- 客戶端認為服務器仍然無法接收數據
為了解決這一問題,TCP協議棧在發送窗口引入零窗口探測報文,同時增加了零窗口定時器,該定時器會在對端的接受窗口變為0時被啟動,一旦定時器超時,發送方就會發送一個零窗口探測報文,而接收方接收到這種類型報文時會返回帶有窗口大小的應答報文。這樣發送方就可以根據窗口大小判斷是繼續等待還是已經可以發送數據了。如果繼續等待,則重置零窗口定時器,重新計時。
總結
以上是生活随笔為你收集整理的TCP/IP学习笔记(三)TCP流量控制以及滑动窗口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TCP/IP学习笔记(二)TCP三次握手
- 下一篇: TCP/IP学习笔记(四)TCP超时重传