增加FD_SETSIZE和select的限制

18

我想增加我的系统的FD_SETSIZE宏值。是否有任何方法可以增加FD_SETSIZE,以便select不会失败


是的,在我的情况下,我需要它大约为2048。有没有办法设置它? - Vivek Goel
因为我想增加服务器的限制,以支持这么多的连接。 - Vivek Goel
6
不冒犯,但考虑增加FD_SETSIZE是相当愚蠢的事情。2048个并发连接(或更多)已经在epoll_wait大幅优于selectpoll的范围内,因为它不需要每次复制8千字节的数据,并且不需要每次遍历两千个描述符。 - Damon
真正的答案是:在 Linux 下不行。对于 BSD 和 Windows 可以重新定义 FD_SETSIZE。尝试这样做需要进行黑客攻击,而且肯定会导致未来的问题。因此,最好在描述符值可能超过 1024 的情况下使用 poll。 - philippe lhardy
真正的答案是:在Linux下是可以的。对于在Linux上运行的MMO游戏Furcadia,我们修改了FD_SETSIZE常量,这样我们就能在同一台服务器上容纳上万名玩家。自1996年以来,在Linux的各个版本、服务器升级和迁移中,它一直运行良好。 - Dewi Morgan
5个回答

17
根据标准,无法增加FD_SETSIZE。一些程序和库(例如libevent)尝试通过为fd_set对象分配额外空间并传递大于FD_SETSIZE的值给FD_*宏来解决这个问题,但这是一个非常糟糕的想法,因为强大的实现可能会对参数进行边界检查,并在参数超出范围时终止操作。
我有另一种替代方案,它应该始终有效(即使标准没有要求)。不要使用单个fd_set对象,而是分配足够大的数组以容纳所需的最大fd,然后使用FD_SET(fd%FD_SETSIZE, &fds_array[fd/FD_SETSIZE])等访问集合。

6
@dns:那是虚假的资料。 :-) 请阅读http://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html。开玩笑了,请解释一下你认为我的答案有什么“虚假”的地方。 - R.. GitHub STOP HELPING ICE
4
根据 Microsoft 自己的说法,“根据标准,没有办法增加 FD_SETSIZE”是错误的,因为我要逐字引用 Microsoft 的话:“这个值用于构造在 select() 中使用的 fd_set 结构。WINSOCK.H 中的默认值为 64。如果一个应用程序设计成能够处理超过 64 个套接字,则在包含 WINSOCK.H 之前,在每个源文件中定义显式的 FD_SETSIZE 标记。”请参阅 http://support.microsoft.com/kb/111855。 - BrierMay
9
那也不是标准做法,而是特定实现的技巧。我同意在许多实现中,你可以采用这种特定实现的技巧,但这并不符合标准。 - R.. GitHub STOP HELPING ICE
7
@AndrewHacking说:“人们什么时候才会厌倦在这上面发布错误评论?是的,有些实现允许它。不,根据接口规范,这不是有效的。相关文本是...” - R.. GitHub STOP HELPING ICE
3
关于我的说法“强大的实现可能会检查”,请查看glibc提供的bits/select2.h。每当激活_FORTIFY_SOURCE时,它都会被使用,并且它积极检查fd号码是否超出内部常量(不是你错误地声称可以修改的公共FD_SETSIZE),并且如果fd号码超出范围就会终止程序(合法,因为UB被调用)。 - R.. GitHub STOP HELPING ICE
显示剩余17条评论

15

我建议尽可能使用poll。还有一些处理“事件”的库,例如libeventlibev(或者来自GTK的GlibQtCore等的事件功能),这些应该可以帮助您。还有像epoll这样的东西。而您的问题与C10k有关。


1
是的。我认为这是最终答案。如果你在Linux下的监视器描述符之一超过1024个,那么你要么必须通过fd_set结构进行“黑客”操作(并冒着更多问题的风险),要么使用poll更好。 - philippe lhardy
你必须祈祷你使用的每个库都能正确地执行它。 唯一可行的解决方案是使用1024个句柄或者100%理解你程序中包括所有库的代码,并确保永远不使用“select”。 - Lothar

13

使用轮询(poll)替换掉 select() 会更好(也更容易)。一般来说,poll() 是select()的简单替代方案,并且不受 FD_SETSIZE 1024 的限制...

fd_set fd_read;
int id = 42;
FD_ZERO(fd_read);
FD_SET(id, &fd_read);
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
if (select(id + 1, &fd_read, NULL, NULL, &tv) != 1) {
   // Error.
}

变成:

struct pollfd pfd_read;
int id = 42;
int timeout = 5000;
pfd_read.fd = id;
pfd_read.events = POLLIN;
if (poll(&pfd_read, 1, timeout) != 1) {
   // Error
}

你需要包含poll.h头文件以使用pollfd结构体。

如果你需要进行读写操作,则需要将events标志设置为POLLIN | POLLOUT。


7
为了使用一个比FD_SETSIZE更大的fd_set,可以定义一个扩展的fd_set,如下所示:
#include <sys/select.h>
#include <stdio.h>

#define EXT_FD_SETSIZE 2048
typedef struct
{
    long __fds_bits[EXT_FD_SETSIZE / 8 / sizeof(long)];
} ext_fd_set;

int main()
{
    ext_fd_set fd;
    int s;
    printf("FD_SETSIZE:%d sizeof(fd):%ld\n", EXT_FD_SETSIZE, sizeof(fd));
    FD_ZERO(&fd);
    while ( ((s=dup(0)) != -1) && (s < EXT_FD_SETSIZE) )
    {
        FD_SET(s, &fd);
    }
    printf("select:%d\n", select(EXT_FD_SETSIZE,(fd_set*)&fd, NULL, NULL, NULL));
    return 0;
}

这将输出:

FD_SETSIZE:2048 sizeof(fd):256

select:2045


为了打开超过1024个文件描述符,需要使用例如 ulimit -n 2048 来增加限制。


1
这是潜在的危险行为。不涉及符合标准的问题(在我看来是有效的),还有一个更明显的用例可能会失败,那就是链接到编译为使用系统定义FD_SETSIZE的库时。使用poll()是正确的方法。 - edam

2
实际上,在Windows上有一种方法可以增加FD_SETSIZE。它在winsock.h中定义,根据微软自己的说法,你可以通过在包含winsock.h之前定义它来增加它的值:
请参见应用程序可以使用的套接字的最大数量(旧链接),或者更近期的页面支持的套接字的最大数量
我经常这样做,没有遇到任何问题。我曾经使用的最大值是大约5000,用于我正在开发的服务器。

4
BSD和Windows允许设置FD_SETSIZE,而Linux中的fd_set内部大小实际上由硬编码为1024的__FD_SETSIZE设置。对于Linux,最好使用poll,因为select无法处理超过前1024个描述符。实际上,在Linux上它似乎可以工作,但会使用分配给fd_set之外的位,这肯定会破坏堆栈并导致崩溃。这就是为什么当前的解决方案是在堆栈上分配更多的空间,这不太美观。 - philippe lhardy
1
这个问题涉及到Linux,而不是Windows。 - mpromonet
这里也提到了:https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select(ctrl+f 'FD_SETSIZE') - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接