为什么在尝试读取和写入套接字时选择行为会有所不同?

Why does selects-behaviour differ when trying to read and write sockets?

假设我们有一个用 accept() 接受的客户端文件描述符

client_socket = accept(_socket, (sockaddr *)&client_addr, &len)

我们现在将这个文件描述符设置为可读写fd_set:

fd_set readfds;
fd_set writefds;

//zero them
FD_ZERO(readfds);
FD_ZERO(writefds);

//set the client_socket
FD_SET(client_socket, &readfds);
FD_SET(client_socket, &writefds);

现在我们使用select检查套接字是否可读或可写:

select(FD_SETSIZE, &readfs, &writefds, NULL, NULL)

我们现在检查是否可以先读取并从中读取所有字节。

if (FD_ISSET(client_socket, &readfds) {
    read(client_socket, &buf, 4096);
}
//assume that buf is big enough and that read returns less than 4096

在下一个循环中,我们像以前一样重置 fd_sets。 现在 select 将允许我们将回复写给客户:

if (FD_ISSET(client_socket, &readfds) {
    write(client_socket, &buf, len(buf));
}

到这里为止一切正常,但现在出现了奇怪的行为。 让我们假设我们的客户告诉我们保持连接,因为 如果我们像以前一样设置 fd_set:

//zero them
FD_ZERO(readfds);
FD_ZERO(writefds);

//set the client_socket
FD_SET(client_socket, &readfds);
FD_SET(client_socket, &writefds);
// reading not allowed

现在使用 select 时,它允许我们再次写入,但不允许从 client_socket 读取。 但是,如果我们将 writefds 的设置更改为零,它将允许我们读取,尽管我们没有更改 readfds 中的任何内容。

//zero them
FD_ZERO(readfds);
FD_ZERO(writefds);

//set the client_socket
FD_SET(client_socket, &readfds);
//FD_SET(client_socket, &writefds); -> don't set the file-descriptors for write
// now reading is allowed

有人可以向我解释一下这是 select 的正确行为吗,或者如果这是我的错,可能是我没有显示的代码的其他部分(太复杂了)。 对我来说,在设置两组(写入和读取)时,select 的行为似乎是随机的。 我知道有一种方法可以通过保持一种状态来决定我们是否要设置读取文件描述符或写入文件描述符来解决这个问题,但我希望有一个更清晰的解决方案。

select() 的目的是 不是 return 直到你的程序有事情要做 。这样您的程序就可以在 select() 内休眠,直到 I/O 准备就绪,立即醒来执行 I/O,然后尽快返回休眠状态。

那么问题来了,select() 怎么知道什么时候到 return?答案是,您必须通过以适当的方式调用 FD_SET() 来告诉它什么会导致它 return。

通常你会想要 select() 到 return 当你的任何套接字上的数据是 ready-for-read 时(这样你就可以读取 newly-arrived 数据),所以你应该通常在所有套接字上调用 FD_SET(mySock, &readFD)

FD_SET(mySock, &writeFD) 有点细微差别。当套接字有 buffer-space 可用于写入输出字节时,它告诉 select() 到 return。但是,在许多情况下,当套接字有 buffer-space 可用时,您 想要 select() 到 return,仅仅是因为您目前没有任何你想写入套接字的数据。在那种情况下,如果你总是调用 FD_SET(mySocket, &writeFD) 那么 select() 将立即保持 returning 即使你没有任何你想执行的任务,这将导致你的程序使用毫无意义地进行了很多 CPU 循环。

所以你应该调用 FD_SET(mySocket, &writeFD) 的唯一时间是当你知道你想尽快将一些数据写入该套接字时。

在您的程序中,可能发生的情况是 FD_SET(mySocket, &writeFD) 导致 select() 立即变为 return(因为 mySocket 当前有 buffer-space 可用于写入),然后你的程序(错误地)假设因为 select() 有 returned,套接字是 ready-for-read,只是发现它不是。在您注释掉 FD_SET(mySocket, &writeFD) 的情况下,OTOH,select() 不会 return 直到套接字为 ready-for-read,因此您会得到预期的行为你打电话给 FD_ISSET(mySocket, &readFD).