【问题标题】:select does not wait for timeout value in c++ sockets选择不等待 C++ 套接字中的超时值
【发布时间】:2013-01-24 07:43:08
【问题描述】:

我编写了一个服务器代码,它接受新客户端和来自客户端的数据。但问题是尽管没有来自客户端的数据,但 select 不会等到超时。我想等待 5 秒钟,然后为可用的客户发送心跳。但它在第一次迭代中等待 5 秒,然后在下一次迭代中快速发送心跳。如何解决这个问题呢。提前致谢。

void * Communicate(void * id)
{
int *iSockID = (int *) id;
int listener =  *iSockID;

fd_set master;    // master file descriptor list
fd_set read_fds;  // temp file descriptor list for select() read
fd_set write_fds; // temp file descriptor list for select() read
int fdmax;        // maximum file descriptor number

int i, j, rv;

FD_ZERO(&master);    // clear the master and temp sets
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
// add the listener to the master set
FD_SET(listener, &master);
printf("Listener is %d \n" , listener);

// keep track of the biggest file descriptor
fdmax = listener; // so far, it's this one
//accept 3 clients


// main loop
for(;;) {
    read_fds = master; // copy it
    write_fds = master;
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    int iResult = select(fdmax+1, &read_fds, &write_fds, NULL, &tv) ;
    if (iResult == -1) 
    {
        perror("select");
        exit(4);
    }

    for(i = 0; i <= fdmax; i++) 
    {           
        //send work for clients
        SendHeartBeats(write_fds , fdmax , listener , i );

    }

    // run through the existing connections looking for data to read
    // ADD NEW CONNECTIONS READ FROM CONNECTIONS    
    for(i = 0; i <= fdmax; i++)
    {   


        if (FD_ISSET(i, &read_fds)) 
        { // we got one!!
            // handle new connections               
            if (i == listener) 
            {                                    
                AcceptNewClients(master , fdmax , listener );   
            } else 
            {
                AccepeDataFromClients(i , master);
            } // END handle data from client
        } // END got new incoming connection
    } // END looping through file descriptors
    sleep(3);
} // END for(;;)
return 0;
}

【问题讨论】:

  • 如果没有看到SendHeartBeats的实现,我无法提供更详细的答案。

标签: c++ sockets client-server


【解决方案1】:

你不能 fd_set 用等号来设置变量。您需要使用 FD_COPY。如果不这样做,您只需将句柄复制到已标记为已完成的实际数据。

【讨论】:

  • +1。 fdmax = listener; 也在等待失败,特别是考虑到 AcceptNewClients(master , fdmax , listener ); 需要更新 fdmax 的尴尬。而且,代码不应该执行sleep(3) - 让超时处理它,进行超时后经过时间检查以避免过度频繁的心跳(如果您关心的话)。
  • 我已经包含了 sys/time.h 文件。但是 FD_COPY 给了我错误 FD_COPY 没有在这个范围内声明。 @Tony 我使用了 sleep() ,因为超时不起作用。骗子你告诉我我在这里做错了什么??..
  • @Zeus:如果您不重建 fd 集并提供正确的最大值,则超时将不起作用......一旦设置任何内容,它将保持设置状态。对于 FD_COPY - 您是否检查了您的手册页或其他系统文档? stys/time.h 在某些系统(例如某些 IBM)上绝对是正确的 - 但这些东西可能会有所不同,或者它可能根本不可用。您可以潜在地重置/清除它并再次设置标志,或者在某些系统上您不需要使用 FD_COPY - 例如检查 GNU libc 如何在gnu.org/software/libc/manual/html_node/Server-Example.html
  • @TonyD uname -r 给了我“Linux 2.6.27.19-5-default”版本。我已经包含了 sys/time.h。但是编译器仍然抱怨它无法识别 FD_COPY。
【解决方案2】:

您需要更改代码以在每次调用 select() 时重新初始化 read_fdwrite_fd 变量,因为它会在退出时修改它们,因此您每次都需要重置它们。正如其他人所说,使用= 运算符复制master 变量不是复制fd_set 结构的正确方法。

试试这个:

void * Communicate(void * id)
{
    int *iSockID = (int *) id;
    int listener =  *iSockID;

    fd_set master;    // master file descriptor list
    fd_set read_fds;  // temp file descriptor list for select() read
    fd_set write_fds; // temp file descriptor list for select() read
    struct timeval tv;
    int fdmax;        // maximum file descriptor number
    int i, j, rv;

    printf("Listener is %d \n", listener);

    // add the listener to the master set
    FD_ZERO(&master);
    FD_SET(listener, &master);

    // keep track of the biggest file descriptor
    fdmax = listener; // so far, it's this one
    //accept 3 clients

    // main loop
    clock_t c1 = clock();
    while (1)
    {
        FD_ZERO(&read_fds);
        FD_ZERO(&write_fds);

        #ifdef MSWINDOWS
        // Windows does not have FD_COPY()
        for (u_int i = 0; i < master.fd_count; ++i)
        {
            FD_SET(master.fd_array[i], &read_fd);
            FD_SET(master.fd_array[i], &write_fd);
        }
        #else
        FD_COPY(&master, &read_fd);
        FD_COPY(&master, &write_fd);
        #endif

        tv.tv_sec = 1;
        tv.tv_usec = 0;

        int iResult = select(fdmax+1, &read_fds, &write_fds, NULL, &tv);
        if (iResult == -1) 
        {
            perror("select");
            exit(4);
        }

        clock_t c2 = clock();
        if (((c2-c1) / CLOCKS_PER_SEC) >= 5)
        {
            c1 = c2;
            for(i = 0; i <= fdmax; i++) 
            {           
                if ((i != listener) && FD_ISSET(i, &write_fds))
                { 
                    //send work for client
                    SendHeartBeat(i);
                }
            }
        }

        // run through the existing connections looking for data to read
        // ADD NEW CONNECTIONS READ FROM CONNECTIONS    
        for(i = 0; i <= fdmax; i++)
        {   
            if (FD_ISSET(i, &read_fds)) 
            {
                // we got one!!
                if (i == listener) 
                {                                    
                    AcceptNewClient(master, fdmax, listener);   
                }
                else 
                {
                    AcceptDataFromClient(i);
                }
            }
        }
    }

    return 0;
}

【讨论】:

  • 非常感谢您的回答。问题是存在编译错误。“FD_COPY 未在此范围内声明”。
  • 如果您的编译器的套接字 API 没有可用的 FD_COPY()(并非所有平台都有),那么您将不得不手动循环通过 master 列表,就像我为 Windows 展示的那样。
【解决方案3】:

您在评论中声明您使用的是Linux 2.6.27。由于在 Linux 中,fd_set 是一个包含整数类型数组的结构,因此使用赋值将 master 复制到 read_fds 就可以了。

您的select 调用没有阻塞的原因是因为您正在从master 初始化read_fdswrite_fds。您的空闲客户端套接字始终是可写的,因此您会立即醒来。顺便说一句,在 listener 套接字上选择可写是没有意义的。

当您尝试写入但收到EAGAIN/EWOULDBLOCK 错误时,您应该只在write_fds 中设置客户端套接字。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-12
    • 2017-03-08
    • 2010-12-14
    • 2017-01-17
    • 1970-01-01
    • 2013-06-08
    • 1970-01-01
    相关资源
    最近更新 更多