使用 select() 阻止,直到获得或失去客户
Block with select() untill client acquisition or loss
我需要知道是否有客户connected/disconnected并处理它。
这是我唯一的想法:
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_UNIX
和 SOCK_STREAM
标志制作的。
您应该在连接后将每个客户端文件描述符放入读取描述符 (rfds) 集中,并且当文件描述符随后 return 编辑为可读时,尝试从套接字读取。
首先,如果您的客户端真的没有发送任何内容(并且还没有断开连接),它的套接字将永远不会被标记为可读。这似乎可以解决您的问题,因为您说客户端实际上从未发送任何内容:直到客户端断开连接之前,它不会被标记为可读。
但是即使客户端发送数据,文件描述符也只有在有数据可用时才会被标记为可读或客户端有断开连接。然后,您可以通过尝试读取套接字轻松区分。 return 值要么是读取的字节数(如果有数据),要么是零(如果客户端已断开连接)。
(服务器通常将 O_NONBLOCK 选项添加到套接字,以确保在客户端有数据要发送但希望确保它们不会阻塞等待来自客户端的数据时得到通知。使用该选项, read still returns 0 when the client has disconnected. 使用该选项,如果客户端仍在附近,但没有可用数据,则 read 调用将 return -1,errno 设置为 EAGAIN/EWOULDBLOCK.)
另一个我没有解释的细微差别是,可以关闭一个方向的数据传输,同时允许它在另一个方向继续(如果您关心这个,请参阅 shutdown(2))。
您正在将客户端套接字放入 write 描述符集中。您需要将它们放入 read 描述符集中。
当服务器套接字至少有 1 个待处理的客户端请求时,它是可读。您可以致电 accept()
接受客户。
当套接字在其入站缓冲区中有数据时,或者其连接的对等方断开连接时,它是可读,而不是可写。可以调用read()
来区分。 read()
returns > 0 入站数据,0 正常断开,-1 错误。
当套接字在其出站缓冲区中有可用 space 时,它是 可写的 。如果 write()
失败并出现 EWOULDBLOCK
错误,则出站缓冲区已满,套接字不再 可写 。当缓冲区清除一些 space 时,套接字将再次变为 可写。
此外,select()
会修改您传递给它的 fdset
,因此您需要在每次循环迭代时重置 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
}
}
我需要知道是否有客户connected/disconnected并处理它。
这是我唯一的想法:
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_UNIX
和 SOCK_STREAM
标志制作的。
您应该在连接后将每个客户端文件描述符放入读取描述符 (rfds) 集中,并且当文件描述符随后 return 编辑为可读时,尝试从套接字读取。
首先,如果您的客户端真的没有发送任何内容(并且还没有断开连接),它的套接字将永远不会被标记为可读。这似乎可以解决您的问题,因为您说客户端实际上从未发送任何内容:直到客户端断开连接之前,它不会被标记为可读。
但是即使客户端发送数据,文件描述符也只有在有数据可用时才会被标记为可读或客户端有断开连接。然后,您可以通过尝试读取套接字轻松区分。 return 值要么是读取的字节数(如果有数据),要么是零(如果客户端已断开连接)。
(服务器通常将 O_NONBLOCK 选项添加到套接字,以确保在客户端有数据要发送但希望确保它们不会阻塞等待来自客户端的数据时得到通知。使用该选项, read still returns 0 when the client has disconnected. 使用该选项,如果客户端仍在附近,但没有可用数据,则 read 调用将 return -1,errno 设置为 EAGAIN/EWOULDBLOCK.)
另一个我没有解释的细微差别是,可以关闭一个方向的数据传输,同时允许它在另一个方向继续(如果您关心这个,请参阅 shutdown(2))。
您正在将客户端套接字放入 write 描述符集中。您需要将它们放入 read 描述符集中。
当服务器套接字至少有 1 个待处理的客户端请求时,它是可读。您可以致电 accept()
接受客户。
当套接字在其入站缓冲区中有数据时,或者其连接的对等方断开连接时,它是可读,而不是可写。可以调用read()
来区分。 read()
returns > 0 入站数据,0 正常断开,-1 错误。
当套接字在其出站缓冲区中有可用 space 时,它是 可写的 。如果 write()
失败并出现 EWOULDBLOCK
错误,则出站缓冲区已满,套接字不再 可写 。当缓冲区清除一些 space 时,套接字将再次变为 可写。
此外,select()
会修改您传递给它的 fdset
,因此您需要在每次循环迭代时重置 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
}
}