有一些现成的软件库可以提供此功能。我能想到的主要是curl。您可以在here找到curl multi库的简单介绍。
通常最好避免重新发明轮子,除非您有充分的理由(例如改善技术世界或进行学术研究)。
为了学术研究,由于没有“仅链接”的答案就足够了,我将详细说明一种可能的多路复用套接字方法。
非阻塞套接字
第一种也是目前最可移植的方法是使用非阻塞套接字和/或非阻塞套接字调用,但是实现这一点很重要(尤其是当使用非阻塞套接字调用作为反对将O_NONBLOCK 设置为文件描述符):有些东西仍然会阻塞。例如,除非您将文件描述符设置为非阻塞模式,否则您无法让connect 立即返回,当然您getaddrinfo(以及类似的标准名称解析函数)也会阻塞。
当您使用非阻塞文件或调用函数时,函数将立即返回。如果没有准备好数据,他们将通过返回值来表明这一点。如果有数据准备好处理,再次通过返回值显示。
有两种方法(据我所知)可以确保非阻塞套接字调用(包括connect)。
- 对于类 Unix 系统,请致电
fcntl(socket_fd, fcntl(socket_fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK)。之后,所有对read、write、accept 和connect 的调用将立即返回,不会有任何延迟。 connect 有一些专门用于此的错误代码(在 errno 中),例如 EALREADY、EINPROGRESS、EISCONN 和 EWOULDBLOCK,您需要检查它们,因为当您启用非阻塞,有些错误返回值实际上是变相的成功返回值;你需要检查errno。
-
对于 Windows 系统,请致电 ioctlsocket(socket_fd, FIONBIO, (u_long[]){1})。除了errno 代码不会是errno 代码(相反,它们将是GetLastError() 代码)并且它们是......可能不同的值,我不知道之外,将出现相同的语义。不知道。但是,它们中的许多名称相似,因此在我的项目中,我通常使用以下名称:
#ifdef _WIN32
#define set_nonblock(fd) (ioctlsocket(fd, FIONBIO, (u_long[]){1}) == 0)
#define EAGAIN WSAEWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EISCONN WSAEISCONN
#define EINPROGRESS WSAEINPROGRESS
#define EINVAL WSAEINVAL
#define EALREADY WSAEALREADY
#else
#define set_nonblock(fd) (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) != -1)
#endif
不值一提
单独使用非阻塞套接字,如果您设法在各种系统上使用单个线程维护数千个连接,而无需进行少量调整,我一点也不感到惊讶。但是,这种模型并不理想,因为您需要一个繁忙的循环来循环遍历每个套接字,并在每个循环中即时测试它们的事件;例如,而不是在事件到达时触发您的代码被操作系统唤醒。
我们知道为了向应用程序发送事件,内核需要处理这些事件,所以我们可以给它一些时间,例如使用sleep(0);,作为快速修复。这肯定会看到 CPU 使用率从接近 100% 下降到 10% 以下。但是,另一种方法是使用非阻塞(或超时中断)函数多路复用多个(阻塞或非阻塞)套接字,这样函数将在某些数据可用时立即返回,或者将等到时间到期接收数据。
select 有明显的优点,但也有缺点;也就是说,这些集合通常仅限于少量的套接字;要支持大量套接字,您需要在循环中设置一个循环,因为您会发现 64 个套接字限制(或其他任何限制)很快就会用完。此外,它不能解决 connect 阻塞问题(就像 O_NONBLOCK 和 ~FIONBIO` 方法所做的那样)。
因此,我不再谈论select;我将描述您可以使用的其他选项。另一个有类似限制的例子是poll;我也不会谈论这个。如果你想知道,互联网上有很多关于它的信息......
请注意,从现在开始的所有内容都非常不可移植(尽管您可能会找到将它们全部包装到一个通用接口中的方法,就像 curl multi 所做的那样)。
异步套接字调用将开始一个连接,然后像非阻塞套接字调用一样立即返回,除了它们还会在连接完成时引发信号或调用您指定的函数。这是让操作系统控制在事件到达时通知您的代码,而不是让操作系统等待您。应该清楚的是,就优化而言,异步套接字是理想的,但它们不可移植。每个操作系统都有不同的选项:
所有这些都有一个共同点,那就是它们在成功或失败时调用一个函数(或引发一个信号,您可以将其转换为对函数的调用)。但是,它们的界面并不是那么熟悉。
通常,我没有费心为它们编写任何类型的包装器,因为我发现我在这个答案开头提到的非阻塞套接字现在已经绰绰有余了。重要的是我不需要将它移植到每个系统,因为……我懒得做那个!只有当有人向我展示该系统运行缓慢时,我才会针对该系统进行优化。否则,我们最终会陷入海量的系统中,人们甚至可能永远不会在……上使用我们的软件。