Socket中send()函數和recv()函數詳解


1、send函數

int send( SOCKET s, const char FAR *buf, int len, int flags );

不論是客戶還是服務器應用程序都用send函數來向TCP連接的另一端發送數據。

客戶程序一般用send函數向服務器發送請求,而服務器則通常用send函數來向客戶程序發送應答。

(1)第一個參數指定發送端套接字描述符;

(2)第二個參數指明一個存放應用程序要發送數據的緩沖區;

(3)第三個參數指明實際要發送的數據的字節數;

(4)第四個參數一般置0。

這里只描述同步Socket的send函數的執行流程。當調用該函數時,send先比較待發送數據的長度len和套接字s的發送緩沖的長度, 如果len大於s的發送緩沖區的長度,該函數返回SOCKET_ERROR;如果len小於或者等於s的發送緩沖區的長度,那么send先檢查協議是否正在發送s的發送緩沖中的數據,如果是就等待協議把數據發送完,如果協議還沒有開始發送s的發送緩沖中的數據或者s的發送緩沖中沒有數據,那么send就比較s的發送緩沖區的剩余空間和len,如果len大於剩余空間大小send就一直等待協議把s的發送緩沖中的數據發送完,如果len小於剩余空間大小send就僅僅把buf中的數據copy到剩余空間里(注意並不是send把s的發送緩沖中的數據傳到連接的另一端的,而是協議的,send僅僅是把buf中的數據copy到s的發送緩沖區的剩余空間里)。

如果send函數copy數據成功,就返回實際copy的字節數,如果send在copy數據時出現錯誤,那么send就返回SOCKET_ERROR;如果send在等待協議傳送數據時網絡斷開的話,那么send函數也返回SOCKET_ERROR。

要注意send函數把buf中的數據成功copy到s的發送緩沖的剩余空間里后它就返回了,但是此時這些數據並不一定馬上被傳到連接的另一端。如果協議在后續的傳送過程中出現網絡錯誤的話,那么下一個Socket函數就會返回SOCKET_ERROR。(每一個除send外的Socket函數在執行的最開始總要先等待套接字的發送緩沖中的數據被協議傳送完畢才能繼續,如果在等待時出現網絡錯誤,那么該Socket函數就返回SOCKET_ERROR)。

注意:在Unix系統下,如果send在等待協議傳送數據時網絡斷開的話,調用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。

2、recv函數

int recv( SOCKET s, char FAR *buf, int len, int flags );

不論是客戶還是服務器應用程序都用recv函數從TCP連接的另一端接收數據。

(1)第一個參數指定接收端套接字描述符;

(2)第二個參數指明一個緩沖區,該緩沖區用來存放recv函數接收到的數據;

(3)第三個參數指明buf的長度;

(4)第四個參數一般置0。

這里只描述同步Socket的recv函數的執行流程。當應用程序調用recv函數時,recv先等待s的發送緩沖中的數據被協議傳送完畢,如果協議在傳送s的發送緩沖中的數據時出現網絡錯誤,那么recv函數返回SOCKET_ERROR,如果s的發送緩沖中沒有數據或者數據被協議成功發送完畢后,recv先檢查套接字s的接收緩沖區,如果s接收緩沖區中沒有數據或者協議正在接收數據,那么recv就一直等待,只到協議把數據接收完畢。當協議把數據接收完畢,recv函數就把s的接收緩沖中的數據copy到buf中(注意協議接收到的數據可能大於buf的長度,所以在這種情況下要調用幾次recv函數才能把s的接收緩沖中的數據copy完。recv函數僅僅是copy數據,真正的接收數據是協議來完成的),recv函數返回其實際copy的字節數。如果recv在copy時出錯,那么它返回SOCKET_ERROR;如果recv函數在等待協議接收數據時網絡中斷了,那么它返回0。

注意:在Unix系統下,如果recv函數在等待協議接收數據時網絡斷開了,那么調用recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。

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

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

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

3、緩存大小查看即更改

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的值來分配內存的.

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

[cpp] view plain copy
  1. socklen_t sendbuflen = 0;  
  2. socklen_t len = sizeof(sendbuflen);  
  3. getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);  
  4. printf("default,sendbuf:%d\n", sendbuflen);  
  5.   
  6. sendbuflen = 10240;  
  7. setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len);  
  8. getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);  
  9. printf("now,sendbuf:%d\n", sendbuflen);  
需要注意的是,雖然將發送緩存設置成了10k,但實際上,協議棧會將其擴大1倍,設為20k.

4、實例分析

在實際應用中,如果發送端是非阻塞發送,由於網絡的阻塞或者接收端處理過慢,通常出現的情況是,發送應用程序看起來發送了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

5、send函數特點及相關問題收藏

在send函數的help里面看到

The successful completion of a send call does not indicate that the data was successfully delivered.

send成功完成並不代表數據已經成功送達。

If no buffer space is available within the transport system to hold the data to be transmitted, send will block unless the socket has been placed in nonblocking mode.

如果沒有緩沖存儲待發送的數據,send會阻塞直到socket被設置為非阻塞模式,

On nonblocking stream-oriented sockets, the number of bytes written can be between 1 and the requested length, depending on buffer availability on both client and server machines.

在非阻塞流模式socket中,寫入的字節可以是1到需要的長度,依賴於客戶端和服務器的緩沖。

The select or WSAEventSelect function can be used to determine when it is possible to send more data.

select 或WSAEventSelect 函數可以用於決定什么時候可以繼續發送數據

阻塞模式下send並不是說直到你發送數據到對方機器才返回的意思,它是說把你要發送的數據放入發送緩沖后,就直接返回。而不是阻塞時,如發送緩沖區沒有了,他就直接返回,而阻塞時會等待發送緩沖區有空間。

先看看在阻塞模式下send的表現吧(注意緩沖區的大小,我這里是16k)

(1)發送一個小於16k的數據,send馬上就返回了

也就說是,send把待發送的數據放入發送緩沖馬上就返回了,前提是發送的數據字節數小於緩沖大小

(2)發送一個大於16k的數據,send沒有馬上返回,阻塞了一下

send一定要把所有數據放入緩沖區才會返回,假設我們發32k的數據,當send返回的時候,有16k數據已經到達另一端,剩下16k還在緩沖里面沒有發出去

在阻塞模式下

如果發送成功,返回的nBytes一定等於len

nBytes = send(m_socket,buf,len,0);

也就是在上面代碼中那個發送循環其實是沒有必要的

再看看在非阻塞模式下的情況吧

(1)發送一個小於16k的數據,send馬上返回了,而且返回的字節長度是等於發送的字節長度的,情況和阻塞模式是向相同的

(2)發送一個大於16k的數據,send也是馬上就返回了,返回的nByte小於待發送的字節數

來模擬一下實際情況,假設我們有32k的數據要發送,

第一次send,返回16384字節(16k),也就是填滿了緩沖區

第二次send,在這之前sleep了1000毫秒,這段時間可能已經有5000字節從緩沖區發出,到達另外一端了,於是緩沖區空了5000字節出來,相應的,這次返回的是5000,表示新放入了5000字節到緩沖區

第三次send ,和第二次相同,又放了6000字節

最后一次send,放入了剩下的字節數,這個時候緩沖還是有數據的。

再發送大於16k數據的情況下,那個send發送循環就是必須的了

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com