如何迭代fd_set?如果这是一个非常糟糕的做法,除了循环遍历所有连接的套接字之外,还有其他解决“问题”的方法吗?
谢谢
如何迭代fd_set?如果这是一个非常糟糕的做法,除了循环遍历所有连接的套接字之外,还有其他解决“问题”的方法吗?
谢谢
在调用select()之前,您必须填充一个fd_set结构体,不能直接传入原始的std::set套接字。然后,select()会相应地修改fd_set,删除任何未“设置”的套接字,并返回剩余的套接字数量。您必须遍历生成的fd_set,而不是遍历您的std::set。因为生成的fd_set只包含准备就绪的“设置”套接字,所以没有必要调用FD_ISSET(),例如:
fd_set read_fds;
FD_ZERO(&read_fds);
int max_fd = 0;
read_fds.fd_count = connected_sockets.size();
for( int i = 0; i < read_fds.fd_count; ++i )
{
read_fds.fd_array[i] = connected_sockets[i];
if (read_fds.fd_array[i] > max_fd)
max_fd = read_fds.fd_array[i];
}
if (select(max_fd+1, &read_fds, NULL, NULL, NULL) > 0)
{
for( int i = 0; i < read_fds.fd_count; ++i )
do_socket_operation( read_fds.fd_array[i] );
}
FD_ISSET()在使用select()进行错误检查时更常用,例如:
fd_set read_fds;
FD_ZERO(&read_fds);
fd_set error_fds;
FD_ZERO(&error_fds);
int max_fd = 0;
read_fds.fd_count = connected_sockets.size();
for( int i = 0; i < read_fds.fd_count; ++i )
{
read_fds.fd_array[i] = connected_sockets[i];
if (read_fds.fd_array[i] > max_fd)
max_fd = read_fds.fd_array[i];
}
error_fds.fd_count = read_fds.fd_count;
for( int i = 0; i < read_fds.fd_count; ++i )
{
error_fds.fd_array[i] = read_fds.fd_array[i];
}
if (select(max_fd+1, &read_fds, NULL, &error_fds, NULL) > 0)
{
for( int i = 0; i < read_fds.fd_count; ++i )
{
if( !FD_ISSET(read_fds.fd_array[i], &error_fds) )
do_socket_operation( read_fds.fd_array[i] );
}
for( int i = 0; i < error_fds.fd_count; ++i )
{
do_socket_error( error_fds.fd_array[i] );
}
}
error_fds.fd_count = read_fds.fd_count;
,那么在if(select...)
语句中使用只有1个for
循环会更好,我猜测检查read_fds
和error_fds
的FD_ISSET
存在一些错误。(即FD_ISSET(read_fds)
根本没有被检查) - RIscRIptFD_ISSET(read_fds.fd_array[i], &read_fds)
是多余的,因为循环已经遍历了“设置”的 read_fds
的项。该循环在处理每个“可读”项目之前检查它是否也不在 error_fds
中。要同时使用单个循环处理两个 fd_set
,需要不同的循环逻辑: for (int fd = 0; fd <= max_fd; ++fd) { if (FD_ISSET(fd, &error_fds) {...} else if (FD_ISSET(fd, &read_fds)) {...} }
,但这在所有平台上都无法移植(尤其是 Windows)。 - Remy Lebeaufd_count
也不是真正的可移植性。fd_set
应该是不透明的,你真的不应该直接访问它的元素。FD_...()
宏是为了隐藏这些细节而设计的。 - Remy LebeauSelect函数将文件描述符对应的位设置在集合中,所以如果你只对其中几个感兴趣(并且可以忽略其他的),就不需要遍历所有的文件描述符,只需要测试那些你感兴趣的文件描述符即可。
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
perror("select");
exit(4);
}
if(FD_ISSET(fd0, &read_fds))
{
//do things
}
if(FD_ISSET(fd1, &read_fds))
{
//do more things
}
编辑
以下是fd_set结构体:
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
其中,fd_count是设置的套接字数量(因此,您可以使用此进行优化),fd_array是位向量(大小为FD_SETSIZE * sizeof(int),这取决于机器)。 在我的机器上,它是64 * 64 = 4096。
所以,您的问题本质上是:在大小约为4096位的位向量中找到1的位位置的最有效方法是什么?
我想在这里澄清一件事:
“循环遍历所有连接的套接字”并不意味着实际读取/处理连接。FD_ISSET()仅检查分配给连接的文件描述符号码处的fd_set中的位是否设置。如果效率是您的目标,那么这不是最有效的方法吗?使用启发式算法?
请告诉我们这种方法的问题在哪里,以及您试图使用替代方法实现什么。
FD_SETSIZE * sizeof(int)
没有任何意义。据我所知,FD_SETSIZE
是位图中文件描述符的数量,没有理由将其乘以魔法常数。此外,伪 C 语言定义位数组只会更加混淆。 - Pavel Šimerda这相当简单:
for( int fd = 0; fd < max_fd; fd++ )
if ( FD_ISSET(fd, &my_fd_set) )
do_socket_operation( fd );
我认为你试图做的事情不是一个好主意。
首先,它是系统相关的,但我相信你已经知道了。
其次,在内部级别,这些集合被存储为整数数组,而fds则被存储为设置位。现在根据select的man页面,FD_SETSIZE为1024。 即使您想迭代并获取您感兴趣的fd,您也必须循环该数字以及一堆位操作的混乱。 因此,除非您正在等待超过FD_SETSIZE fd的选择,否则我认为这是不可能的,这不是一个好主意。
哦等等!!无论如何都不是一个好主意。
ffs() 可以在 POSIX 或 4.3BSD 上用于比特迭代,但它期望 int(long 和 long long 版本是 glibc 扩展)。当然,你必须检查 ffs() 是否像 strlen 和 strchr 一样被优化。