【发布时间】:2022-10-24 08:34:49
【问题描述】:
我正在创建一个必须处理 1000 多个客户端的服务器,我目前使用的方法是:
一个线程将使用WSAAccept 处理传入连接,它有一个线程池,其中每个线程将使用WSAPoll 一次处理多个客户端。
例如,如果 Client 刚刚连接,Server 会找到一个空闲的 poller 线程并将其添加到该 poller 线程的WSAPoll 的 fdset 中,这样 poller 线程将处理新的 Client 连接。
poller 线程将使用非阻塞 WSAPoll 来处理连接,但随后它将使用(阻塞)recv() 来接收数据包。
Server: WSAAccept
Thread Poller #1: WSAPoll [1, 2, 3, 4, 5] // max out
recv[1, 2, 3, 4, 5]
Thread Poller #2: WSAPoll [6, 7, 8, 9, 10] // max out
recv[6, 7, 8, 9, 10]
Thread Poller #3: WSAPoll [11, 12] // free
recv[11, 12]
// create more pollers if all maxed out
它对我来说工作得很好,但后来我遇到了一个(可能是)更好的解决方案与WSARecv 重叠的套接字.
这里的想法是使用非阻塞 WSARecv 完成回调而不是 WSAPoll
CompletionCallback(){ WSARecv(socket, CompletionCallback); }
Loop:
socket = WSAAccept();
WSARecv(socket, CompletionCallback); // CompletionCallback will handle the connection.
因此无需多线程和/或WSAPoll
我做了一个 PoC,它似乎工作得很好,但它是单线程的,我想知道与旧方法相比,它的性能如何。
谢谢!
【问题讨论】:
-
与所有性能问题一样:您必须对其进行分析并检查它对于您的用例是否足够快。尽管只使用一个线程,但您不可避免地会遇到性能壁垒——就原始吞吐量而言,单个线程只能处理这么多。您将获得的性能将取决于很多因素(每个连接每秒需要处理多少数据,一次将有多少连接处于活动状态,服务器的硬件等......) - 唯一的方法您可以通过对其进行分析来确定它会足够快。
-
一般来说,如果您需要一个真正高性能的套接字服务器,我建议您使用I/O Completion Ports - 它们也适用于完成回调,但是您可以拥有一个专用于处理完成的整个线程池,而不是在单个线程上运行它们。
-
AcceptEx 也非常有用,因为它允许您一次批量处理 3 个操作(接受连接、获取本地和远程地址以及读取第一个数据块) - 另外它使用重叠 i/o,因此可以完美运行带有完成端口。 (您还可以针对同一个套接字设置多个待处理的
AcceptEx,以准备突发的传入连接请求) -
感谢您提供有用的信息,我也考虑过使用线程池。不幸的是,由于我的特殊需要,我不能使用任何其他框架,必须自己编写
-
仅供参考,
WSAPoll()is broken,所以无论如何你都不应该使用它。
标签: c++ networking server tcp winsock