2013-07-01 12 views
1

自己編寫簡單的C/S應用到測試非阻塞套接字的特性,這裏是關於服務器和客戶端的一些簡要信息:爲什麼選擇()有時超時當客戶端是忙於接收數據

//On linux The server thread will send 
//a file to the client using non-blocking socket  
void *SendFileThread(void *param){ 
    CFile* theFile = (CFile*) param; 
    int sockfd = theFile->GetSocket(); 
    set_non_blocking(sockfd); 
    set_sock_sndbuf(sockfd, 1024 * 64); //set the send buffer to 64K 

    //get the total packets count of target file 
    int PacketCOunt = theFile->GetFilePacketsCount(); 
    int CurrPacket = 0; 
    while (CurrPacket < PacketCount){ 
     char buffer[512]; 
     int len = 0; 

     //get packet data by packet no. 
     GetPacketData(currPacket, buffer, len); 

     //send_non_blocking_sock_data will loop and send 
     //data into buffer of sockfd until there is error 
     int ret = send_non_blocking_sock_data(sockfd, buffer, len); 
     if (ret < 0 && errno == EAGAIN){ 
      continue; 
      } else if (ret < 0 || ret == 0){ 
      break; 
     } else { 
      currPacket++; 
     } 


     ...... 
    } 
} 

//On windows, the client thread will do something like below 
//to receive the file data sent by the server via block socket 
void *RecvFileThread(void *param){ 
    int sockfd = (int) param; //blocking socket 
    set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256 

    while (1){ 
     struct timeval timeout; 
     timeout.tv_sec = 1; 
     timeout.tv_usec = 0; 

     fd_set rds; 
     FD_ZERO(&rds); 
     FD_SET(sockfd, &rds)' 

     //actually, the first parameter of select() is 
     //ignored on windows, though on linux this parameter 
     //should be (maximum socket value + 1) 
     int ret = select(sockfd + 1, &rds, NULL, NULL, &timeout); 
     if (ret == 0){ 
      // log that timer expires 
      CLogger::log("RecvFileThread---Calling select() timeouts\n"); 
     } else if (ret) { 
      //log the number of data it received 
      int ret = 0; 
      char buffer[1024 * 256]; 
      int len = recv(sockfd, buffer, sizeof(buffer), 0); 
      // handle error 
      process_tcp_data(buffer, len); 
     } else { 
      //handle and break; 
      break; 
     } 

    } 
} 

讓我吃驚的是,服務器線程經常失敗,因爲插座緩衝區滿,如發送一個14M大小的文件,它報告errno = EAGAIN 50000次失敗。然而,通過日誌記錄,我觀察到在傳輸過程中有幾十個超時,流程如下:

  1. 在第N個循環中,select()成功併成功讀取了256K的數據。
  2. 在第(N + 1)個循環中,select()失敗且超時。 (N + 2)循環上的
  3. ,select()成功併成功讀取256K的數據。

爲什麼在接收過程中會發生交錯的超時?誰能解釋這種現象?

[UPDATE]
1.上傳14M的文件到服務器只需要8秒
2.使用與1相同的文件)時,服務器需要近30秒的所有數據發送到客戶端。
3.客戶端使用的所有套接字都被阻塞。服務器使用的所有套接字都是非阻塞的。

關於#2,我認爲超時是#2需要更多時間的原因,我想知道爲什麼當客戶端忙於接收數據時會有這麼多超時。

[UPDATE2]
感謝來自@Duck,@ebrob,@EJP,@ja_mesa的意見,我會做更多的調查今天 然後更新這個帖子。
關於爲什麼我在服務器線程中發送每個循環512字節,這是因爲我發現服務器線程發送數據的速度比接收它們的客戶端線程快得多。我很困惑,爲什麼超時發生在客戶端線程。

+0

爲什麼'GetFilePacketsCount()'與服務器端的'CurrPacket'有任何關係?不是512字節的緩衝區長度有點任意嗎?另外,在服務器端,似乎你會得到一大堆'EAGAIN',但是當你正確處理它們時應該沒問題。也許在EAGAIN上睡某種東西會是個好主意?等待它看起來像GetPacketData消耗數據,所以你可能通過多次調用一個'EAGAIN'來創建空隙? – 2013-07-01 14:06:49

+0

你如何聲明/處理'rds'?我想你需要FD_SET/FD_ZERO每次循環,在你打電話之前select(...) – 2013-07-01 14:08:57

+0

@ebyrob謝謝,我更新了源代碼。令我感到驚訝的是,在客戶端線程忙於接收數據期間,選擇調用發生超時,並且服務器線程報告了數千個EAGAIN失敗! – Steve

回答

2

考慮這個比解答更長的評論,但是正如幾個人已經注意到網絡比你的處理器慢幾個數量級。非阻塞I/O點在於差異非常大,您可以真正使用它來完成實際工作而不是阻塞。在這裏,你只是在電梯按鈕上衝擊,希望能帶來改變。

我不確定你的代碼有多少是真實的,有多少是爲了發佈而切斷的,但是在服務器中你沒有考慮(ret == 0),即對方的正常關閉。

客戶端中的select錯誤。同樣,不確定這是不是馬虎的編輯,但如果不是,那麼參數的數量是錯誤的,但更重要的是,第一個參數 - 即應該是選擇查看的最高文件描述符加1 - 是零。根據select的實施情況,我想知道這是否只是將select變成了花式的sleep聲明。

+0

如果他使用的是MS WinSock,那麼第一個參數將被忽略,並且僅用於與Berkeley套接字的兼容性。無論如何,參數的數量是錯誤的,我也想知道他爲什麼使用1秒超時,並在select()超時時不執行任何操作。當服務器以512字節發送時,他還爲每次讀取()創建一個256K緩衝區。我認爲我們提供的工具要用到智慧和認真,否則我們最終會遇到這種神祕的東西。 –

0

您應該先撥打recv(),然後撥打select(),前提是recv()要求您這樣做。首先不要致電select(),那是浪費處理。 recv()或者知道,如果數據立即可用,如果有等待數據的到來:

void *RecvFileThread(void *param){ 
    int sockfd = (int) param; //blocking socket 
    set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256 

    char buffer[1024 * 256]; 

    while (1){ 

     int ret = 0; 
     int len = recv(sockfd, buffer, sizeof(buffer), 0); 
     if (len == -1) { 
      if (WSAGetLastError() != WSAEWOULDBLOCK) { 
       //handle error 
       break; 
      } 

      struct timeval timeout; 
      timeout.tv_sec = 1; 
      timeout.tv_usec = 0; 

      fd_set rds; 
      FD_ZERO(&rds); 
      FD_SET(sockfd, &rds)' 

      //actually, the first parameter of select() is 
      //ignored on windows, though on linux this parameter 
      //should be (maximum socket value + 1) 
      int ret = select(sockfd + 1, &rds, NULL, &timeout); 
      if (ret == -1) { 
       // handle error 
       break; 
      } 

      if (ret == 0) { 
       // log that timer expires 
       break; 
      } 

      // socket is readable so try read again 
      continue; 
     } 

     if (len == 0) { 
      // handle graceful disconnect 
      break; 
     } 

     //log the number of data it received 
     process_tcp_data(buffer, len); 
    } 
} 

請在發送側類似的東西爲好。首先撥打send(),然後只有send()告訴您這樣做,然後致電select()等待可寫性。