下面小編就為大家?guī)硪黄獙end(),recv()函數(shù)的全面理解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
int send( SOCKET s, const char FAR *buf, int len, int flags );
不論是客戶還是服務(wù)器應(yīng)用程序都用send函數(shù)來向TCP連接的另一端發(fā)送數(shù)據(jù)。
客戶程序一般用send函數(shù)向服務(wù)器發(fā)送請求,而服務(wù)器則通常用send函數(shù)來向客戶程序發(fā)送應(yīng)答。
該函數(shù)的第一個參數(shù)指定發(fā)送端套接字描述符;
第二個參數(shù)指明一個存放應(yīng)用程序要發(fā)送數(shù)據(jù)的緩沖區(qū);
第三個參數(shù)指明實際要發(fā)送的數(shù)據(jù)的字節(jié)數(shù);
第四個參數(shù)一般置0。
這里只描述同步Socket的send函數(shù)的執(zhí)行流程。當(dāng)調(diào)用該函數(shù)時,send先比較待發(fā)送數(shù)據(jù)的長度len和套接字s的發(fā)送緩沖的 長度, 如果len大于s的發(fā)送緩沖區(qū)的長度,該函數(shù)返回SOCKET_ERROR;如果len小于或者等于s的發(fā)送緩沖區(qū)的長度,那么send先檢查協(xié)議 是否正在發(fā)送s的發(fā)送緩沖中的數(shù)據(jù),如果是就等待協(xié)議把數(shù)據(jù)發(fā)送完,如果協(xié)議還沒有開始發(fā)送s的發(fā)送緩沖中的數(shù)據(jù)或者s的發(fā)送緩沖中沒有數(shù)據(jù),那么 send就比較s的發(fā)送緩沖區(qū)的剩余空間和len,如果len大于剩余空間大小send就一直等待協(xié)議把s的發(fā)送緩沖中的數(shù)據(jù)發(fā)送完,如果len小于剩余 空間大小send就僅僅把buf中的數(shù)據(jù)copy到剩余空間里(注意并不是send把s的發(fā)送緩沖中的數(shù)據(jù)傳到連接的另一端的,而是協(xié)議傳的,send僅僅是把buf中的數(shù)據(jù)copy到s的發(fā)送緩沖區(qū)的剩余空間里)。如果send函數(shù)copy數(shù)據(jù)成功,就返回實際copy的字節(jié)數(shù),如果send在copy數(shù)據(jù)時出現(xiàn)錯誤,那么send就返回SOCKET_ERROR;如果send在等待協(xié)議傳送數(shù)據(jù)時網(wǎng)絡(luò)斷開的話,那么send函數(shù)也返回SOCKET_ERROR。
要注意send函數(shù)把buf中的數(shù)據(jù)成功copy到s的發(fā)送緩沖的剩余空間里后它就返回了,但是此時這些數(shù)據(jù)并不一定馬上被傳到連接的另一端。如 果協(xié)議在后續(xù)的傳送過程中出現(xiàn)網(wǎng)絡(luò)錯誤的話,那么下一個Socket函數(shù)就會返回SOCKET_ERROR。(每一個除send外的Socket函數(shù)在執(zhí) 行的最開始總要先等待套接字的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢才能繼續(xù),如果在等待時出現(xiàn)網(wǎng)絡(luò)錯誤,那么該Socket函數(shù)就返回 SOCKET_ERROR)
注意:在Unix系統(tǒng)下,如果send在等待協(xié)議傳送數(shù)據(jù)時網(wǎng)絡(luò)斷開的話,調(diào)用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
recv函數(shù)
int recv( SOCKET s, char FAR *buf, int len, int flags );
不論是客戶還是服務(wù)器應(yīng)用程序都用recv函數(shù)從TCP連接的另一端接收數(shù)據(jù)。
該函數(shù)的第一個參數(shù)指定接收端套接字描述符;
第二個參數(shù)指明一個緩沖區(qū),該緩沖區(qū)用來存放recv函數(shù)接收到的數(shù)據(jù);
第三個參數(shù)指明buf的長度;
第四個參數(shù)一般置0。
這里只描述同步Socket的recv函數(shù)的執(zhí)行流程。當(dāng)應(yīng)用程序調(diào)用recv函數(shù)時,recv先等待s的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢,如果協(xié)議在傳送s的發(fā)送緩沖中的數(shù)據(jù)時出現(xiàn)網(wǎng)絡(luò)錯誤,那么recv函數(shù)返回SOCKET_ERROR,如果s的發(fā)送緩沖中沒有數(shù) 據(jù)或者數(shù)據(jù)被協(xié)議成功發(fā)送完畢后,recv先檢查套接字s的接收緩沖區(qū),如果s接收緩沖區(qū)中沒有數(shù)據(jù)或者協(xié)議正在接收數(shù)據(jù),那么recv就一直等待,只到 協(xié)議把數(shù)據(jù)接收完畢。當(dāng)協(xié)議把數(shù)據(jù)接收完畢,recv函數(shù)就把s的接收緩沖中的數(shù)據(jù)copy到buf中(注意協(xié)議接收到的數(shù)據(jù)可能大于buf的長度,所以 在這種情況下要調(diào)用幾次recv函數(shù)才能把s的接收緩沖中的數(shù)據(jù)copy完。recv函數(shù)僅僅是copy數(shù)據(jù),真正的接收數(shù)據(jù)是協(xié)議來完成的),recv函數(shù)返回其實際copy的字節(jié)數(shù)。如果recv在copy時出錯,那么它返回SOCKET_ERROR;如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時網(wǎng)絡(luò)中斷了,那么它返回0。
注意:在Unix系統(tǒng)下,如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時網(wǎng)絡(luò)斷開了,那么調(diào)用recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
cp協(xié)議本身是可靠的,并不等于應(yīng)用程序用tcp發(fā)送數(shù)據(jù)就一定是可靠的.不管是否阻塞,send發(fā)送的大小,并不代表對端recv到多少的數(shù)據(jù).
在阻塞模式下, send函數(shù)的過程是將應(yīng)用程序請求發(fā)送的數(shù)據(jù)拷貝到發(fā)送緩存中發(fā)送并得到確認后再返回.但由于發(fā)送緩存的存在,表現(xiàn)為:如果發(fā)送緩存大小比請求發(fā)送的大小要大,那么send函數(shù)立即返回,同時向網(wǎng)絡(luò)中發(fā)送數(shù)據(jù);否則,send向網(wǎng)絡(luò)發(fā)送緩存中不能容納的那部分數(shù)據(jù),并等待對端確認后再返回(接收端只要將數(shù)據(jù)收到接收緩存中,就會確認,并不一定要等待應(yīng)用程序調(diào)用recv);
在非阻塞模式下,send函數(shù)的過程僅僅是將數(shù)據(jù)拷貝到協(xié)議棧的緩存區(qū)而已,如果緩存區(qū)可用空間不夠,則盡能力的拷貝,返回成功拷貝的大小;如緩存區(qū)可用空間為0,則返回-1,同時設(shè)置errno為EAGAIN.
linux下可用sysctl -a | grep net.ipv4.tcp_wmem查看系統(tǒng)默認的發(fā)送緩存大小:
net.ipv4.tcp_wmem = 4096 16384 81920
這有三個值,第一個值是socket的發(fā)送緩存區(qū)分配的最少字節(jié)數(shù),第二個值是默認值(該值會被net.core.wmem_default覆蓋),緩存區(qū)在系統(tǒng)負載不重的情況下可以增長到這個值,第三個值是發(fā)送緩存區(qū)空間的最大字節(jié)數(shù)(該值會被net.core.wmem_max覆蓋).
根據(jù)實際測試,如果手工更改了net.ipv4.tcp_wmem的值,則會按更改的值來運行,否則在默認情況下,協(xié)議棧通常是按net.core.wmem_default和net.core.wmem_max的值來分配內(nèi)存的.
應(yīng)用程序應(yīng)該根據(jù)應(yīng)用的特性在程序中更改發(fā)送緩存大小:
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);
需要注意的是,雖然將發(fā)送緩存設(shè)置成了10k,但實際上,協(xié)議棧會將其擴大1倍,設(shè)為20k.
-------------------實例分析----------------------
在實際應(yīng)用中,如果發(fā)送端是非阻塞發(fā)送,由于網(wǎng)絡(luò)的阻塞或者接收端處理過慢,通常出現(xiàn)的情況是,發(fā)送應(yīng)用程序看起來發(fā)送了10k的數(shù)據(jù),但是只發(fā)送了2k到對端緩存中,還有8k在本機緩存中(未發(fā)送或者未得到接收端的確認).那么此時,接收應(yīng)用程序能夠收到的數(shù)據(jù)為2k.假如接收應(yīng)用程序調(diào)用recv函數(shù)獲取了1k的數(shù)據(jù)在處理,在這個瞬間,發(fā)生了以下情況之一,雙方表現(xiàn)為:
A. 發(fā)送應(yīng)用程序認為send完了10k數(shù)據(jù),關(guān)閉了socket:
發(fā)送主機作為tcp的主動關(guān)閉者,連接將處于FIN_WAIT1的半關(guān)閉狀態(tài)(等待對方的ack),并且,發(fā)送緩存中的8k數(shù)據(jù)并不清除,依然會發(fā)送給對端.如果接收應(yīng)用程序依然在recv,那么它會收到余下的8k數(shù)據(jù)(這個前題是,接收端會在發(fā)送端FIN_WAIT1狀態(tài)超時前收到余下的8k數(shù)據(jù).), 然后得到一個對端socket被關(guān)閉的消息(recv返回0).這時,應(yīng)該進行關(guān)閉.
B. 發(fā)送應(yīng)用程序再次調(diào)用send發(fā)送8k的數(shù)據(jù):
假 如發(fā)送緩存的空間為20k,那么發(fā)送緩存可用空間為20-8=12k,大于請求發(fā)送的8k,所以send函數(shù)將數(shù)據(jù)做拷貝后,并立即返回8192;
假如發(fā)送緩存的空間為12k,那么此時發(fā)送緩存可用空間還有12-8=4k,send()會返回4096,應(yīng)用程序發(fā)現(xiàn)返回的值小于請求發(fā)送的大小值后,可以認為緩存區(qū)已滿,這時必須阻塞(或通過select等待下一次socket可寫的信號),如果應(yīng)用程序不理會,立即再次調(diào)用send,那么會得到-1的值, 在linux下表現(xiàn)為errno=EAGAIN.
C. 接收應(yīng)用程序在處理完1k數(shù)據(jù)后,關(guān)閉了socket:
接收主機作為主動關(guān)閉者,連接將處于FIN_WAIT1的半關(guān)閉狀態(tài)(等待對方的ack).然后,發(fā)送應(yīng)用程序會收到socket可讀的信號(通常是 select調(diào)用返回socket可讀),但在讀取時會發(fā)現(xiàn)recv函數(shù)返回0,這時應(yīng)該調(diào)用close函數(shù)來關(guān)閉socket(發(fā)送給對方ack);
如果發(fā)送應(yīng)用程序沒有處理這個可讀的信號,而是在send,那么這要分兩種情況來考慮,假如是在發(fā)送端收到RST標(biāo)志之后調(diào)用send,send將返回 -1,同時errno設(shè)為ECONNRESET表示對端網(wǎng)絡(luò)已斷開,但是,也有說法是進程會收到SIGPIPE信號,該信號的默認響應(yīng)動作是退出進程,如果忽略該信號,那么send是返回-1,errno為EPIPE(未證實);如果是在發(fā)送端收到RST標(biāo)志之前,則send像往常一樣工作;
以上說的是非阻塞的send情況,假如send是阻塞調(diào)用,并且正好處于阻塞時(例如一次性發(fā)送一個巨大的buf,超出了發(fā)送緩存),對端socket關(guān)閉,那么send將返回成功發(fā)送的字節(jié)數(shù),如果再次調(diào)用send,那么會同上一樣.
D. 交換機或路由器的網(wǎng)絡(luò)斷開:
接收應(yīng)用程序在處理完已收到的1k數(shù)據(jù)后,會繼續(xù)從緩存區(qū)讀取余下的1k數(shù)據(jù),然后就表現(xiàn)為無數(shù)據(jù)可讀的現(xiàn)象,這種情況需要應(yīng)用程序來處理超時.一般做法是設(shè)定一個select等待的最大時間,如果超出這個時間依然沒有數(shù)據(jù)可讀,則認為socket已不可用.
發(fā)送應(yīng)用程序會不斷的將余下的數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上,但始終得不到確認,所以緩存區(qū)的可用空間持續(xù)為0,這種情況也需要應(yīng)用程序來處理.
如果不由應(yīng)用程序來處理這種情況超時的情況,也可以通過tcp協(xié)議本身來處理,具體可以查看sysctl項中的:
net.ipv4.tcp_keepalive_intvl
net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time
send函數(shù)特點及相關(guān)問題收藏
在send函數(shù)的help里面看到
The successful completion of a send call does not indicate that the data was successfully delivered.
send成功完成并不代表數(shù)據(jù)已經(jīng)成功送達。
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.
如果沒有緩沖存儲待發(fā)送的數(shù)據(jù),send會阻塞直到socket被設(shè)置為非阻塞模式,
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中,寫入的字節(jié)可以是1到需要的長度,依賴于客戶端和服務(wù)器的緩沖。
The select or WSAEventSelect function can be used to determine when it is possible to send more data.
select 或 WSAEventSelect 函數(shù)可以用于決定什么時候可以繼續(xù)發(fā)送數(shù)據(jù)
阻塞模式下send并不是說直到你發(fā)送數(shù)據(jù)到對方機器才返回的意思,它是說把你要發(fā)送的數(shù)據(jù)放入發(fā)送緩沖后,就直接返回。而不是阻塞時,如發(fā)送緩沖區(qū)沒有了,他就直接返回,而阻塞時會等待發(fā)送緩沖區(qū)有空間。
先看看在阻塞模式下send的表現(xiàn)吧(注意緩沖區(qū)的大小,我這里是16k)
1,發(fā)送一個小于16k的數(shù)據(jù),send馬上就返回了
也就說是,send把待發(fā)送的數(shù)據(jù)放入發(fā)送緩沖馬上就返回了,前提是發(fā)送的數(shù)據(jù)字節(jié)數(shù)小于緩沖大小
2,發(fā)送一個大于16k的數(shù)據(jù),send沒有馬上返回,阻塞了一下
send一定要把所有數(shù)據(jù)放入緩沖區(qū)才會返回,假設(shè)我們發(fā)32k的數(shù)據(jù),當(dāng)send返回的時候,有16k數(shù)據(jù)已經(jīng)到達另一端,剩下16k還在緩沖里面沒有發(fā)出去
在阻塞模式下
如果發(fā)送成功,返回的nBytes一定等于len
nBytes = send(m_socket,buf,len,0);
也就是在上面代碼中那個發(fā)送循環(huán)其實是沒有必要的
再看看在非阻塞模式下的情況吧
1,發(fā)送一個小于16k的數(shù)據(jù),send馬上返回了,而且返回的字節(jié)長度是等于發(fā)送的字節(jié)長度的,情況和阻塞模式是向相同的
2,發(fā)送一個大于16k的數(shù)據(jù),send也是馬上就返回了,返回的nByte小于待發(fā)送的字節(jié)數(shù)
來模擬一下實際情況,假設(shè)我們有32k的數(shù)據(jù)要發(fā)送,
第一次send,返回16384字節(jié)(16k),也就是填滿了緩沖區(qū)
第二次send,在這之前sleep了1000毫秒,這段時間可能已經(jīng)有5000字節(jié)從緩沖區(qū)發(fā)出,到達另外一端了,于是緩沖區(qū)空了5000字節(jié)出來,相應(yīng)的,這次返回的是5000,表示新放入了5000字節(jié)到緩沖區(qū)
第三次send ,和第二次相同,又放了6000字節(jié)
最后一次send,放入了剩下的字節(jié)數(shù),這個時候緩沖還是有數(shù)據(jù)的。
再發(fā)送大于16k數(shù)據(jù)的情況下,那個send發(fā)送循環(huán)就是必須的了
以上就是小編為大家?guī)淼膶end(),recv()函數(shù)的全面理解全部內(nèi)容了,希望大家多多支持腳本之家~