http://blog.csdn.net/xiaofei0859/article/details/6037814服務器
int send( SOCKET s, const char FAR *buf, int len, int flags );網絡
不管是客戶仍是服務器應用程序都用send函數來向TCP鏈接的另外一端發送數據。客戶程序通常用send函數向服務器發送請求,而服務器則一般用send函數來向客戶程序發送應答。異步
該函數的第一個參數指定發送端套接字描述符;socket
第二個參數指明一個存放應用程序要發送數據的緩衝區;函數
第三個參數指明實際要發送的數據的字節數;測試
第四個參數通常置0。.net
這裏只描述同步Socket的send函數的執行流程。當調用該函數時,blog
(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)
{
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
if(buflen < 0)
{
// 因爲是非阻塞的模式,因此當errno爲EAGAIN時,表示當前緩衝區已無數據可讀
// 在這裏就看成是該次事件已處理處.
if(errno == EAGAIN)
break;
else
return;
}
else if(buflen == 0)
{
// 這裏表示對端的socket已正常關閉.
}
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){ssize_t tmp;size_t total = buflen;const char *p = buffer;while(1){ tmp = send(sockfd, p, total, 0); if(tmp < 0) { // 當send收到信號時,能夠繼續寫,但這裏返回-1. if(errno == EINTR) return -1; // 當socket是非阻塞時,如返回此錯誤,表示寫緩衝隊列已滿, // 在這裏作延時後再重試. if(errno == EAGAIN) { usleep(1000); continue; } return -1; } if((size_t)tmp == total) return buflen; total -= tmp; p += tmp;}return tmp;}