在创建将同时为 200 多个客户端发送/接收任务的服务器时,使用 select 或多线程(或两者)是否更好

问题描述 投票:0回答:2

我正在创建一个服务器,它将同时从 200 多个客户端发送和接收任务(将来可能会有更多客户端)。客户端上还将有后台引擎,它们将执行任务并向服务器发送响应,而无需先询问。我预计双向会有大量信息传输。我一直在研究多线程和使用 select 函数,我想知道给定项目的一些参数,哪个选项(或组合)将是基于流量的最有效的可扩展解决方案可能会发生。

如有任何建议,我们将不胜感激。我很乐意回答任何问题以提供更清晰的信息。

c multithreading sockets posix-select
2个回答
3
投票

两种方法都有效;至于哪个“更好”,这在很大程度上取决于您如何定义“更好”这个词。

  • 单线程方法避免了出现竞争条件或死锁问题的任何机会,因为这些问题本质上不会在单线程程序中发生。在多线程程序中,您必须非常小心数据锁定模式,否则您会发现自己试图调试每隔几天/几周/几个月才发生一次的非常神秘的故障。

  • 另一方面,单线程方法限制您只能使用单个核心;它将无法利用现代多核 CPU 来提高并行速度。

  • 第三方面,如果各种线程/连接经常需要访问任何共享/可变数据结构,多线程方法可能会变得很麻烦(并失去加速潜力)。在“共享数据瓶颈”场景中,线程可能会花费大量时间等待锁定互斥体,然后您基本上会回到使用单核。如果每个连接独立于其他连接运行(例如,作为简单 Web 服务器的一部分)并且不需要与其他线程交互,那么这不应该是一个问题。

  • 多线程允许您使用阻塞 I/O(这比非阻塞 I/O 更容易实现),但是阻塞 I/O 限制了您对线程的控制(例如,如何让线程干净地退出,或者如果在

    recv()
    调用中无限期地阻塞,采取其他非客户端启动的操作?对于这个问题没有任何好的解决方案,只有糟糕的解决方案)

  • 单线程要求您使用非阻塞 I/O(否则,当服务器在

    send()
    recv()
    调用中被阻塞时,单个无响应的客户端可以停止对所有其他客户端的服务)和非阻塞正确执行 I/O 很棘手,因为您必须优雅地处理部分读取和部分写入。

  • 如果您的程序需要执行大量计算或文件 I/O,请注意,单线程设计将迫使所有客户端等待任何客户端的计算(或 I/O)完成。在多线程设计中,OTOH,当客户端 A 忙于从磁盘读取或处理数字时,客户端 B 到 Z 可以继续在其他内核/线程上获得服务。

  • 生成和维护线程的开销因操作系统而异。如果您要同时运行数百个线程,您可能需要首先验证您的目标操作系统(和硬件)是否能够有效地处理该负载。 (您可以通过线程池减少生成和获取线程的开销,但会增加 RAM 使用量)

我个人更喜欢单线程/非阻塞 I/O 方法,因为如果您希望程序能够干净可靠地关闭(您应该希望这样,只要这样您就可以),阻塞 I/O 就会出现问题例如在 valgrind 下进行内存泄漏测试)。如果单核性能不足,通常可以相当简单地将 1 个线程上的“handle-N-sockets-on-1-threads”设计扩展到更强大的“handle-N-sockets-on-each-of-M-threads”设计,然后你可以尝试使用不同的 N 和 M 值,直到找到能够为你提供最佳性能的值(例如,通过将 M 设置为主机上的核心数,并将新接受的套接字分发给任一线程)当前处理的套接字数量最少)


0
投票

我曾经用Java编写了一个程序,一个聊天应用程序,与服务器建立的每个连接都代表服务器中的一个新线程,以管理有问题的客户端。

在 Server 类中,有一个静态变量,用于管理连接了哪些客户端。

我不知道推荐不同的技术是否是回答您问题的正确方法,但我认为,对于您的情况,看看 Erlang/Elixir 平台是个好主意,前提是能够同时容纳很多客户。

目前,像 Whatsapp 这样的大公司使用 Erlang 和 Discord Elixir。

希望我的回答对您有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.