为什么有时在客户端忙于接收数据时选择()超时

时间:2013-07-01 13:53:02

标签: c++ linux windows sockets tcp

我编写了简单的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大小的文件,它会报告50000个错误并使用errno = EAGAIN。然而,通过记录我观察到在传输期间有几十个超时,流程如下:

  1. 在第N个循环上,select()成功并成功读取256K的数据。
  2. 在第(N + 1)个循环上,select()因超时而失败。
  3. 在第(N + 2)个循环上,select()成功并成功读取256K的数据。
  4. 为什么在接收期间会有超时交错?任何人都能解释这种现象吗?

    [UPDATE]
    1.将14M的文件上传到服务器只需要8秒 2.使用与1)相同的文件,服务器需要将近30秒的时间将所有数据发送到客户端 3.客户端使用的所有套接字都是阻塞的。服务器使用的所有套接字都是非阻塞的。

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

    [UPDATE2]
    感谢@ Duck,@ ebrob,@ EJP,@ ah_mesa的评论,今天我会做更多的调查 然后更新这篇文章 关于为什么我在服务器线程中每个循环发送512个字节,这是因为我发现服务器线程比接收它们的客户端线程更快地发送数据。我很困惑,为什么超时发生在客户端线程上。

2 个答案:

答案 0 :(得分:2)

考虑这个问题不仅仅是答案,而是因为有几个人注意到网络比处理器慢了几个数量级。非阻塞i / o的意义在于,差异非常大,您实际上可以使用它来进行实际工作而不是阻塞。在这里,您只需按下电梯按钮,希望能有所作为。

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

客户端中的select错误。再次,不确定这是否是草率编辑,但如果没有那么参数的数量是错误的,但更关注的是,第一个参数 - 即应该是选择看加一的最高文件描述符 - 是零。根据{{​​1}}的实现情况,我想知道这实际上是否只是将select转换为花哨的select语句。

答案 1 :(得分: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(),然后仅在select()告诉您这样做时才调用send()等待可写。