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