tcp之recv/send和滑動窗口知識學習


轉自:http://hi.baidu.com/gamedot/item/9a140eb9a615fbee4ec7fdc0

一、TCP的滑動接收窗口大小實際上就是socket的接收緩沖區大小的字節數。

  1、以上這話不絕對,接收滑動窗口大小會隨着內核接收緩沖區可接收數據大小變化。

  2、如下 ‘三’所述,tcp頭部只有16位表示有最大值限制65536,當然可以使用擴展的32位。

二、對於server端的socket一定要在listen之間設置緩沖區大小,因為,accept時新產生的socket會繼承監聽socket的緩沖區大小。對於client端的socket一定要在connet之前設置緩沖區大小,因為connet時需要進行三次握手過程,會通知對方自己的窗口大小。在connet之后再設置緩沖區,已經沒有什么意義。

三、由於緩沖區大小在TCP頭部只有16位來表示,所以它的最大值是65536,但是對於一些情況來說需要使用更大的滑動窗口,這時候就要使用擴展的滑動窗口,如光纖高速通信網絡,或者是衛星長連接網絡,需要窗口盡可能的大。這時會使用擴展的32位的滑動窗口大小。

四、滑動窗口聽移動規則:

1、窗口合攏:在收到對端數據后,自己確認了數據的正確性,這些數據會被存儲到緩沖區,等待應用程序獲取。但這時候因為已經確認了數據的正確性,需要向對方發送確認響應ACK,又因為這些數據還沒有被應用進程取走,這時候便需要進行窗口合攏,緩沖區的窗口左邊緣向右滑動。注意響應的ACK序號是對方發送數據包的序號,一個對方發送的序號,可能因為窗口張開會被響應(ACK)多次。

2、窗口張開:窗口收縮后,應用進程一旦從緩沖區中取出數據,TCP的滑動窗口需要進行擴張,這時候窗口的右邊緣向右擴張,實際上窗口這是一個環形緩沖區,窗口的右邊緣擴張會使用原來被應用進程取走內容的緩沖區。在窗口進行擴張后,需要使用ACK通知對端,這時候ACK的序號依然是上次確認收到包的序號。

3、窗口收縮,窗口的右邊緣向左滑動,稱為窗口收縮,Host Requirement RFC強烈建議不要這樣做,但TCP必須能夠在某一端產生這種情況時進行處理。

 

五、send()/recv()

1、send():send函數的作用是把應用程序緩沖區中的數據拷貝到內核緩沖區, 僅此而已.

2、recv():recv函數的作用是把內核緩沖區中的數據拷貝/剪切到應用程序緩沖區,recv函數是剪切還是復制, 由最后一個參數決定。

 

轉自http://blog.csdn.net/nodeathphoenix/article/details/30060461

本篇我們用一個測試機上的阻塞socket實例來說明主題。文章中所有圖都是在測試系統上現截取的。

需要理解的3個概念

1. TCP socket的buffer

每個TCP socket在內核中都有一個發送緩沖區和一個接收緩沖區,TCP的全雙工的工作模式以及TCP的流量(擁塞)控制便是依賴於這兩個獨立的buffer以及buffer的填充狀態。接收緩沖區把數據緩存入內核,應用進程一直沒有調用recv()進行讀取的話,此數據會一直緩存在相應socket的接收緩沖區內。再啰嗦一點,不管進程是否調用recv()讀取socket,對端發來的數據都會經由內核接收並且緩存到socket的內核接收緩沖區之中。recv()所做的工作,就是把內核緩沖區中的數據拷貝到應用層用戶的buffer里面,並返回,僅此而已。進程調用send()發送的數據的時候,最簡單情況(也是一般情況),將數據拷貝進入socket的內核發送緩沖區之中,然后send便會在上層返回。換句話說,send()返回之時,數據不一定會發送到對端去(和write寫文件有點類似),send()僅僅是把應用層buffer的數據拷貝進socket的內核發送buffer中,發送是TCP的事情,和send其實沒有太大關系。接收緩沖區被TCP用來緩存網絡上來的數據,一直保存到應用進程讀走為止。對於TCP,如果應用進程一直沒有讀取,接收緩沖區滿了之后,發生的動作是:收端通知發端,接收窗口關閉(win=0)。這個便是滑動窗口的實現。保證TCP套接口接收緩沖區不會溢出,從而保證了TCP是可靠傳輸。因為對方不允許發出超過所通告窗口大小的數據。 這就是TCP的流量控制,如果對方無視窗口大小而發出了超過窗口大小的數據,則接收方TCP將丟棄它。
查看測試機的socket發送緩沖區大小,如圖1所示

圖1

第一個值是一個限制值,socket發送緩存區的最少字節數;
第二個值是默認值;
第三個值是一個限制值,socket發送緩存區的最大字節數;
根據實際測試,發送緩沖區的尺寸在默認情況下的全局設置是16384字節,即16k。
在測試系統上,發送緩存默認值是16k。
proc文件系統下的值和sysctl中的值都是全局值,應用程序可根據需要在程序中使用setsockopt()對某個socket的發送緩沖區尺寸進行單獨修改,詳見文章《TCP選項之SO_RCVBUF和SO_SNDBUF》,不過這都是題外話。

2. 接收窗口(滑動窗口)

TCP連接建立之時的收端的初始接受窗口大小是14600,細節如圖2所示(129是收端,130是發端)

圖2

接收窗口是TCP中的滑動窗口,TCP的收端用這個接受窗口----win=14600,通知發端,我目前的接收能力是14600字節。
后續發送過程中,收端會不斷的用ACK(ACK的全部作用請參照博文《TCP之ACK發送情景》)通知發端自己的接收窗口的大小狀態,如圖3,而發端發送數據的量,就根據這個接收窗口的大小來確定,發端不會發送超過收端接收能力的數據量。這樣就起到了一個流量控制的的作用。

圖3
圖3說明
21,22兩個包都是收端發給發端的ACK包
第21個包,收端確認收到的前7240個字節數據,7241的意思是期望收到的包從7241號開始,序號加了1.同時,接收窗口從最初的14656(如圖2)經過慢啟動階段增加到了現在的29120。用來表明現在收端可以接收29120個字節的數據,而發端看到這個窗口通告,在沒有收到新的ACK的時候,發端可以向收端發送29120字節這么多數據。
第22個包,收端確認收到的前8688個字節數據,並通告自己的接收窗口繼續增長為32000這么大。

3. 單個TCP的負載量和MSS的關系

MSS在以太網上通常大小是1460字節,而我們在后續發送過程中的單個TCP包的最大數據承載量是1448字節,這二者的關系可以參考博文《TCP之1460MSS和1448負載》。

實例詳解send()

實例功能說明:接收端129作為客戶端去連接發送端130,連接上之后並不調用recv()接收,而是sleep(1000),把進程暫停下來,不讓進程接收數據。內核會緩存數據至接收緩沖區。發送端作為服務器接收TCP請求之后,立即用ret = send(sock,buf,70k,0);這個C語句,向接收端發送70k數據。
我們現在來觀察這個過程。看看究竟發生了些什么事。wireshark抓包截圖如下圖4

圖4


圖4說明,包序號等同於時序
1. 客戶端sleep在recv()之前,目的是為了把數據壓入接收緩沖區。服務端調用"ret = send(sock,buf,70k,0);"這個C語句,向接收端發送70k數據。由於發送緩沖區大小16k,send()無法將70k數據全部拷貝進發送緩沖區,故先拷貝16k進入發送緩沖區,下層發送緩沖區中有數據要發送,內核開始發送。上層send()在應用層處於阻塞狀態;
2. 11號TCP包,發端從這兒開始向收端發送1448個字節的數據;
3. 12號TCP包,發端沒有收到之前發送的1448個數據的ACK包,仍然繼續向收端發送1448個字節的數據;
4. 13號TCP包,收端向發端發送1448字節的確認包,表明收端成功接收總共1448個字節。此時收端並未調用recv()讀取,目前發送緩沖區中被壓入1448字節。由於處於慢啟動狀態,win接收窗口持續增大,表明接受能力在增加,吞吐量持續上升;
5. 14號TCP包,收端向發端發送2896字節的確認包,表明收端成功接收總共2896個字節。此時收端並未調用recv()讀取,目前發送緩沖區中被壓入2896字節。由於處於慢啟動狀態,win接收窗口持續增大,表明接受能力在增加,吞吐量持續上升;
6. 15號TCP包,發端繼續向收端發送1448個字節的數據;
7. 16號TCP包,收端向發端發送4344字節的確認包,表明收端成功接收總共4344個字節。此時收端並未調用recv()讀取,目前發送緩沖區中被壓入4344字節。由於處於慢啟動狀態,win接收窗口持續增大,表明接受能力在增加,吞吐量持續上升;
8. 從這兒開始,我略去很多包,過程類似上面過程。同時,由於不斷的發送出去的數據被收端用ACK確認,發送緩沖區的空間被逐漸騰出空地,send()內部不斷的把應用層buf中的數據向發送緩沖區拷貝,從而不斷的發送,過程重復。70k數據並沒有被完全送入內核,send()不管是否發送出去,send不管發送出去的是否被確認,send()只關心buf中的數據有沒有被全部送往發送緩沖區。如果buf中的數據沒有被全部送往發送緩沖區,send()在應用層阻塞,負責等待發送緩沖區中有空余空間的時候,逐步拷貝buf中的數據;如果buf中的數據被全部拷入發送緩沖區,send()立即返回。
9. 經過慢啟動階段接收窗口增大到穩定階段,TCP吞吐量升高到穩定階段,收端一直處於sleep狀態,沒有調用recv()把內核中接收緩沖區中的數據拷貝到應用層去,此時收端的接收緩沖區中被壓入大量數據;
10. 66號、67號TCP數據包,發端繼續向收端發送數據;
11. 68號TCP數據包,收端發送ACK包確認接收到的數據,ACK=62265表明收端已經收到62265字節的數據,這些數據目前被壓在收端的接收緩沖區中。win=3456,比較之前的16號TCP包的win=23296,表明收端的窗口已經處於收縮狀態,收端的接收緩沖區中的數據遲遲未被應用層讀走,導致接收緩沖區空間吃緊,故收縮窗口,控制發送端的發送量,進行流量控制;
12. 69號、70號TCP數據包,發端在接收窗口允許的數據量的范圍內,繼續向收端發送2段1448字節長度的數據;
13. 71號TCP數據包,至此,收端已經成功接收65160字節的數據,全部被壓在接收緩沖區之中,接收窗口繼續收縮,尺寸為1600字節;
14. 72號TCP數據包,發端在接收窗口允許的數據量的范圍內,繼續向收端發送1448字節長度的數據;
15. 73號TCP數據包,至此,收端已經成功接收66609字節的數據,全部被壓在接收緩沖區之中,接收窗口繼續收縮,尺寸為192字節。
16. 74號TCP數據包,和我們這個例子沒有關系,是別的應用發送的包;
17. 75號TCP數據包,發端在接收窗口允許的數據量的范圍內,向收端發送192字節長度的數據;
18. 76號TCP數據包,至此,收端已經成功接收66609字節的數據,全部被壓在接收緩沖區之中,win=0接收窗口關閉,接收緩沖區滿,無法再接收任何數據;
19. 77號、78號、79號TCP數據包,由keepalive觸發的數據包,響應的ACK持有接收窗口的狀態win=0,另外,ACK=66801表明接收端的接收緩沖區中積壓了66800字節的數據。
20. 從以上過程,我們應該熟悉了滑動窗口通告字段win所說明的問題,以及ACK確認數據等等。現在可得出一個結論,接收端的接收緩存尺寸應該是66800字節(此結論並非本篇主題)。
send()要發送的數據是70k,現在發出去了66800字節,發送緩存中還有16k,應用層剩余要拷貝進內核的數據量是N=70k-66800-16k。接收端仍處於sleep狀態,無法recv()數據,這將導致接收緩沖區一直處於積壓滿的狀態,窗口會一直通告0(win=0)。發送端在這樣的狀態下徹底無法發送數據了,send()的剩余數據無法繼續拷貝進內核的發送緩沖區,最終導致send()被阻塞在應用層;
21. send()一直阻塞中。。。

圖4和send()的關系說明完畢。
那什么時候send返回呢?有3種返回場景

send()返回場景

場景1,我們繼續圖4這個例子,不過這兒開始我們就跳出圖4所示的過程了

22. 接收端sleep(1000)到時間了,進程被喚醒,代碼片段如圖5

圖5
隨着進程不斷的用"recv(fd,buf,2048,0);"將數據從內核的接收緩沖區拷貝至應用層的buf,在使用win=0關閉接收窗口之后,現在接收緩沖區又逐漸恢復了緩存的能力,這個條件下,收端會主動發送攜帶"win=n(n>0)"這樣的ACK包去通告發送端接收窗口已打開;

23. 發端收到攜帶"win=n(n>0)"這樣的ACK包之后,開始繼續在窗口運行的數據量范圍內發送數據。發送緩沖區的數據被發出;
24. 收端繼續接收數據,並用ACK確認這些數據;
25. 發端收到ACK,可以清理出一些發送緩沖區空間,應用層send()的剩余數據又可以被不斷的拷貝進內核的發送緩沖區;
26. 不斷重復以上發送過程;
27. send()的70k數據全部進入內核,send()成功返回。

場景2,我們繼續圖4這個例子,不過這兒開始我們就跳出圖4所示的過程了
22. 收端進程或者socket出現問題,給發端發送一個RST,請參考博文《》;
23. 內核收到RST,send返回-1。

場景3,和以上例子沒關系
連接上之后,馬上send(1k),這樣,發送的數據肯定可以一次拷貝進入發送緩沖區,send()拷貝完數據立即成功返回。

send()發送結論

其實場景1和場景2說明一個問題
send()只是負責拷貝,拷貝完立即返回,不會等待發送和發送之后的ACK。如果socket出現問題,RST包被反饋回來。在RST包返回之時,如果send()還沒有把數據全部放入內核或者發送出去,那么send()返回-1,errno被置錯誤值;如果RST包返回之時,send()已經返回,那么RST導致的錯誤會在下一次send()或者recv()調用的時候被立即返回。
場景3完全說明send()只要完成拷貝就成功返回,如果發送數據的過程中出現各種錯誤,下一次send()或者recv()調用的時候被立即返回。

概念上容易疑惑的地方

1. TCP協議本身是為了保證可靠傳輸,並不等於應用程序用tcp發送數據就一定是可靠的,必須要容錯;
2. send()和recv()沒有固定的對應關系,不定數目的send()可以觸發不定數目的recv(),這話不專業,但是還是必須說一下,初學者容易疑惑;
3. 關鍵點,send()只負責拷貝,拷貝到內核就返回,我通篇在說拷貝完返回,很多文章中說send()在成功發送數據后返回,成功發送是說發出去的東西被ACK確認過。send()只拷貝,不會等ACK;
4. 此次send()調用所觸發的程序錯誤,可能會在本次返回,也可能在下次調用網絡IO函數的時候被返回。

 

實際上理解了阻塞式的,就能理解非阻塞的。

 

 

轉自http://www.cnblogs.com/lidabo/p/4534755.html

Tcp協議本身是可靠的,並不等於應用程序用tcp發送數據就一定是可靠的.不管是否阻塞,send發送的大小,並不代表對端recv到多少的數據.

在阻塞模式下, send函數的過程是將應用程序請求發送的數據拷貝到發送緩存中發送並得到確認后再返回.但由於發送緩存的存在,表現為:如果發送緩存大小比請求發送的大小要大,那么send函數立即返回,同時向網絡中發送數據;否則,send向網絡發送緩存中不能容納的那部分數據,並等待對端確認后再返回(接收端只要將數據收到接收緩存中,就會確認,並不一定要等待應用程序調用recv);

在非阻塞模式下,send函數的過程僅僅是將數據拷貝到協議棧的緩存區而已,如果緩存區可用空間不夠,則盡能力的拷貝,返回成功拷貝的大小;如緩存區可用空間為0,則返回-1,同時設置errno為EAGAIN.


linux下可用sysctl -a | grep net.ipv4.tcp_wmem查看系統默認的發送緩存大小:
net.ipv4.tcp_wmem = 4096 16384 81920
這有三個值,第一個值是socket的發送緩存區分配的最少字節數,第二個值是默認值(該值會被net.core.wmem_default覆蓋),緩存區在系統負載不重的情況下可以增長到這個值,第三個值是發送緩存區空間的最大字節數(該值會被net.core.wmem_max覆蓋).
根據實際測試,如果手工更改了net.ipv4.tcp_wmem的值,則會按更改的值來運行,否則在默認情況下,協議棧通常是按net.core.wmem_default和net.core.wmem_max的值來分配內存的.

應用程序應該根據應用的特性在程序中更改發送緩存大小:

socklen_t sendbuflen = 0;
socklen_t len = sizeof(sendbuflen);
getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);
printf("default,sendbuf:%d/n", sendbuflen);

sendbuflen = 10240;
setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len);
getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);
printf("now,sendbuf:%d/n", sendbuflen);


需要注意的是,雖然將發送緩存設置成了10k,但實際上,協議棧會將其擴大1倍,設為20k.
-------------------實例分析---------------

在實際應用中,如果發送端是非阻塞發送,由於網絡的阻塞或者接收端處理過慢,通常出現的情況是,發送應用程序看起來發送了10k的數據,但是只發送了2k到對端緩存中,還有8k在本機緩存中(未發送或者未得到接收端的確認).那么此時,接收應用程序能夠收到的數據為2k.假如接收應用程序調用recv函數獲取了1k的數據在處理,在這個瞬間,發生了以下情況之一,雙方表現為:

A. 發送應用程序認為send完了10k數據,關閉了socket:
發送主機作為tcp的主動關閉者,連接將處於FIN_WAIT1的半關閉狀態(等待對方的ack),並且,發送緩存中的8k數據並不清除,依然會發送給對端.如果接收應用程序依然在recv,那么它會收到余下的8k數據(這個前題是,接收端會在發送端FIN_WAIT1狀態超時前收到余下的8k數據.), 然后得到一個對端socket被關閉的消息(recv返回0).這時,應該進行關閉.

B. 發送應用程序再次調用send發送8k的數據:
假如發送緩存的空間為20k,那么發送緩存可用空間為20-8=12k,大於請求發送的8k,所以send函數將數據做拷貝后,並立即返回8192;

假如發送緩存的空間為12k,那么此時發送緩存可用空間還有12-8=4k,send()會返回4096,應用程序發現返回的值小於請求發送的大小值后,可以認為緩存區已滿,這時必須阻塞(或通過select等待下一次socket可寫的信號),如果應用程序不理會,立即再次調用send,那么會得到-1的值, 在linux下表現為errno=EAGAIN.

C. 接收應用程序在處理完1k數據后,關閉了socket:
接收主機作為主動關閉者,連接將處於FIN_WAIT1的半關閉狀態(等待對方的ack).然后,發送應用程序會收到socket可讀的信號(通常是 select調用返回socket可讀),但在讀取時會發現recv函數返回0,這時應該調用close函數來關閉socket(發送給對方ack);

如果發送應用程序沒有處理這個可讀的信號,而是在send,那么這要分兩種情況來考慮,假如是在發送端收到RST標志之后調用send,send將返回-1,同時errno設為ECONNRESET表示對端網絡已斷開,但是,也有說法是進程會收到SIGPIPE信號,該信號的默認響應動作是退出進程,如果忽略該信號,那么send是返回-1,errno為EPIPE(未證實);如果是在發送端收到RST標志之前,則send像往常一樣工作;

以上說的是非阻塞的send情況,假如send是阻塞調用,並且正好處於阻塞時(例如一次性發送一個巨大的buf,超出了發送緩存),對端socket關閉,那么send將返回成功發送的字節數,如果再次調用send,那么會同上一樣.

D. 交換機或路由器的網絡斷開:
接收應用程序在處理完已收到的1k數據后,會繼續從緩存區讀取余下的1k數據,然后就表現為無數據可讀的現象,這種情況需要應用程序來處理超時.一般做法是設定一個select等待的最大時間,如果超出這個時間依然沒有數據可讀,則認為socket已不可用.

發送應用程序會不斷的將余下的數據發送到網絡上,但始終得不到確認,所以緩存區的可用空間持續為0,這種情況也需要應用程序來處理.

如果不由應用程序來處理這種情況超時的情況,也可以通過tcp協議本身來處理,具體可以查看sysctl項中的:
net.ipv4.tcp_keepalive_intvl
net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
http://www.ixpub.net/thread-1446913-1-1.html
 
發送成功只是表示發到了內核socket緩沖區
此時如果close,正常情況會進入TIME_WAIT狀態,在此狀態,對端可以繼續接收數據
但是如果發送方的接收緩沖區還有未讀數據,就會走異常close的途徑,置RST,立刻結束連接,沒有TIME_WAIT狀態。這時對端就收不全數據,報錯: Connection reset by peer。
 
///////////////////////////////////////////////////////////////////////////////////////////
值得參考的 TCP send和recv函數解析
http://blog.csdn.net/wjtxt/article/details/6603456

一、 滑動窗口的概念

        TCP數據包的TCP頭部有一個window字段,它主要是用來告訴對方自己能接收多大的數據(注意只有TCP包中的數據部分占用這個空間),這個字段在通信雙方建立連接時協商確定,並且在通信過程中不斷更新,故取名為滑動窗口。有了這個字段,數據發送方就知道自己該不該發送數據,以及該發多少數據了。TCP協議的流量控制正是通過滑動窗口實現,從而保證通信雙方的接收緩沖區不會溢出,數據不會丟失。

由於窗口大小在TCP頭部只有16位來表示,所以它的最大值是65536,但是對於一些情況來說需要使用更大的滑動窗口,這時候就要使用擴展的滑動窗口,如光纖高速通信網絡,或者是衛星長連接網絡,需要窗口盡可能的大。這時會使用擴展的32位的滑動窗口大小。

二、 滑動窗口移動規則

        1、窗口合攏:在收到對端數據后,自己確認了數據的正確性,這些數據會被存儲到接收緩沖區,等待應用程序獲取。但這時候因為已經確認了數據的正確性,需要向對方發送確認響應ACK,又因為這些數據還沒有被應用進程取走,這時候便需要進行窗口合攏,緩沖區的窗口左邊緣向右滑動。注意響應的ACK序號是對方發送數據包的序號,一個對方發送的序號,可能因為窗口張開會被響應(ACK)多次。

        2、窗口張開:窗口收縮后,應用進程一旦從緩沖區(滑動窗口區或接收緩沖區)中取出數據,TCP的滑動窗口需要進行擴張,這時候窗口的右邊緣向右擴張,實際上窗口這是一個環形緩沖區,窗口的右邊緣擴張會使用原來被應用進程取走內容的緩沖區。在窗口進行擴張后,需要使用ACK通知對端,這時候ACK的序號依然是上次確認收到包的序號。

        3、窗口收縮,窗口的右邊緣向左滑動,稱為窗口收縮,HostRequirement RFC強烈建議不要這樣做,但TCP必須能夠在某一端產生這種情況時進行處理。

三、send行為

        默認情況下,send的功能是拷貝指定長度的數據到發送緩沖區,只有當數據被全部拷貝完成后函數才會正確返回,否則進入阻塞狀態或等待超時。如果你想修改這種默認行為,將數據直接發送到目標機器,可以將發送緩沖區大小設為0,這樣當send返回時,就表示數據已經正確的、完整的到達了目標機器。注意,這里只表示數據到達目標機器網絡緩沖區,並不表示數據已經被對方應用層接收了。

        協議層在數據發送過程中,根據對方的滑動窗口,再結合MSS值共同確定TCP報文中數據段的長度,以確保對方接收緩沖區不會溢出。當本方發送緩沖區尚有數據沒有發送,而對方滑動窗口已經為0時,協議層將啟動探測機制,即每隔一段時間向對方發送一個字節的數據,時間間隔會從剛開始的30s調整為1分鍾,最后穩定在2分鍾。這個探測機制不僅可以檢測到對方滑動窗口是否變化,同時也可以發現對方是否有異常退出的情況。

        push標志指示接收端應盡快將數據提交給應用層。如果send函數提交的待發送數據量較小,例如小於1460B(參照MSS值確定),那么協議層會將該報文中的TCP頭部的push字段置為1;如果待發送的數據量較大,需要拆成多個數據段發送時,協議層只會將最后一個分段報文的TCP頭部的push字段置1。

四、recv行為

        默認情況下,recv的功能是從接收緩沖區讀取(其實就是拷貝)指定長度的數據。如果將接收緩沖區大小設為0,recv將直接從協議緩沖區(滑動窗口區)讀取數據,避免了數據從協議緩沖區到接收緩沖區的拷貝。recv返回的條件有兩種:

      1. recv函數傳入的應用層接收緩沖區已經讀滿

      2. 協議層接收到push字段為1的TCP報文,此時recv返回值為實際接收的數據長度

        協議層收到TCP數據包后(保存在滑動窗口區),本方的滑動窗口合攏(窗口值減小);當協議層將數據拷貝到接收緩沖區(滑動窗口區—>接收緩沖區),或者應用層調用recv接收數據(接收緩沖區—>應用層緩沖區,滑動窗口區—>應用層緩沖區)后,本方的滑動窗口張開(窗口值增大)。收到數據更新window后,協議層向對方發送ACK確認。

        協議層的數據接收動作完全由發送動作驅動,是一個被動行為。在應用層沒有任何干涉行為的情況下(比如recv操作等),協議層能夠接收並保存的最大數據大小是窗口大小與接收緩沖區大小之和。Windows系統的窗口大小默認是64K,接收緩沖區默認為8K,所以默認情況下協議層最多能夠被動接收並保存72K的數據。

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

http://blog.csdn.net/wjtxt/article/details/6598925

TCP連接關閉的問題:

 

 從TCP協議角度來看,一個已建立的TCP連接有兩種關閉方式,一種是正常關閉,即四次揮手關閉連接;還有一種則是異常關閉,我們通常稱之為連接重置(RESET)。

        首先說一下正常關閉時四次揮手的狀態變遷,關閉連接的主動方狀態變遷是FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT,而關閉連接的被對方的狀態變遷是CLOSE_WAIT->LAST_ACK->TIME_WAIT。在四次揮手過程中ACK包都是協議棧自動完成的,而FIN包則必須由應用層通過closesocket或shutdown主動發送,通常連接正常關閉后,recv會得到返回值0,send會得到錯誤碼10058。

        除此之外,在我們的日常應用中,連接異常關閉的情況也很多。比如應用程序被強行關閉、本地網絡突然中斷(禁用網卡、網線拔出)、程序處理不當等都會導致連接重置,連接重置時將會產生RST包,同時網絡絡緩沖區中未接收(發送)的數據都將丟失。連接重置后,本方send或recv會得到錯誤碼10053(closesocket時是10038),對方recv會得到錯誤碼10054,send則得到錯誤碼10053(closesocket時是10054)。

        操作系統為我們提供了兩個函數來關閉一個TCP連接,分別是closesocket和shutdown。通常情況下,closesocket會向對方發送一個FIN包,但是也有例外。比如有一個工作線程正在調用recv接收數據,此時外部調用closesocket,會導致連接重置,同時向對方發送一個RST包,這個RST包是由本方主動產生的。

        shutdown可以用來關閉指定方向的連接,該函數接收兩個參數,一個是套接字,另一個是關閉的方向,可用值為SD_SEND,SD_RECEIVE和SD_BOTH。方向取值為SD_SEND時,無論socket處於什么狀態(recv阻塞,或空閑狀態),都會向對方發送一個FIN包,注意這點與closesocket的區別。此時本方進入FIN_WAIT_2狀態,對方進入CLOSE_WAIT狀態,本方依然可以調用recv接收數據;方向取值為SD_RECEIVE時,雙發連接狀態沒有改變,依然處於ESTABLISHED狀態,本方依然可以send數據,但是,如果對方再調用send方法,連接會被立即重置,同時向對方發送一個RST包,這個RST包是被動產生的,這點注意與closesocket的區別。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM