为什么客户端忙于接收数据时select()有时会超时

Why select() timeouts sometimes when the client is busy receiving data

我已经编写了简单的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\

");

    } 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;

    }



  }

}

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);

  }

}
//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\

");

    } 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;

    }



  }

}

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);

  }

}

令我惊讶的是,由于套接字缓冲区已满,服务器线程经常失败,例如要发送一个 14M 大小的文件,它会报告 50000 次失败,且 errno = EAGAIN。但是,通过日志记录我观察到传输过程中有数十次超时,流程如下:

  • 第N次循环,select()成功,成功读取256K的数据。
  • 在第 (N 1) 个循环中,select() 因超时而失败。
  • 在第 (N 2) 次循环中,select() 成功并成功读取 256K 的数据。
  • 为什么在接收过程中会出现交错的超时?谁能解释一下这个现象?

    [更新]

    1.上传一个14M的文件到服务器只需要8秒

    2. 使用与1)相同的文件,服务器需要将近30秒的时间将所有数据发送到客户端。

    3. 客户端使用的所有套接字都是阻塞的。服务器使用的所有套接字都是非阻塞的。

    关于#2,我认为超时是#2比#1花费更多时间的原因,我想知道为什么客户端忙于接收数据时会有这么多超时。

    [更新2]

    感谢@Duck、@ebrobe、@EJP、@ja_mesa 的评论,我今天会做更多的调查

    然后更新这篇文章。

    关于为什么我在服务器线程中每个循环发送 512 个字节,这是因为我发现服务器线程发送数据的速度比客户端线程接收它们的速度快得多。我很困惑为什么客户端线程会发生超时。


    认为这更像是一个长评论而不是答案,但正如一些人所指出的那样,网络比您的处理器慢几个数量级。非阻塞 i/o 的关键在于差异如此之大,以至于您实际上可以使用它来完成实际工作而不是阻塞。在这里,您只是在按电梯按钮,希望有所作为。

    我不确定你的代码有多少是真实的,有多少是为了发布而被砍掉的,但在服务器中你没有考虑 (ret == 0),即对等方的正常关闭。

    客户端中的select 错误。同样,不确定这是否是草率的编辑,但如果不是,那么参数的数量是错误的,但更令人担忧的是,第一个参数 - 即应该是 select 查看的最高文件描述符加一 - 为零。根据 select 的实现,我想知道这是否实际上只是将 select 变成了一个花哨的 sleep 语句。


    您应该先调用 recv(),然后仅当 recv() 告诉您这样做时才调用 select()。不要先调用select(),那是浪费处理。 recv() 知道数据是立即可用还是必须等待数据到达:

    //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\
    
    ");
    
        } 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;
    
        }
    
    
    
      }
    
    }
    
    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(),然后调用 select() 等待可写性,前提是 send() 告诉你这样做。


相关推荐

  • Spring部署设置openshift

    Springdeploymentsettingsopenshift我有一个问题让我抓狂了三天。我根据OpenShift帐户上的教程部署了spring-eap6-quickstart代码。我已配置调试选项,并且已将Eclipse工作区与OpehShift服务器同步-服务器上的一切工作正常,但在Eclipse中出现无法消除的错误。我有这个错误:cvc-complex-type.2.4.a:Invali…
    2025-04-161
  • 检查Java中正则表达式中模式的第n次出现

    CheckfornthoccurrenceofpatterninregularexpressioninJava本问题已经有最佳答案,请猛点这里访问。我想使用Java正则表达式检查输入字符串中特定模式的第n次出现。你能建议怎么做吗?这应该可以工作:MatchResultfindNthOccurance(intn,Patternp,CharSequencesrc){Matcherm=p.matcher…
    2025-04-161
  • 如何让 JTable 停留在已编辑的单元格上

    HowtohaveJTablestayingontheeditedcell如果有人编辑JTable的单元格内容并按Enter,则内容会被修改并且表格选择会移动到下一行。是否可以禁止JTable在单元格编辑后转到下一行?原因是我的程序使用ListSelectionListener在单元格选择上同步了其他一些小部件,并且我不想在编辑当前单元格后选择下一行。Enter的默认绑定是名为selectNext…
    2025-04-161
  • Weblogic 12c 部署

    Weblogic12cdeploy我正在尝试将我的应用程序从Tomcat迁移到Weblogic12.2.1.3.0。我能够毫无错误地部署应用程序,但我遇到了与持久性提供程序相关的运行时错误。这是堆栈跟踪:javax.validation.ValidationException:CalltoTraversableResolver.isReachable()threwanexceptionatorg.…
    2025-04-161
  • Resteasy Content-Type 默认值

    ResteasyContent-Typedefaults我正在使用Resteasy编写一个可以返回JSON和XML的应用程序,但可以选择默认为XML。这是我的方法:@GET@Path("/content")@Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})publicStringcontentListRequestXm…
    2025-04-161
  • 代码不会停止运行,在 Java 中

    thecodedoesn'tstoprunning,inJava我正在用Java解决项目Euler中的问题10,即"Thesumoftheprimesbelow10is2+3+5+7=17.Findthesumofalltheprimesbelowtwomillion."我的代码是packageprojecteuler_1;importjava.math.BigInteger;importjava…
    2025-04-161
  • Out of memory java heap space

    Outofmemoryjavaheapspace我正在尝试将大量文件从服务器发送到多个客户端。当我尝试发送大小为700mb的文件时,它显示了"OutOfMemoryjavaheapspace"错误。我正在使用Netbeans7.1.2版本。我还在属性中尝试了VMoption。但仍然发生同样的错误。我认为阅读整个文件存在一些问题。下面的代码最多可用于300mb。请给我一些建议。提前致谢publicc…
    2025-04-161
  • Log4j 记录到共享日志文件

    Log4jLoggingtoaSharedLogFile有没有办法将log4j日志记录事件写入也被其他应用程序写入的日志文件。其他应用程序可以是非Java应用程序。有什么缺点?锁定问题?格式化?Log4j有一个SocketAppender,它将向服务发送事件,您可以自己实现或使用与Log4j捆绑的简单实现。它还支持syslogd和Windows事件日志,这对于尝试将日志输出与来自非Java应用程序…
    2025-04-161