【问题标题】:Block with select() untill client acquisition or loss使用 select() 阻塞,直到客户端获取或丢失
【发布时间】:2015-06-10 08:42:54
【问题描述】:

我需要知道客户端是否连接/断开并处理它。

这是我唯一的想法:

while(!serverStop)
{
    fd_set rfds, wfdsBefore, wfdsAfter;

    FD_ZERO(&rfds);
    FD_SET(serverFd, &rfds);

    FD_ZERO(&wfdsBefore);
    fillWithClientFds(&wfdsBefore); // clients only listen for messages
    wfdsAfter = wfdsBefore;

    while(1)
    {
        select(notimportant, &rfds, &wfdsAfter, NULL, NULL);

        if (FD_ISSET(serverFd, &rfds)) // new client appeared
            break;

        if (doSetsDiffer(&wfdsBefore, &wfdsAfter)) // some client disconnected (doesn't work)
            break;
    }

    // inform connected clients about disconnected ones

}

不仅会发生忙等待,而且这种方法甚至不起作用(wfdsAfter 不会改变,尽管客户端关闭了套接字)。

有什么办法吗?唯一的要求是不要使用多线程。

serverFd 是使用 PF_UNIXSOCK_STREAM 标志创建的。

【问题讨论】:

  • 这不是忙等待。 select() 块!要知道一个工作套接字已断开连接,您必须 read() 它并获得 0 个字节作为回报,或者尝试 write() 到一个损坏的套接字并失败。除非发生这两种情况之一,否则将套接字视为已连接
  • 在这种情况下不会,因为select() 会立即返回要写入的描述符,即使它们对应的套接字已经关闭。
  • 客户端不发送任何内容,因此read() 将始终返回 0,我不想通过 write() 发送任何无意义的数据,只是为了检查连接是否仍然正常(除非我可以发送 0 个字节仍然失败)。
  • 你的理解有误。读写集的差异并不意味着客户端消失了。可靠地检测断开的 TCP 连接的唯一方法是写入它。如果没有数据被交换,你究竟是为了什么而浪费套接字和连接?
  • 有数据交换。服务器通知所有连接的客户端有关新的连接/断开连接。

标签: c sockets unix file-descriptor unix-socket


【解决方案1】:

您应该在连接后将每个客户端文件描述符放在读取描述符 (rfds) 集中,并且当文件描述符随后返回为可读时,尝试从套接字读取。

首先,如果您的客户端真的什么都没有发送(并且还没有断开连接),那么它的套接字将永远不会被标记为可读。这似乎可以解决您的问题,因为您说客户端实际上从未发送任何内容:在客户端断开连接之前,它不会被标记为可读。

但即使客户端发送数据,文件描述符也只有在数据可用客户端已断开连接时才会被标记为可读。然后,您可以通过尝试读取套接字来轻松区分。返回值可以是读取的字节数(如果有数据),或者如果客户端已断开连接则为零。

(服务器通常在套接字中添加 O_NONBLOCK 选项,以确保在客户端有数据要发送时收到通知,但要确保它们不会阻塞等待来自客户端的数据。使用该选项,当客户端已断开连接。使用该选项,如果客户端仍然存在,但没有可用数据,则读取调用将返回 -1,并将 errno 设置为 EAGAIN/EWOULDBLOCK。)

我没有解释的另一个细微差别是,可以在一个方向关闭数据传输,同时允许它在另一个方向继续(如果您关心这一点,请参阅shutdown(2))。

【讨论】:

    【解决方案2】:

    您将客户端套接字放入 write 描述符集中。您需要将它们放在 read 描述符集中。

    当服务器套接字至少有 1 个待处理的客户端请求时,它是可读的。您可以拨打accept()接受客户。

    当一个套接字在其入站缓冲区中有数据,或其连接的对等方已断开连接,它是可读,而不是可写。您可以拨打read()进行区分。 read() 在入站数据时返回 > 0,在正常断开连接时返回 > 0,在错误时返回 -1。

    当其出站缓冲区中有可用空间时,套接字是可写的。如果write() 失败并出现EWOULDBLOCK 错误,则出站缓冲区已填满,并且套接字不再可写。当缓冲区清除一些空间时,套接字将再次变为可写

    此外,select() 会修改您传递给它的 fdsets,因此您需要在每次循环迭代时重置 rfds。为避免这种情况,您可以改用(e)poll()

    所以,你需要更像这样的东西:

    fd_set rfds;
    
    while (!serverStop)
    {
        FD_ZERO(&rfds);
        FD_SET(serverFd, &rfds);
        fillWithClientFds(&rfds); // clients only listen for messages
    
        if (select(notimportant, &rfds, NULL, NULL, NULL) < 0)
            break;
    
        if (FD_ISSET(serverFd, &rfds)) // new client appeared
        {
            // call accept(), add client to connected list...
        }
    
        // clear disconnected list...
    
        for (each client in connected list)
        {
            if (FD_ISSET(clientFd, &rfds))
            {
                int nBytes = read(clientFd, ...);
                if (nBytes > 0)
                {
                    // handle client data as needed ...
                }
                else if (nBytes == 0)
                {
                    // add client to disconnected list
                }
                else
                {
                    // handle error...
                    // possibly add client to disconnected list...
                }
            }
        }
    
        for (each client in disconnected list)
        {
            // remove client from connected list...
        }
    
        for (each client in disconnected list)
        {
            // inform connected clients
        }
    }
    

    【讨论】:

    • 一个套接字当它的发送缓冲区中有空间时是可写的,并且 select() 会告诉你这一点。它不知道历史。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-20
    • 2014-11-26
    • 2017-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多