recv send 阻塞和非阻塞

2021-06-20 16:12:07 字數 4872 閱讀 2528

阻塞和非阻塞 

阻塞函式在完成其指定的任務以前不允許程式呼叫另乙個函式。例如,程式執行乙個讀資料的函式呼叫時,在此函式完成讀操作以前將不會執行下一程式語句。當伺服器執行到accept語句時,而沒有客戶連線服務請求到來,伺服器就會停止在accept語句上等待連線服務請求的到來。這種情況稱為阻塞(blocking)。而非阻塞操作則可以立即完成。比如,如果你希望伺服器僅僅注意檢查是否有客戶在等待連線,有就接受連線,否則就繼續做其他事情,則可以通過將socket設定為非阻塞方式來實現。非阻塞socket在沒有客戶在等待時就使accept呼叫立即返回。 

#include

#include

…… sockfd = socket(af_inet,sock_stream,0); 

fcntl(sockfd,f_setfl,o_nonblock); 

…… 通過設定socket為非阻塞方式,可以實現"輪詢"若干socket。當企圖從乙個沒有資料等待處理的非阻塞socket讀入資料時,函式將立即返回,返回值為-1,並置errno值為ewouldblock。但是這種"輪詢"會使cpu處於忙等待方式,從而降低效能,浪費系統資源。而呼叫select()會有效地解決這個問題,它允許你把程序本身掛起來,而同時使系統核心監聽所要求的一組檔案描述符的任何活動,只要確認在任何被監控的檔案描述符上出現活動,select()呼叫將返回指示該檔案描述符已準備好的資訊,從而實現了為程序選出隨機的變化,而不必由程序本身對輸入進行測試而浪費cpu開銷。select函式原型為: 

int select(int numfds,fd_set *readfds,fd_set *writefds, 

fd_set *exceptfds,struct timeval *timeout); 

其中readfds、writefds、exceptfds分別是被select()監視的讀、寫和異常處理的檔案描述符集合。如果你希望確定是否可以從標準輸入和某個socket描述符讀取資料,你只需要將標準輸入的檔案描述符0和相應的sockdtfd加入到readfds集合中;numfds的值是需要檢查的號碼最高的檔案描述符加1,這個例子中numfds的值應為sockfd+1;當select返回時,readfds將被修改,指示某個檔案描述符已經準備被讀取,你可以通過fd_issset()來測試。為了實現fd_set中對應的檔案描述符的設定、復位和測試,它提供了一組巨集: 

fd_zero(fd_set *set)----清除乙個檔案描述符集; 

fd_set(int fd,fd_set *set)----將乙個檔案描述符加入檔案描述符集中; 

fd_clr(int fd,fd_set *set)----將乙個檔案描述符從檔案描述符集中清除; 

fd_isset(int fd,fd_set *set)----試判斷是否檔案描述符被置位。 

timeout引數是乙個指向struct timeval型別的指標,它可以使select()在等待timeout長時間後沒有檔案描述符準備好即返回。struct timeval資料結構為: 

struct timeval ; 

另一種是使用select()函式。

** int send( socket s, const char far *buf, int len, int flags );

不論是客戶還是伺服器應用程式都用send函式來向tcp連線的另一端傳送資料。客戶程式一般用send函式向伺服器傳送請求,而伺服器則通常用send函式來向客戶程式傳送應答。

該函式的第乙個引數指定傳送端套接字描述符;

第二個引數指明乙個存放應用程式要傳送資料的緩衝區;

第三個引數指明實際要傳送的資料的位元組數;

第四個引數一般置0。

這裡只描述同步socket的send函式的執行流程。當呼叫該函式時,

(1)send先比較待傳送資料的長度len和套接字s的傳送緩衝的長度, 如果len大於s的傳送緩衝區的長度,該函式返回socket_error;

(2)如果len小於或者等於s的傳送緩衝區的長度,那麼send先檢查協議是否正在傳送s的傳送緩衝中的資料,如果是就等待協議把資料傳送完,如果協議 還沒有開始傳送s的傳送緩衝中的資料或者s的傳送緩衝中沒有資料,那麼send就比較s的傳送緩衝區的剩餘空間和len

(3)如果len大於剩餘空間大小,send就一直等待協議把s的傳送緩衝中的資料傳送完

(4)如果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訊號,程序對該訊號的預設處理是程序終止。

通過測試發現,非同步socket的send函式在網路剛剛斷開時還能傳送返回相應的位元組數,同時使用select檢測也是可寫的,但是過幾秒鐘之後,再send就會出錯了,返回-1。select也不能檢測出可寫了。

2. recv函式

int recv( socket s, char far *buf, int len, int flags);

不論是客戶還是伺服器應用程式都用recv函式從tcp連線的另一端接收資料。該函式的第乙個引數指定接收端套接字描述符;

第二個引數指明乙個緩衝區,該緩衝區用來存放recv函式接收到的資料;

第三個引數指明buf的長度;

第四個引數一般置0。

這裡只描述同步socket的recv函式的執行流程。當應用程式呼叫recv函式時,

(1)recv先等待s的傳送緩衝中的資料被協議傳送完畢,如果協議在傳送s的傳送緩衝中的資料時出現網路錯誤,那麼recv函式返回socket_error,

(2)如果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訊號,程序對該訊號的預設處理是程序終止。

阻塞就是幹不完不准回來,   

非組賽就是你先乾,我現看看有其他事沒有,完了告訴我一聲

我們拿最常用的send和recv兩個函式來說吧... 

比如你呼叫send函式傳送一定的byte,在系統內部send做的工作其實只是把資料傳輸(copy)到tcp/ip協議棧的輸出緩衝區,它執行成功並不代表資料已經成功的傳送出去了,如果tcp/ip協議棧沒有足夠的可用緩衝區來儲存你copy過來的資料的話...這時候就體現出阻塞和非阻塞的不同之處了:對於阻塞模式的socket send函式將不返回直到系統緩衝區有足夠的空間把你要傳送的資料copy過去以後才返回,而對於非阻塞的socket來說send會立即返回wsaewoulddblock告訴呼叫者說:"傳送操作被阻塞了!!!你想辦法處理吧..." 

對於recv函式,同樣道理,該函式的內部工作機制其實是在等待tcp/ip協議棧的接收緩衝區通知它說:嗨,你的資料來了.對於阻塞模式的socket來說如果tcp/ip協議棧的接收緩衝區沒有通知乙個結果給它它就一直不返回:耗費著系統資源....對於非阻塞模式的socket該函式會馬上返回,然後告訴你:wsaewoulddblock---"現在沒有資料,回頭在來看看"

讀資料的時候需要考慮的是當recv()返回的大小如果等於請求的大小,那麼很有可能是緩衝區還有資料未讀完,也意味著該次事件還沒有處理完,所以還需要再次讀取:

while(rs)

else if(buflen == 0)

if(buflen == sizeof(buf)

rs = 1;   // 需要再次讀取

else

rs = 0;

}還有,假如傳送端流量大於接收端的流量(意思是epoll所在的程式讀比**的socket要快),由於是非阻塞的socket,那麼send()函式雖然返回,但實際緩衝區的資料並未真正發給接收端,這樣不斷的讀和發,當緩衝區滿後會產生eagain錯誤(參考man send),同時,不理會這次請求傳送的資料.所以,需要封裝socket_send()的函式用來處理這種情況,該函式會盡量將資料寫完再返回,返回-1表示出錯。在socket_send()內部,當寫緩衝已滿(send()返回-1,且errno為eagain),那麼會等待後再重試.這種方式並不很完美,在理論上可能會長時間的阻塞在socket_send()內部,但暫沒有更好的辦法.

ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)

return -1;

}if((size_t)tmp == total)

return buflen;

total -= tmp;

p += tmp;

}return tmp;

}

recv send 阻塞和非阻塞

在 阻塞模式 下,send函式的過程是將應用程式請求傳送的資料拷貝到傳送快取中傳送就返回.但由於傳送快取的存在,表現為 如果傳送快取大小比請求傳送的大小要大,那麼send函式立即返回,同時向網路中傳送資料 否則,send會等待接收端對之前傳送資料的確認,以便騰出快取空間容納新的待傳送資料,再返回 接...

recv send 阻塞和非阻塞

int send socket s,const char far buf,int len,int flags 不論是客戶還是伺服器應用程式都用send函式來向tcp連線的另一端傳送資料。客戶程式一般用send函式向伺服器傳送請求,而伺服器則通常用send函式來向客戶程式傳送應答。該函式的第乙個引數指...

recv send 阻塞和非阻塞

nt send socket s,const char far buf,int len,int flags 不論是客戶還是伺服器應用程式都用send函式來向tcp連線的另一端傳送資料。客戶程式一般用send函式向伺服器傳送請求,而伺服器則通常用send函式來向客戶程式傳送應答。該函式的第乙個引數指定...