我想用 c++ 编写一个程序,它创建 100 多个与 telnet 服务器的连接并读取它们的数据流(解析和解释它们)。我应该为每个连接使用一个线程吗?还是有另一种方法可以在没有数百个线程的情况下处理如此多的连接?
3 回答
在最简单的形式中,每个连接可以使用一个线程。但这不会扩展到您的数百个连接,并且多线程可能会使您的代码和逻辑更加复杂(它也可能会使其更简单。这在很大程度上取决于您的应用程序正在尝试做什么。)
比那更好一点的是使用select
. 这是大多数(全部?)套接字库和操作系统支持的函数调用。基本上,您将所有套接字放在一个集合中,然后将集合提供给select
并告诉它等待任何这些套接字上的任何事件(事件类似于新数据到达套接字或连接错误或写入完成或类似的东西。)如果任何这些套接字上发生任何事件,select
调用将返回并告诉您在哪个套接字上发生了什么。然后处理这些事件(读取传入数据、写入更多数据、处理错误等),然后循环返回并等待更多事件。
select
一般来说,关于事件驱动编程有很多很好的教程。此外,还有更有效的(尽管是特定于平台的)系统调用和工具,例如poll
, epoll
, kqueue
,inotify
等。
当然,有许多优秀的库使用最有效的特定于平台的方法,并为您提供(大部分)简单的界面来使用。libev、libevent和libuv等库。
如果您不需要 Windows 可移植性,我建议您使用libev。libevent有点老和更大,但有更多的功能。如果您确实需要支持 Windows,请使用libuv。
但处理连接及其事件只是解决方案的一部分。正如对您的问题和其他答案的评论所提到的,在您收到连接上的事件后,一种常见的(更不用说明智和可扩展的)解决方案是将数据和活动的实际处理移交给其他线程.
通常所做的是拥有一个工作线程池。在您的主线程中,您会收到连接上的偶数通知(通过select
等),但不是在主线程中完成所有工作,而是将工作项交给一个工作线程来处理并生成结果并将结果发回。
这里的一个关键问题是主线程(select
线程)和工作线程之间的通信。有时,会使用某种形式的线程安全共享队列。主线程将工作项目(事件、请求等)放入此队列,所有工作线程在不忙时尝试从该队列中获取项目。
请注意,您在上面阅读的所有内容都已简化为最低限度。在现实世界中,编写这种低延迟和可扩展的系统是一项具有挑战性和复杂性的任务,因此如果您确实需要性能和/或正在处理大量数据,您可能需要进行(大量)更多研究和许多客户。
yzt 的答案已经很好,但这是另一种“混合”方法。
不要为每个连接使用单独的线程,而是仅使用线程池中的线程来实际处理流量。在您轮询 select() 的中央循环中,您将工作分派到下一个空闲线程。如果没有更多线程可用,您要么简单地等待一个可用,要么生成更多线程来处理额外的流量。
这提供了更好的延迟,因为除非线程池耗尽并且您不想产生更多线程,否则下一个套接字不必等待。
您的问题被标记为boost-asio
这样。您可以使用它来处理超过 100 个连接和超过 10000 个连接(取决于您的程序 CPU 和内存使用情况)。查看boost 示例以研究如何执行此操作。我认为您的 telnet 案例可以从示例 tcp echo 服务器开始