UDP接收報錯10040的問題——socket的一些知識

最近遇到一個很奇怪的問題,服務端接收客戶端發來的包出現了這個UDP錯誤,返回-1.GetlastError爲10040.

服務端這邊recvfrom裏面buffer大小已經取得很大了,有16kb.客戶端發過來的內容也不是很大。

按照一般思路可以嘗試一下辦法:1.就讓減少包的大小,看服務端這邊還會不會報這個錯。2.或許可以設置一下緩衝區大小。

返回-1有時候可能是套接字緩衝區裏拷貝到buffer裏面產生的錯誤。

recvfrom默認在創建時是阻塞的。

找了一篇文章可以對socket有基本認識:

一下內容轉自:https://blog.csdn.net/rethyx/article/details/19982609

阻塞和非阻塞 
  阻塞函數在完成其指定的任務以前不允許程序調用另一個函數。例如,程序執行一個讀數據的函數調用時,在此函數完成讀操作以前將不會執行下一程序語句。當服務器運行到accept語句時,而沒有客戶連接服務請求到來,服務器就會停止在accept語句上等待連接服務請求的到來。這種情況稱爲阻塞(blocking)。而非阻塞操作則可以立即完成。比如,如果你希望服務器僅僅注意檢查是否有客戶在等待連接,有就接受連接,否則就繼續做其他事情,則可以通過將Socket設置爲非阻塞方式來實現。非阻塞socket在沒有客戶在等待時就使accept調用立即返回。 
  #include <unistd.h> 
  #include <fcntl.h> 
  …… 
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 { 
   int tv_sec; /* seconds */ 
   int tv_usec; /* microseconds */ 
}; 

 

另一種是使用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)
{
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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章