TCP之深入浅出send和recv
生活随笔
收集整理的這篇文章主要介紹了
TCP之深入浅出send和recv
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
本篇我們用一個(gè)測(cè)試機(jī)上的阻塞socket實(shí)例來(lái)說(shuō)明主題。文章中所有圖都是在測(cè)試系統(tǒng)上現(xiàn)截取的。
查看測(cè)試機(jī)的socket發(fā)送緩沖區(qū)大小,如圖1所示
圖1 第一個(gè)值是一個(gè)限制值,socket發(fā)送緩存區(qū)的最少字節(jié)數(shù);
第二個(gè)值是默認(rèn)值;
第三個(gè)值是一個(gè)限制值,socket發(fā)送緩存區(qū)的最大字節(jié)數(shù);
根據(jù)實(shí)際測(cè)試,發(fā)送緩沖區(qū)的尺寸在默認(rèn)情況下的全局設(shè)置是16384字節(jié),即16k。
在測(cè)試系統(tǒng)上,發(fā)送緩存默認(rèn)值是16k。
proc文件系統(tǒng)下的值和sysctl中的值都是全局值,應(yīng)用程序可根據(jù)需要在程序中使用setsockopt()對(duì)某個(gè)socket的發(fā)送緩沖區(qū)尺寸進(jìn)行單獨(dú)修改,詳見(jiàn)文章《TCP選項(xiàng)之SO_RCVBUF和SO_SNDBUF》,不過(guò)這都是題外話。
圖2 接收窗口是TCP中的滑動(dòng)窗口,TCP的收端用這個(gè)接受窗口----win=14600,通知發(fā)端,我目前的接收能力是14600字節(jié)。
后續(xù)發(fā)送過(guò)程中,收端會(huì)不斷的用ACK(ACK的全部作用請(qǐng)參照博文《TCP之ACK發(fā)送情景》)通知發(fā)端自己的接收窗口的大小狀態(tài),如圖3,而發(fā)端發(fā)送數(shù)據(jù)的量,就根據(jù)這個(gè)接收窗口的大小來(lái)確定,發(fā)端不會(huì)發(fā)送超過(guò)收端接收能力的數(shù)據(jù)量。這樣就起到了一個(gè)流量控制的的作用。
圖3
圖3說(shuō)明
21,22兩個(gè)包都是收端發(fā)給發(fā)端的ACK包
第21個(gè)包,收端確認(rèn)收到的前7240個(gè)字節(jié)數(shù)據(jù),7241的意思是期望收到的包從7241號(hào)開(kāi)始,序號(hào)加了1.同時(shí),接收窗口從最初的14656(如圖2)經(jīng)過(guò)慢啟動(dòng)階段增加到了現(xiàn)在的29120。用來(lái)表明現(xiàn)在收端可以接收29120個(gè)字節(jié)的數(shù)據(jù),而發(fā)端看到這個(gè)窗口通告,在沒(méi)有收到新的ACK的時(shí)候,發(fā)端可以向收端發(fā)送29120字節(jié)這么多數(shù)據(jù)。
第22個(gè)包,收端確認(rèn)收到的前8688個(gè)字節(jié)數(shù)據(jù),并通告自己的接收窗口繼續(xù)增長(zhǎng)為32000這么大。
我們現(xiàn)在來(lái)觀察這個(gè)過(guò)程。看看究竟發(fā)生了些什么事。wireshark抓包截圖如下圖4
圖4
圖4說(shuō)明,包序號(hào)等同于時(shí)序
1. 客戶(hù)端sleep在recv()之前,目的是為了把數(shù)據(jù)壓入接收緩沖區(qū)。服務(wù)端調(diào)用"ret = send(sock,buf,70k,0);"這個(gè)C語(yǔ)句,向接收端發(fā)送70k數(shù)據(jù)。由于發(fā)送緩沖區(qū)大小16k,send()無(wú)法將70k數(shù)據(jù)全部拷貝進(jìn)發(fā)送緩沖區(qū),故先拷貝16k進(jìn)入發(fā)送緩沖區(qū),下層發(fā)送緩沖區(qū)中有數(shù)據(jù)要發(fā)送,內(nèi)核開(kāi)始發(fā)送。上層send()在應(yīng)用層處于阻塞狀態(tài);
2. 11號(hào)TCP包,發(fā)端從這兒開(kāi)始向收端發(fā)送1448個(gè)字節(jié)的數(shù)據(jù);
3. 12號(hào)TCP包,發(fā)端沒(méi)有收到之前發(fā)送的1448個(gè)數(shù)據(jù)的ACK包,仍然繼續(xù)向收端發(fā)送1448個(gè)字節(jié)的數(shù)據(jù);
4. 13號(hào)TCP包,收端向發(fā)端發(fā)送1448字節(jié)的確認(rèn)包,表明收端成功接收總共1448個(gè)字節(jié)。此時(shí)收端并未調(diào)用recv()讀取,目前發(fā)送緩沖區(qū)中被壓入1448字節(jié)。由于處于慢啟動(dòng)狀態(tài),win接收窗口持續(xù)增大,表明接受能力在增加,吞吐量持續(xù)上升;
5. 14號(hào)TCP包,收端向發(fā)端發(fā)送2896字節(jié)的確認(rèn)包,表明收端成功接收總共2896個(gè)字節(jié)。此時(shí)收端并未調(diào)用recv()讀取,目前發(fā)送緩沖區(qū)中被壓入2896字節(jié)。由于處于慢啟動(dòng)狀態(tài),win接收窗口持續(xù)增大,表明接受能力在增加,吞吐量持續(xù)上升;
6. 15號(hào)TCP包,發(fā)端繼續(xù)向收端發(fā)送1448個(gè)字節(jié)的數(shù)據(jù);
7. 16號(hào)TCP包,收端向發(fā)端發(fā)送4344字節(jié)的確認(rèn)包,表明收端成功接收總共4344個(gè)字節(jié)。此時(shí)收端并未調(diào)用recv()讀取,目前發(fā)送緩沖區(qū)中被壓入4344字節(jié)。由于處于慢啟動(dòng)狀態(tài),win接收窗口持續(xù)增大,表明接受能力在增加,吞吐量持續(xù)上升;
8. 從這兒開(kāi)始,我略去很多包,過(guò)程類(lèi)似上面過(guò)程。同時(shí),由于不斷的發(fā)送出去的數(shù)據(jù)被收端用ACK確認(rèn),發(fā)送緩沖區(qū)的空間被逐漸騰出空地,send()內(nèi)部不斷的把應(yīng)用層buf中的數(shù)據(jù)向發(fā)送緩沖區(qū)拷貝,從而不斷的發(fā)送,過(guò)程重復(fù)。70k數(shù)據(jù)并沒(méi)有被完全送入內(nèi)核,send()不管是否發(fā)送出去,send不管發(fā)送出去的是否被確認(rèn),send()只關(guān)心buf中的數(shù)據(jù)有沒(méi)有被全部送往發(fā)送緩沖區(qū)。如果buf中的數(shù)據(jù)沒(méi)有被全部送往發(fā)送緩沖區(qū),send()在應(yīng)用層阻塞,負(fù)責(zé)等待發(fā)送緩沖區(qū)中有空余空間的時(shí)候,逐步拷貝buf中的數(shù)據(jù);如果buf中的數(shù)據(jù)被全部拷入發(fā)送緩沖區(qū),send()立即返回。
9. 經(jīng)過(guò)慢啟動(dòng)階段接收窗口增大到穩(wěn)定階段,TCP吞吐量升高到穩(wěn)定階段,收端一直處于sleep狀態(tài),沒(méi)有調(diào)用recv()把內(nèi)核中接收緩沖區(qū)中的數(shù)據(jù)拷貝到應(yīng)用層去,此時(shí)收端的接收緩沖區(qū)中被壓入大量數(shù)據(jù);
10. 66號(hào)、67號(hào)TCP數(shù)據(jù)包,發(fā)端繼續(xù)向收端發(fā)送數(shù)據(jù);
11. 68號(hào)TCP數(shù)據(jù)包,收端發(fā)送ACK包確認(rèn)接收到的數(shù)據(jù),ACK=62265表明收端已經(jīng)收到62265字節(jié)的數(shù)據(jù),這些數(shù)據(jù)目前被壓在收端的接收緩沖區(qū)中。win=3456,比較之前的16號(hào)TCP包的win=23296,表明收端的窗口已經(jīng)處于收縮狀態(tài),收端的接收緩沖區(qū)中的數(shù)據(jù)遲遲未被應(yīng)用層讀走,導(dǎo)致接收緩沖區(qū)空間吃緊,故收縮窗口,控制發(fā)送端的發(fā)送量,進(jìn)行流量控制;
12. 69號(hào)、70號(hào)TCP數(shù)據(jù)包,發(fā)端在接收窗口允許的數(shù)據(jù)量的范圍內(nèi),繼續(xù)向收端發(fā)送2段1448字節(jié)長(zhǎng)度的數(shù)據(jù);
13. 71號(hào)TCP數(shù)據(jù)包,至此,收端已經(jīng)成功接收65160字節(jié)的數(shù)據(jù),全部被壓在接收緩沖區(qū)之中,接收窗口繼續(xù)收縮,尺寸為1600字節(jié);
14. 72號(hào)TCP數(shù)據(jù)包,發(fā)端在接收窗口允許的數(shù)據(jù)量的范圍內(nèi),繼續(xù)向收端發(fā)送1448字節(jié)長(zhǎng)度的數(shù)據(jù);
15. 73號(hào)TCP數(shù)據(jù)包,至此,收端已經(jīng)成功接收66609字節(jié)的數(shù)據(jù),全部被壓在接收緩沖區(qū)之中,接收窗口繼續(xù)收縮,尺寸為192字節(jié)。
16. 74號(hào)TCP數(shù)據(jù)包,和我們這個(gè)例子沒(méi)有關(guān)系,是別的應(yīng)用發(fā)送的包;
17. 75號(hào)TCP數(shù)據(jù)包,發(fā)端在接收窗口允許的數(shù)據(jù)量的范圍內(nèi),向收端發(fā)送192字節(jié)長(zhǎng)度的數(shù)據(jù);
18. 76號(hào)TCP數(shù)據(jù)包,至此,收端已經(jīng)成功接收66609字節(jié)的數(shù)據(jù),全部被壓在接收緩沖區(qū)之中,win=0接收窗口關(guān)閉,接收緩沖區(qū)滿,無(wú)法再接收任何數(shù)據(jù);
19. 77號(hào)、78號(hào)、79號(hào)TCP數(shù)據(jù)包,由keepalive觸發(fā)的數(shù)據(jù)包,響應(yīng)的ACK持有接收窗口的狀態(tài)win=0,另外,ACK=66801表明接收端的接收緩沖區(qū)中積壓了66800字節(jié)的數(shù)據(jù)。
20. 從以上過(guò)程,我們應(yīng)該熟悉了滑動(dòng)窗口通告字段win所說(shuō)明的問(wèn)題,以及ACK確認(rèn)數(shù)據(jù)等等。現(xiàn)在可得出一個(gè)結(jié)論,接收端的接收緩存尺寸應(yīng)該是66800字節(jié)(此結(jié)論并非本篇主題)。
send()要發(fā)送的數(shù)據(jù)是70k,現(xiàn)在發(fā)出去了66800字節(jié),發(fā)送緩存中還有16k,應(yīng)用層剩余要拷貝進(jìn)內(nèi)核的數(shù)據(jù)量是N=70k-66800-16k。接收端仍處于sleep狀態(tài),無(wú)法recv()數(shù)據(jù),這將導(dǎo)致接收緩沖區(qū)一直處于積壓滿的狀態(tài),窗口會(huì)一直通告0(win=0)。發(fā)送端在這樣的狀態(tài)下徹底無(wú)法發(fā)送數(shù)據(jù)了,send()的剩余數(shù)據(jù)無(wú)法繼續(xù)拷貝進(jìn)內(nèi)核的發(fā)送緩沖區(qū),最終導(dǎo)致send()被阻塞在應(yīng)用層;
21. send()一直阻塞中。。。
圖4和send()的關(guān)系說(shuō)明完畢。
那什么時(shí)候send返回呢?有3種返回場(chǎng)景
隨著進(jìn)程不斷的用"recv(fd,buf,2048,0);"將數(shù)據(jù)從內(nèi)核的接收緩沖區(qū)拷貝至應(yīng)用層的buf,在使用win=0關(guān)閉接收窗口之后,現(xiàn)在接收緩沖區(qū)又逐漸恢復(fù)了緩存的能力,這個(gè)條件下,收端會(huì)主動(dòng)發(fā)送攜帶"win=n(n>0)"這樣的ACK包去通告發(fā)送端接收窗口已打開(kāi); 23. 發(fā)端收到攜帶"win=n(n>0)"這樣的ACK包之后,開(kāi)始繼續(xù)在窗口運(yùn)行的數(shù)據(jù)量范圍內(nèi)發(fā)送數(shù)據(jù)。發(fā)送緩沖區(qū)的數(shù)據(jù)被發(fā)出;
24. 收端繼續(xù)接收數(shù)據(jù),并用ACK確認(rèn)這些數(shù)據(jù);
25. 發(fā)端收到ACK,可以清理出一些發(fā)送緩沖區(qū)空間,應(yīng)用層send()的剩余數(shù)據(jù)又可以被不斷的拷貝進(jìn)內(nèi)核的發(fā)送緩沖區(qū);
26. 不斷重復(fù)以上發(fā)送過(guò)程;
27. send()的70k數(shù)據(jù)全部進(jìn)入內(nèi)核,send()成功返回。
場(chǎng)景2,我們繼續(xù)圖4這個(gè)例子,不過(guò)這兒開(kāi)始我們就跳出圖4所示的過(guò)程了
22. 收端進(jìn)程或者socket出現(xiàn)問(wèn)題,給發(fā)端發(fā)送一個(gè)RST,請(qǐng)參考博文《》;
23. 內(nèi)核收到RST,send返回-1。
場(chǎng)景3,和以上例子沒(méi)關(guān)系
連接上之后,馬上send(1k),這樣,發(fā)送的數(shù)據(jù)肯定可以一次拷貝進(jìn)入發(fā)送緩沖區(qū),send()拷貝完數(shù)據(jù)立即成功返回。
send()只是負(fù)責(zé)拷貝,拷貝完立即返回,不會(huì)等待發(fā)送和發(fā)送之后的ACK。如果socket出現(xiàn)問(wèn)題,RST包被反饋回來(lái)。在RST包返回之時(shí),如果send()還沒(méi)有把數(shù)據(jù)全部放入內(nèi)核或者發(fā)送出去,那么send()返回-1,errno被置錯(cuò)誤值;如果RST包返回之時(shí),send()已經(jīng)返回,那么RST導(dǎo)致的錯(cuò)誤會(huì)在下一次send()或者recv()調(diào)用的時(shí)候被立即返回。
場(chǎng)景3完全說(shuō)明send()只要完成拷貝就成功返回,如果發(fā)送數(shù)據(jù)的過(guò)程中出現(xiàn)各種錯(cuò)誤,下一次send()或者recv()調(diào)用的時(shí)候被立即返回。
2. send()和recv()沒(méi)有固定的對(duì)應(yīng)關(guān)系,不定數(shù)目的send()可以觸發(fā)不定數(shù)目的recv(),這話不專(zhuān)業(yè),但是還是必須說(shuō)一下,初學(xué)者容易疑惑;
3. 關(guān)鍵點(diǎn),send()只負(fù)責(zé)拷貝,拷貝到內(nèi)核就返回,我通篇在說(shuō)拷貝完返回,很多文章中說(shuō)send()在成功發(fā)送數(shù)據(jù)后返回,成功發(fā)送是說(shuō)發(fā)出去的東西被ACK確認(rèn)過(guò)。send()只拷貝,不會(huì)等ACK;
4. 此次send()調(diào)用所觸發(fā)的程序錯(cuò)誤,可能會(huì)在本次返回,也可能在下次調(diào)用網(wǎng)絡(luò)IO函數(shù)的時(shí)候被返回。
需要理解的3個(gè)概念
1. TCP socket的buffer
每個(gè)TCP socket在內(nèi)核中都有一個(gè)發(fā)送緩沖區(qū)和一個(gè)接收緩沖區(qū),TCP的全雙工的工作模式以及TCP的流量(擁塞)控制便是依賴(lài)于這兩個(gè)獨(dú)立的buffer以及buffer的填充狀態(tài)。接收緩沖區(qū)把數(shù)據(jù)緩存入內(nèi)核,應(yīng)用進(jìn)程一直沒(méi)有調(diào)用recv()進(jìn)行讀取的話,此數(shù)據(jù)會(huì)一直緩存在相應(yīng)socket的接收緩沖區(qū)內(nèi)。再啰嗦一點(diǎn),不管進(jìn)程是否調(diào)用recv()讀取socket,對(duì)端發(fā)來(lái)的數(shù)據(jù)都會(huì)經(jīng)由內(nèi)核接收并且緩存到socket的內(nèi)核接收緩沖區(qū)之中。recv()所做的工作,就是把內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝到應(yīng)用層用戶(hù)的buffer里面,并返回,僅此而已。進(jìn)程調(diào)用send()發(fā)送的數(shù)據(jù)的時(shí)候,最簡(jiǎn)單情況(也是一般情況),將數(shù)據(jù)拷貝進(jìn)入socket的內(nèi)核發(fā)送緩沖區(qū)之中,然后send便會(huì)在上層返回。換句話說(shuō),send()返回之時(shí),數(shù)據(jù)不一定會(huì)發(fā)送到對(duì)端去(和write寫(xiě)文件有點(diǎn)類(lèi)似),send()僅僅是把應(yīng)用層buffer的數(shù)據(jù)拷貝進(jìn)socket的內(nèi)核發(fā)送buffer中,發(fā)送是TCP的事情,和send其實(shí)沒(méi)有太大關(guān)系。接收緩沖區(qū)被TCP用來(lái)緩存網(wǎng)絡(luò)上來(lái)的數(shù)據(jù),一直保存到應(yīng)用進(jìn)程讀走為止。對(duì)于TCP,如果應(yīng)用進(jìn)程一直沒(méi)有讀取,接收緩沖區(qū)滿了之后,發(fā)生的動(dòng)作是:收端通知發(fā)端,接收窗口關(guān)閉(win=0)。這個(gè)便是滑動(dòng)窗口的實(shí)現(xiàn)。保證TCP套接口接收緩沖區(qū)不會(huì)溢出,從而保證了TCP是可靠傳輸。因?yàn)閷?duì)方不允許發(fā)出超過(guò)所通告窗口大小的數(shù)據(jù)。 這就是TCP的流量控制,如果對(duì)方無(wú)視窗口大小而發(fā)出了超過(guò)窗口大小的數(shù)據(jù),則接收方TCP將丟棄它。查看測(cè)試機(jī)的socket發(fā)送緩沖區(qū)大小,如圖1所示
圖1 第一個(gè)值是一個(gè)限制值,socket發(fā)送緩存區(qū)的最少字節(jié)數(shù);
第二個(gè)值是默認(rèn)值;
第三個(gè)值是一個(gè)限制值,socket發(fā)送緩存區(qū)的最大字節(jié)數(shù);
根據(jù)實(shí)際測(cè)試,發(fā)送緩沖區(qū)的尺寸在默認(rèn)情況下的全局設(shè)置是16384字節(jié),即16k。
在測(cè)試系統(tǒng)上,發(fā)送緩存默認(rèn)值是16k。
proc文件系統(tǒng)下的值和sysctl中的值都是全局值,應(yīng)用程序可根據(jù)需要在程序中使用setsockopt()對(duì)某個(gè)socket的發(fā)送緩沖區(qū)尺寸進(jìn)行單獨(dú)修改,詳見(jiàn)文章《TCP選項(xiàng)之SO_RCVBUF和SO_SNDBUF》,不過(guò)這都是題外話。
2. 接收窗口(滑動(dòng)窗口)
TCP連接建立之時(shí)的收端的初始接受窗口大小是14600,細(xì)節(jié)如圖2所示(129是收端,130是發(fā)端)圖2 接收窗口是TCP中的滑動(dòng)窗口,TCP的收端用這個(gè)接受窗口----win=14600,通知發(fā)端,我目前的接收能力是14600字節(jié)。
后續(xù)發(fā)送過(guò)程中,收端會(huì)不斷的用ACK(ACK的全部作用請(qǐng)參照博文《TCP之ACK發(fā)送情景》)通知發(fā)端自己的接收窗口的大小狀態(tài),如圖3,而發(fā)端發(fā)送數(shù)據(jù)的量,就根據(jù)這個(gè)接收窗口的大小來(lái)確定,發(fā)端不會(huì)發(fā)送超過(guò)收端接收能力的數(shù)據(jù)量。這樣就起到了一個(gè)流量控制的的作用。
圖3
圖3說(shuō)明
21,22兩個(gè)包都是收端發(fā)給發(fā)端的ACK包
第21個(gè)包,收端確認(rèn)收到的前7240個(gè)字節(jié)數(shù)據(jù),7241的意思是期望收到的包從7241號(hào)開(kāi)始,序號(hào)加了1.同時(shí),接收窗口從最初的14656(如圖2)經(jīng)過(guò)慢啟動(dòng)階段增加到了現(xiàn)在的29120。用來(lái)表明現(xiàn)在收端可以接收29120個(gè)字節(jié)的數(shù)據(jù),而發(fā)端看到這個(gè)窗口通告,在沒(méi)有收到新的ACK的時(shí)候,發(fā)端可以向收端發(fā)送29120字節(jié)這么多數(shù)據(jù)。
第22個(gè)包,收端確認(rèn)收到的前8688個(gè)字節(jié)數(shù)據(jù),并通告自己的接收窗口繼續(xù)增長(zhǎng)為32000這么大。
3. 單個(gè)TCP的負(fù)載量和MSS的關(guān)系
MSS在以太網(wǎng)上通常大小是1460字節(jié),而我們?cè)诤罄m(xù)發(fā)送過(guò)程中的單個(gè)TCP包的最大數(shù)據(jù)承載量是1448字節(jié),這二者的關(guān)系可以參考博文《TCP之1460MSS和1448負(fù)載》。實(shí)例詳解send()
實(shí)例功能說(shuō)明:接收端129作為客戶(hù)端去連接發(fā)送端130,連接上之后并不調(diào)用recv()接收,而是sleep(1000),把進(jìn)程暫停下來(lái),不讓進(jìn)程接收數(shù)據(jù)。內(nèi)核會(huì)緩存數(shù)據(jù)至接收緩沖區(qū)。發(fā)送端作為服務(wù)器接收TCP請(qǐng)求之后,立即用ret = send(sock,buf,70k,0);這個(gè)C語(yǔ)句,向接收端發(fā)送70k數(shù)據(jù)。我們現(xiàn)在來(lái)觀察這個(gè)過(guò)程。看看究竟發(fā)生了些什么事。wireshark抓包截圖如下圖4
圖4
圖4說(shuō)明,包序號(hào)等同于時(shí)序
1. 客戶(hù)端sleep在recv()之前,目的是為了把數(shù)據(jù)壓入接收緩沖區(qū)。服務(wù)端調(diào)用"ret = send(sock,buf,70k,0);"這個(gè)C語(yǔ)句,向接收端發(fā)送70k數(shù)據(jù)。由于發(fā)送緩沖區(qū)大小16k,send()無(wú)法將70k數(shù)據(jù)全部拷貝進(jìn)發(fā)送緩沖區(qū),故先拷貝16k進(jìn)入發(fā)送緩沖區(qū),下層發(fā)送緩沖區(qū)中有數(shù)據(jù)要發(fā)送,內(nèi)核開(kāi)始發(fā)送。上層send()在應(yīng)用層處于阻塞狀態(tài);
2. 11號(hào)TCP包,發(fā)端從這兒開(kāi)始向收端發(fā)送1448個(gè)字節(jié)的數(shù)據(jù);
3. 12號(hào)TCP包,發(fā)端沒(méi)有收到之前發(fā)送的1448個(gè)數(shù)據(jù)的ACK包,仍然繼續(xù)向收端發(fā)送1448個(gè)字節(jié)的數(shù)據(jù);
4. 13號(hào)TCP包,收端向發(fā)端發(fā)送1448字節(jié)的確認(rèn)包,表明收端成功接收總共1448個(gè)字節(jié)。此時(shí)收端并未調(diào)用recv()讀取,目前發(fā)送緩沖區(qū)中被壓入1448字節(jié)。由于處于慢啟動(dòng)狀態(tài),win接收窗口持續(xù)增大,表明接受能力在增加,吞吐量持續(xù)上升;
5. 14號(hào)TCP包,收端向發(fā)端發(fā)送2896字節(jié)的確認(rèn)包,表明收端成功接收總共2896個(gè)字節(jié)。此時(shí)收端并未調(diào)用recv()讀取,目前發(fā)送緩沖區(qū)中被壓入2896字節(jié)。由于處于慢啟動(dòng)狀態(tài),win接收窗口持續(xù)增大,表明接受能力在增加,吞吐量持續(xù)上升;
6. 15號(hào)TCP包,發(fā)端繼續(xù)向收端發(fā)送1448個(gè)字節(jié)的數(shù)據(jù);
7. 16號(hào)TCP包,收端向發(fā)端發(fā)送4344字節(jié)的確認(rèn)包,表明收端成功接收總共4344個(gè)字節(jié)。此時(shí)收端并未調(diào)用recv()讀取,目前發(fā)送緩沖區(qū)中被壓入4344字節(jié)。由于處于慢啟動(dòng)狀態(tài),win接收窗口持續(xù)增大,表明接受能力在增加,吞吐量持續(xù)上升;
8. 從這兒開(kāi)始,我略去很多包,過(guò)程類(lèi)似上面過(guò)程。同時(shí),由于不斷的發(fā)送出去的數(shù)據(jù)被收端用ACK確認(rèn),發(fā)送緩沖區(qū)的空間被逐漸騰出空地,send()內(nèi)部不斷的把應(yīng)用層buf中的數(shù)據(jù)向發(fā)送緩沖區(qū)拷貝,從而不斷的發(fā)送,過(guò)程重復(fù)。70k數(shù)據(jù)并沒(méi)有被完全送入內(nèi)核,send()不管是否發(fā)送出去,send不管發(fā)送出去的是否被確認(rèn),send()只關(guān)心buf中的數(shù)據(jù)有沒(méi)有被全部送往發(fā)送緩沖區(qū)。如果buf中的數(shù)據(jù)沒(méi)有被全部送往發(fā)送緩沖區(qū),send()在應(yīng)用層阻塞,負(fù)責(zé)等待發(fā)送緩沖區(qū)中有空余空間的時(shí)候,逐步拷貝buf中的數(shù)據(jù);如果buf中的數(shù)據(jù)被全部拷入發(fā)送緩沖區(qū),send()立即返回。
9. 經(jīng)過(guò)慢啟動(dòng)階段接收窗口增大到穩(wěn)定階段,TCP吞吐量升高到穩(wěn)定階段,收端一直處于sleep狀態(tài),沒(méi)有調(diào)用recv()把內(nèi)核中接收緩沖區(qū)中的數(shù)據(jù)拷貝到應(yīng)用層去,此時(shí)收端的接收緩沖區(qū)中被壓入大量數(shù)據(jù);
10. 66號(hào)、67號(hào)TCP數(shù)據(jù)包,發(fā)端繼續(xù)向收端發(fā)送數(shù)據(jù);
11. 68號(hào)TCP數(shù)據(jù)包,收端發(fā)送ACK包確認(rèn)接收到的數(shù)據(jù),ACK=62265表明收端已經(jīng)收到62265字節(jié)的數(shù)據(jù),這些數(shù)據(jù)目前被壓在收端的接收緩沖區(qū)中。win=3456,比較之前的16號(hào)TCP包的win=23296,表明收端的窗口已經(jīng)處于收縮狀態(tài),收端的接收緩沖區(qū)中的數(shù)據(jù)遲遲未被應(yīng)用層讀走,導(dǎo)致接收緩沖區(qū)空間吃緊,故收縮窗口,控制發(fā)送端的發(fā)送量,進(jìn)行流量控制;
12. 69號(hào)、70號(hào)TCP數(shù)據(jù)包,發(fā)端在接收窗口允許的數(shù)據(jù)量的范圍內(nèi),繼續(xù)向收端發(fā)送2段1448字節(jié)長(zhǎng)度的數(shù)據(jù);
13. 71號(hào)TCP數(shù)據(jù)包,至此,收端已經(jīng)成功接收65160字節(jié)的數(shù)據(jù),全部被壓在接收緩沖區(qū)之中,接收窗口繼續(xù)收縮,尺寸為1600字節(jié);
14. 72號(hào)TCP數(shù)據(jù)包,發(fā)端在接收窗口允許的數(shù)據(jù)量的范圍內(nèi),繼續(xù)向收端發(fā)送1448字節(jié)長(zhǎng)度的數(shù)據(jù);
15. 73號(hào)TCP數(shù)據(jù)包,至此,收端已經(jīng)成功接收66609字節(jié)的數(shù)據(jù),全部被壓在接收緩沖區(qū)之中,接收窗口繼續(xù)收縮,尺寸為192字節(jié)。
16. 74號(hào)TCP數(shù)據(jù)包,和我們這個(gè)例子沒(méi)有關(guān)系,是別的應(yīng)用發(fā)送的包;
17. 75號(hào)TCP數(shù)據(jù)包,發(fā)端在接收窗口允許的數(shù)據(jù)量的范圍內(nèi),向收端發(fā)送192字節(jié)長(zhǎng)度的數(shù)據(jù);
18. 76號(hào)TCP數(shù)據(jù)包,至此,收端已經(jīng)成功接收66609字節(jié)的數(shù)據(jù),全部被壓在接收緩沖區(qū)之中,win=0接收窗口關(guān)閉,接收緩沖區(qū)滿,無(wú)法再接收任何數(shù)據(jù);
19. 77號(hào)、78號(hào)、79號(hào)TCP數(shù)據(jù)包,由keepalive觸發(fā)的數(shù)據(jù)包,響應(yīng)的ACK持有接收窗口的狀態(tài)win=0,另外,ACK=66801表明接收端的接收緩沖區(qū)中積壓了66800字節(jié)的數(shù)據(jù)。
20. 從以上過(guò)程,我們應(yīng)該熟悉了滑動(dòng)窗口通告字段win所說(shuō)明的問(wèn)題,以及ACK確認(rèn)數(shù)據(jù)等等。現(xiàn)在可得出一個(gè)結(jié)論,接收端的接收緩存尺寸應(yīng)該是66800字節(jié)(此結(jié)論并非本篇主題)。
send()要發(fā)送的數(shù)據(jù)是70k,現(xiàn)在發(fā)出去了66800字節(jié),發(fā)送緩存中還有16k,應(yīng)用層剩余要拷貝進(jìn)內(nèi)核的數(shù)據(jù)量是N=70k-66800-16k。接收端仍處于sleep狀態(tài),無(wú)法recv()數(shù)據(jù),這將導(dǎo)致接收緩沖區(qū)一直處于積壓滿的狀態(tài),窗口會(huì)一直通告0(win=0)。發(fā)送端在這樣的狀態(tài)下徹底無(wú)法發(fā)送數(shù)據(jù)了,send()的剩余數(shù)據(jù)無(wú)法繼續(xù)拷貝進(jìn)內(nèi)核的發(fā)送緩沖區(qū),最終導(dǎo)致send()被阻塞在應(yīng)用層;
21. send()一直阻塞中。。。
圖4和send()的關(guān)系說(shuō)明完畢。
那什么時(shí)候send返回呢?有3種返回場(chǎng)景
send()返回場(chǎng)景
場(chǎng)景1,我們繼續(xù)圖4這個(gè)例子,不過(guò)這兒開(kāi)始我們就跳出圖4所示的過(guò)程了
22. 接收端sleep(1000)到時(shí)間了,進(jìn)程被喚醒,代碼片段如圖5
圖5隨著進(jìn)程不斷的用"recv(fd,buf,2048,0);"將數(shù)據(jù)從內(nèi)核的接收緩沖區(qū)拷貝至應(yīng)用層的buf,在使用win=0關(guān)閉接收窗口之后,現(xiàn)在接收緩沖區(qū)又逐漸恢復(fù)了緩存的能力,這個(gè)條件下,收端會(huì)主動(dòng)發(fā)送攜帶"win=n(n>0)"這樣的ACK包去通告發(fā)送端接收窗口已打開(kāi); 23. 發(fā)端收到攜帶"win=n(n>0)"這樣的ACK包之后,開(kāi)始繼續(xù)在窗口運(yùn)行的數(shù)據(jù)量范圍內(nèi)發(fā)送數(shù)據(jù)。發(fā)送緩沖區(qū)的數(shù)據(jù)被發(fā)出;
24. 收端繼續(xù)接收數(shù)據(jù),并用ACK確認(rèn)這些數(shù)據(jù);
25. 發(fā)端收到ACK,可以清理出一些發(fā)送緩沖區(qū)空間,應(yīng)用層send()的剩余數(shù)據(jù)又可以被不斷的拷貝進(jìn)內(nèi)核的發(fā)送緩沖區(qū);
26. 不斷重復(fù)以上發(fā)送過(guò)程;
27. send()的70k數(shù)據(jù)全部進(jìn)入內(nèi)核,send()成功返回。
場(chǎng)景2,我們繼續(xù)圖4這個(gè)例子,不過(guò)這兒開(kāi)始我們就跳出圖4所示的過(guò)程了
22. 收端進(jìn)程或者socket出現(xiàn)問(wèn)題,給發(fā)端發(fā)送一個(gè)RST,請(qǐng)參考博文《》;
23. 內(nèi)核收到RST,send返回-1。
場(chǎng)景3,和以上例子沒(méi)關(guān)系
連接上之后,馬上send(1k),這樣,發(fā)送的數(shù)據(jù)肯定可以一次拷貝進(jìn)入發(fā)送緩沖區(qū),send()拷貝完數(shù)據(jù)立即成功返回。
send()發(fā)送結(jié)論
其實(shí)場(chǎng)景1和場(chǎng)景2說(shuō)明一個(gè)問(wèn)題send()只是負(fù)責(zé)拷貝,拷貝完立即返回,不會(huì)等待發(fā)送和發(fā)送之后的ACK。如果socket出現(xiàn)問(wèn)題,RST包被反饋回來(lái)。在RST包返回之時(shí),如果send()還沒(méi)有把數(shù)據(jù)全部放入內(nèi)核或者發(fā)送出去,那么send()返回-1,errno被置錯(cuò)誤值;如果RST包返回之時(shí),send()已經(jīng)返回,那么RST導(dǎo)致的錯(cuò)誤會(huì)在下一次send()或者recv()調(diào)用的時(shí)候被立即返回。
場(chǎng)景3完全說(shuō)明send()只要完成拷貝就成功返回,如果發(fā)送數(shù)據(jù)的過(guò)程中出現(xiàn)各種錯(cuò)誤,下一次send()或者recv()調(diào)用的時(shí)候被立即返回。
概念上容易疑惑的地方
1. TCP協(xié)議本身是為了保證可靠傳輸,并不等于應(yīng)用程序用tcp發(fā)送數(shù)據(jù)就一定是可靠的,必須要容錯(cuò);2. send()和recv()沒(méi)有固定的對(duì)應(yīng)關(guān)系,不定數(shù)目的send()可以觸發(fā)不定數(shù)目的recv(),這話不專(zhuān)業(yè),但是還是必須說(shuō)一下,初學(xué)者容易疑惑;
3. 關(guān)鍵點(diǎn),send()只負(fù)責(zé)拷貝,拷貝到內(nèi)核就返回,我通篇在說(shuō)拷貝完返回,很多文章中說(shuō)send()在成功發(fā)送數(shù)據(jù)后返回,成功發(fā)送是說(shuō)發(fā)出去的東西被ACK確認(rèn)過(guò)。send()只拷貝,不會(huì)等ACK;
4. 此次send()調(diào)用所觸發(fā)的程序錯(cuò)誤,可能會(huì)在本次返回,也可能在下次調(diào)用網(wǎng)絡(luò)IO函數(shù)的時(shí)候被返回。
總結(jié)
以上是生活随笔為你收集整理的TCP之深入浅出send和recv的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: IO之阻塞与非阻塞比较
- 下一篇: linux环境变量显示、添加、删除