一個例子明白髮送緩衝區、接受緩衝區、滑動視窗協議之間的關係。
在上面的幾篇文章中簡單介紹了上述幾個概念在TCP網路編程中的關係,也對應了幾個基本socket系統調用的幾個行為,這裡再列舉一個例子,由於對於每一個TCP的SOCKET來說,都有一個發送緩衝區和接受緩衝區與之對應,所以這裡只做單方向交流,不做互動,在recv端不send,在send端不recv。細細揣摩其中的含義。
一、recv端
在監聽通訊端上準備accept,在accept結束以後不做什麼操作,直接sleep很久,也就是在recv端並不做接受資料的操作,在sleep結束之後再recv資料。
二、send端
通過查看本系統核心預設的支援的最大發送緩衝區大小,cat/proc/sys/net/ipv4/tcp_wmem,最後一個參數為發送緩衝區的最大大小。接受緩衝區最大的設定檔在tcp_rmen中。
將通訊端設定為阻塞,一次發送的buffer大於最大發送緩衝區所能容納的資料量,一次send結束,在發送返回後接著答應發送的資料長度
測試結果:
階段一:
接受端表現:在剛開始發送資料時,接收端處於慢啟動狀態,滑動視窗大小越來愈大,但是由於接收端不處理接受緩衝區內的資料,其滑動視窗越來越小(因為接受端回應發送端中的win大小表示接受端還能夠接受多少資料,發送端下次發送的資料大小不能超過回應中win的大小),最後發送端回應給接受端的ACK中顯示的win大小為0,表示接收端不能夠再接受資料。
發送端表現:發送端一直不能返回,如果接受端一直回應win為0的情況下,發送端的send就會一直不能返回,這種僵局一直持續到接收端的sleep結束。
原因分析:首先需要明白幾個事實,阻塞式I/O會一直等待,直達這個操作完成;發送端接受到接收端的回應後才能將發送緩衝區中的資料進行清空。
在接收端不recv,那麼接收端的接受緩衝區內會一直有資料,接受緩衝區滿,導致滑動視窗為0,導致發送端不能發送資料。但是send操作為何不能反悔呢?send操作只是將應用緩衝區的資料拷貝到發送緩衝區,但是發送緩衝區的資料並沒有完全得到接收端的ACK回應,所以暫時不能將發送緩衝區中的資料丟棄,導致發送緩衝區的被填滿,這樣應用程式層中的資料也就不能拷貝到核心發送緩衝區內,也就會一直阻塞在這裡,直到可以繼續講應用程式層的資料拷貝到發送緩衝區中,何時觸發這個操作呢?等到發送端回應win大於0時才有這樣的操作。
階段二;
接受端:在sleep結束以後,開始調用recv系統調用。這個時候接受端的滑動視窗又開始大於零。那麼這樣就喚醒了發送端繼續發送資料。
發送端:發送端接受到接收端win大於0的回應,這個時候發送端又可以將應用程式層buffer中的資料拷貝到核心的發送緩衝區中。
原因分析:由於接受端調用recv將核心接受緩衝區的資料拷貝到應用程式層中,這樣滑動視窗又大於0了所以激發了發送端繼續發送資料,由於發送端可以發送資料了,核心協議棧便將發送緩衝區中的資料發送給接受端,這樣發送緩衝區又有空間了,那麼send操作就可以將應用程式層的資料拷貝到發送緩衝區了!這樣的操作一直保持到send操作返回,這樣代表著將應用程式層的資料全部拷貝到發送緩衝區內,但不代表將資料發送給對端。發送給對端成功的標誌是接受到對端的ACK回應,這個時候發送端才可以將發送緩衝區的資料丟棄。不丟棄的原因是時刻準備重發丟失/出錯的資料!
Ps: TCP通訊為了保證可靠性,每次發送的資料都需要得到對方的ACK才確認對方收到了(僅保證對方TCP接收緩衝收到 資料了,但不保證對方應用程式取到資料了),這時如果每次發送一次就要停下來等著對方的ACK訊息,顯然是一種極大的資源浪費和低下的效率,這時就有了滑動視窗的出現。
發送方的滑動視窗維持著當前發送的幀序號,已發出去幀的計時器,接收方當前的視窗大小(由接收方ACK通知,大體等於接收緩衝大小-未處理的訊息包),接收方滑動視窗儲存的有已接收的幀資訊、期待的下一幀的幀號等,至於滑動視窗的具體工作原理這裡就不說了。
一 個socket有兩個滑動視窗(一個sendbuf、一個recvbuf),兩個視窗的大小是通過setsockopt函數設定的,現在問題就出在這裡, 通過抓包顯示,設定的視窗大小沒有生效,最後排查發現setsockopt函數是後來加上的,寫到了listen函數的後面,這樣每次accept出的 socket並沒有繼承得到主socket設定的視窗大小,無語啊……
解決辦法:setsockopt函數提前到listen函數之前,這樣在伺服器程式啟動監聽前recvbuf就已經有了,accept後的連結得到的就是recvbuf了,啟動程式運行,抓包顯示視窗已經是指定的大小了。
一、TCP的滑動視窗大小實際上就是socket的接收緩衝區大小的位元組數
二、 對於server端的socket一定要在listen之前設定緩衝區大小,因為,accept時新產生的socket會繼承監聽socket的緩衝區大 小。對於client端的socket一定要在connet之前設定緩衝區大小,因為connet時需要進行三向交握過程,會通知對方自己的視窗大小。在 connet之後再設定緩衝區,已經沒有什麼意義。
三、由於緩衝區大小在TCP頭部只有16位來表示,所以它的最大值是65536,但是對於一些情況來說需要使用更大的滑動視窗,這時候就要使用擴充的滑動視窗,如光纖高速通訊網路,或者是衛星長串連網路,需要視窗儘可能的大。這時會使用擴充的32位的滑動視窗大小。