在可用写入 fdset 上选择阻塞,直到进程中断并续订

Select blocking on available write fdset until the process is interrupted and renewed

本文关键字:进程 中断 fdset 选择      更新时间:2023-10-16

我有一个TCP服务器应用程序和一个TCP客户端应用程序。服务器在多个线程中运行,在一个线程中,select() 调用以无限期超时阻塞:

while(running) {
initFdSets();
nfds = select(max_fd + 1, &listened_fdset, &write_fdset, &exception_fdset, NULL);
...
reads_from_fds_in_listened_fdset();
writes_to_fds_in_write_fdset();
}

同时,在另一个线程中,首次将有效的套接字文件描述符添加到写入 FD 集中:

FD_SET(connection_socket_fd, write_fdset_ptr);    

并且 select() 调用仍然阻塞。 在此期间,客户端应用会阻止想要从两者之间连接的另一端读取的 read() 调用:

read(sockfd, &msgType, sizeof(int32_t));

直到我在调试模式下中断服务器应用程序然后恢复它之前,什么都不会发生。这样做之后,select() 返回,将适当的字节流发送到客户端应用并按计划接收。Writefdset 在 select() 调用之前重新初始化...但是服务器行为在新的 while() 迭代中不会改变。

是的,所有集合都使用 FD_ZERO 清除并在调用 select() 之前重新初始化(包括第二个代码段中的文件描述符)。

我会感谢这个问题的解决,因为这是我必须从我的项目中摆脱的最后一个问题 - 但不知道它是如何以及为什么发生的。

查看手册页,以便在此处选择:选择手册页。

选择挂起的原因是您需要将 timeval 作为参数传递给它,以便告诉它应该超时。引用:

The timeout
The time structures involved are defined in <sys/time.h> and look
like
struct timeval {
long    tv_sec;         /* seconds */
long    tv_usec;        /* microseconds */
};
and
struct timespec {
long    tv_sec;         /* seconds */
long    tv_nsec;        /* nanoseconds */
};
(However, see below on the POSIX.1 versions.)
Some code calls select() with all three sets empty, nfds zero, and a
non-NULL timeout as a fairly portable way to sleep with subsecond
precision.
On Linux, select() modifies timeout to reflect the amount of time not
slept; most other implementations do not do this.  (POSIX.1 permits
either behavior.)  This causes problems both when Linux code which
reads timeout is ported to other operating systems, and when code is
ported to Linux that reuses a struct timeval for multiple select()s
in a loop without reinitializing it.  Consider timeout to be unde‐
fined after select() returns.

因此,这应该可以解决您的问题:

while(running) {
timeval tv = {};
static constexpr long myTimeoutInMicros = 10000; // Put your desired timeout here.
tv.tv_usec = myTimeoutInMicros;
tv.tv_sec = 0;
initFdSets();
nfds = select(max_fd + 1, &listened_fdset, &write_fdset, &exception_fdset, &tv);
...
reads_from_fds_in_listened_fdset();
writes_to_fds_in_write_fdset();
}

我还建议使用beej的网络指南进行额外的阅读:Beejs网络指南,这是bsd风格的套接字呼叫网络的综合来源。

编辑:值得注意的是,在 Solaris 10/Solaris 11(不确定 IlumOs)上,无论是否提供 timeval,选择都将超时。