recv send 阻塞和非阻塞
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ù)時,
?
(1)send先比較待發(fā)送數(shù)據(jù)的長度len和套接字s的發(fā)送緩沖的長度, 如果len大于s的發(fā)送緩沖區(qū)的長度,該函數(shù)返回SOCKET_ERROR;
(2)如果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
(3)如果len大于剩余空間大小,send就一直等待協(xié)議把s的發(fā)送緩沖中的數(shù)據(jù)發(fā)送完
(4)如果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信號,進程對該信號的默認處理是進程終止。
?
通過測試發(fā)現(xiàn),異步socket的send函數(shù)在網(wǎng)絡(luò)剛剛斷開時還能發(fā)送返回相應(yīng)的字節(jié)數(shù),同時使用select檢測也是可寫的,但是過幾秒鐘之后,再send就會出錯了,返回-1。select也不能檢測出可寫了。
?
?
?
2. 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ù)時,
?
(1)recv先等待s的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢,如果協(xié)議在傳送s的發(fā)送緩沖中的數(shù)據(jù)時出現(xiàn)網(wǎng)絡(luò)錯誤,那么recv函數(shù)返回SOCKET_ERROR,
?
(2)如果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信號,進程對該信號的默認處理是進程終止。
阻塞就是干不完不準回來,???
非組賽就是你先干,我現(xiàn)看看有其他事沒有,完了告訴我一聲
我們拿最常用的send和recv兩個函數(shù)來說吧...?
比如你調(diào)用send函數(shù)發(fā)送一定的Byte,在系統(tǒng)內(nèi)部send做的工作其實只是把數(shù)據(jù)傳輸(Copy)到TCP/IP協(xié)議棧的輸出緩沖區(qū),它執(zhí)行成功并不代表數(shù)據(jù)已經(jīng)成功的發(fā)送出去了,如果TCP/IP協(xié)議棧沒有足夠的可用緩沖區(qū)來保存你Copy過來的數(shù)據(jù)的話...這時候就體現(xiàn)出阻塞和非阻塞的不同之處了:對于阻塞模式的socket send函數(shù)將不返回直到系統(tǒng)緩沖區(qū)有足夠的空間把你要發(fā)送的數(shù)據(jù)Copy過去以后才返回,而對于非阻塞的socket來說send會立即返回WSAEWOULDDBLOCK告訴調(diào)用者說:"發(fā)送操作被阻塞了!!!你想辦法處理吧..."?
對于recv函數(shù),同樣道理,該函數(shù)的內(nèi)部工作機制其實是在等待TCP/IP協(xié)議棧的接收緩沖區(qū)通知它說:嗨,你的數(shù)據(jù)來了.對于阻塞模式的socket來說如果TCP/IP協(xié)議棧的接收緩沖區(qū)沒有通知一個結(jié)果給它它就一直不返回:耗費著系統(tǒng)資源....對于非阻塞模式的socket該函數(shù)會馬上返回,然后告訴你:WSAEWOULDDBLOCK---"現(xiàn)在沒有數(shù)據(jù),回頭在來看看"
?
?
讀數(shù)據(jù)的時候需要考慮的是當(dāng)recv()返回的大小如果等于請求的大小,那么很有可能是緩沖區(qū)還有數(shù)據(jù)未讀完,也意味著該次事件還沒有處理完,所以還需要再次讀取:
while(rs)
{
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
if(buflen < 0)
{
??? // 由于是非阻塞的模式,所以當(dāng)errno為EAGAIN時,表示當(dāng)前緩沖區(qū)已無數(shù)據(jù)可讀
??? // 在這里就當(dāng)作是該次事件已處理處.
??? if(errno == EAGAIN)
???? break;
??? else
???? return;
?? }
?? else if(buflen == 0)
?? {
???? // 這里表示對端的socket已正常關(guān)閉.
?? }
?? if(buflen == sizeof(buf)
???? rs = 1;?? //?需要再次讀取
?? else
???? rs = 0;
}
還有,假如發(fā)送端流量大于接收端的流量(意思是epoll所在的程序讀比轉(zhuǎn)發(fā)的socket要快),由于是非阻塞的socket,那么send()函數(shù)雖然返回,但實際緩沖區(qū)的數(shù)據(jù)并未真正發(fā)給接收端,這樣不斷的讀和發(fā),當(dāng)緩沖區(qū)滿后會產(chǎn)生EAGAIN錯誤(參考man send),同時,不理會這次請求發(fā)送的數(shù)據(jù).所以,需要封裝socket_send()的函數(shù)用來處理這種情況,該函數(shù)會盡量將數(shù)據(jù)寫完再返回,返回-1表示出錯。在socket_send()內(nèi)部,當(dāng)寫緩沖已滿(send()返回-1,且errno為EAGAIN),那么會等待后再重試.這種方式并不很完美,在理論上可能會長時間的阻塞在socket_send()內(nèi)部,但暫沒有更好的辦法.
ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)
{
ssize_t tmp;
size_t total = buflen;
const char *p = buffer;
while(1)
{
??? tmp = send(sockfd, p, total, 0);
??? if(tmp < 0)
??? {
????? // 當(dāng)send收到信號時,可以繼續(xù)寫,但這里返回-1.
????? if(errno == EINTR)
??????? return -1;
????? // 當(dāng)socket是非阻塞時,如返回此錯誤,表示寫緩沖隊列已滿,
????? // 在這里做延時后再重試.
????? if(errno == EAGAIN)
????? {
??????? usleep(1000);
??????? continue;
????? }
????? return -1;
??? }
??? if((size_t)tmp == total)
????? return buflen;
??? total -= tmp;
??? p += tmp;
}
return tmp;
}
總結(jié)
以上是生活随笔為你收集整理的recv send 阻塞和非阻塞的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: socket 通信关于bind那点事
- 下一篇: UDP和TCP的区别(详细)