对于我在互联网上看到的 Linux/Unix 套接字教程的每一个教程和示例,服务器端代码总是包含一个无限循环,每次都检查客户端连接。例子:
http://www.thegeekstuff.com/2011/12/c-socket-programming/
http://tldp.org/LDP/LG/issue74/tougher.html#3.2
是否有更有效的方法来构建服务器端代码,使其不涉及无限循环,或者以占用更少系统资源的方式对无限循环进行编码?
对于我在互联网上看到的 Linux/Unix 套接字教程的每一个教程和示例,服务器端代码总是包含一个无限循环,每次都检查客户端连接。例子:
http://www.thegeekstuff.com/2011/12/c-socket-programming/
http://tldp.org/LDP/LG/issue74/tougher.html#3.2
是否有更有效的方法来构建服务器端代码,使其不涉及无限循环,或者以占用更少系统资源的方式对无限循环进行编码?
这些例子中的无限循环已经很有效了。调用accept()
是阻塞调用:在有客户端连接到服务器之前,该函数不会返回。调用该accept()
函数的线程的代码执行被暂停,并且不占用任何处理能力。
可以将其视为对互斥锁/锁/信号量accept()
的调用或等待。join()
当然,还有许多其他方法可以处理传入连接,但这些其他方法处理accept()
. 此功能很难取消,因此存在非阻塞替代方案,允许服务器在等待传入连接时执行其他操作。一种这样的选择是使用select()
. 其他替代方案的可移植性较差,因为它们涉及低级操作系统调用,以通过回调函数、事件或操作系统处理的任何其他异步机制发出连接信号……
对于 C++,您可以查看boost.asio。您还可以研究例如异步 I/O函数。还有SIGIO
.
当然,即使使用这些异步方法,你的主程序仍然需要处于一个循环中,否则程序将退出。
无限循环用于维护服务器的运行状态,因此当接受客户端连接时,服务器不会立即退出,而是会返回侦听另一个客户端连接。
listen() 调用是一个阻塞调用——也就是说,它一直等到它接收到数据。这是一种非常有效的方法,通过使用触发事件(或硬件中断)的操作系统网络驱动程序来唤醒侦听线程,从而使用零系统资源(当然,直到建立连接)。
以下是对可用技术的一个很好的概述 - C10K 问题。
当您实现一个侦听可能无限连接的服务器时,imo 无法绕过某种无限循环。通常这根本不是问题,因为当您的套接字未标记为非阻塞时,调用accept()
将阻塞,直到新连接到达。由于这种阻塞,没有系统资源被浪费。
提供类似基于事件的系统的其他库最终以上述方式实现。
除了已经发布的内容之外,很容易看到调试器发生了什么。在执行 accept() 行之前,您将能够单步执行,此时“单步”突出显示将消失,应用程序将继续运行 - 未到达下一行。如果您将面包点放在下一行,则在客户端连接之前它不会触发。
我们需要遵循编写客户端-服务器编程的最佳实践。目前我可以向您推荐的最佳指南是The C10K Problem。在这种情况下,我们需要遵循一些特定的东西。我们可以选择使用 select 或 poll 或 epoll。每个都有自己的优点和缺点。
如果您使用最新的内核版本运行代码,那么我建议您使用 epoll。点击查看示例程序了解epoll。
如果您正在使用 select、poll、epoll,那么您将被阻止,直到您获得事件/触发器,这样您的服务器就不会因消耗系统时间而陷入无限循环。
就我个人的经验而言,我觉得 epoll 是更进一步的最佳方式,因为我观察到我的服务器机器在拥有 80k ACTIVE 连接时的阈值在比较它将选择和轮询时非常低。在有 80k 活动连接时,我的服务器机器的平均负载仅为 3.2 :)
在使用 poll 进行测试时,我发现在达到 30k 活动客户端连接时,我的服务器负载平均上升到 7.8 :(。