【问题标题】:Building Multi threaded TCP/IP Server构建多线程 TCP/IP 服务器
【发布时间】:2011-01-18 09:35:26
【问题描述】:

我想构建一个最多可供 100 个并发客户端使用的 TCP/IP 服务器,但仍然不知道如何开始。

至少我需要这个服务器:

  1. 监听客户端,并将它们全部存储在数组或列表中。
  2. 对于每个客户端,它需要根据自己的客户端状态来接收和发送数据。
  3. 当有人连接或断开连接时,服务器应该更新客户端列表。
  4. 更喜欢使用 GUI 作为服务来管理它。

任何人都可以帮助如何开始,我查看了 indy 示例但他们没有帮助,还寻找了大多数组件但仍在搜索。

【问题讨论】:

  • 正如其他问题中提到的,如果您的实际客户端数量大于 1000,如果 Indy 设置为每个并发客户端一个线程,则您可能会使用大量内存。使用基于异步或延续的方法,您可以用更少的线程处理更多的连接。 stackoverflow.com/questions/37185/…
  • 您可能想查看 Francois Piette 的 ICS 组件并进行比较。有些人觉得 Indy 更适合他们,有些人觉得 ICS 更适合他们。
  • 我认为阻塞方法在大多数情况下都是错误的。在会话/对象的整个生命周期内让线程处于活动状态是完全错误的。您只需要一个 taks / 线程池,并且对于每个单独的请求,您都使用该池中的一个线程。这样,您将拥有与同时运行的许多并发请求一样多的线程。祝你好运,甚至达到 100。
  • @Runner,Indy 提供了 3 种调度器:基于线程的调度器,基于线程池的基于线程的调度器,以及基于光纤的调度器。默认情况下,Indy 使用简单的基于线程的调度程序。如果需要线程池,只需将 TIdSchedulerOfThreadPool 实例分配给 IdTCPServer.Scheduler 属性即可。

标签: delphi tcp delphi-2010


【解决方案1】:

我最近遇到了这个问题。这是一个使用 EPOLL 管理数百个多播套接字的 C++ 示例的链接。你的应该是 TCP/IP,但这只是一个简单的改变。

无论您选择哪种方法,对于 100 多个并发套接字/客户端,您都需要使用依赖于 poll、select 或首选 epoll(如果您的平台支持)的线程模型。

http://anthonyvirtuoso.com/public/dokuwiki/doku.php/projects:multiplexreceiverepoll

@jfawcett - 在超过 50 个 FD 上进行选择会带来相当大的 CPU 占用,具体取决于您实际执行选择的频率。在我上面的评论中,示例类最初使用 select,但在看到 CPU 成本(valgrind w/callgrind)后,我切换到了 epoll。但当然 select 是一个有效的选项。

【讨论】:

    【解决方案2】:

    在 Windows 平台上,您最好避免 select 处理大量并发连接(虽然 100 不是大量连接)。但是,avirtuos 是正确的,因为您希望避免任何“每个连接线程”模型。在 Windows 上最有效的方法是使用重叠的 I/O 和 I/O 完成端口。这允许您使用少量线程(可能是 2 或 3 个)管理成千上万个连接。

    我没有任何 Delphi 经验,所以我不知道与 C++ 代码交互有多么容易,但我有一个免费的 C++ I/O 完成端口服务器框架(可从here 获得),它可以在至少,向您展示标准 Win32 I/O 完成端口 API 的操作方式。它可能对你有用,你也许可以在 Delphi 中做类似的事情。

    【讨论】:

      【解决方案3】:

      Indy 是您的最佳选择:1000 个客户端并不多:我确实开发了一个必须为 4 到 5 k 客户端提供服务的服务器,它的工作原理很吸引人。

      1. 监听客户端,并将它们全部存储在数组或列表中。 + 3. 当有人连接或断开连接时,服务器应该更新客户端列表。

      --> 至于客户端列表,您可以遍历存储所有“活动”线程的 TidTCPServer(9.0 版)的 TThreadList 成员,每个线程都“等效于”客户端连接,尽管线程可能比客户端的连接,但您可以通过设置适当的连接超时值来解决此问题。如果您愿意,您还可以维护自己的客户端列表(例如从 TList 继承或创建一个 Generics.Collection):您可以在 onConnect 事件之后添加客户端信息(tidPeerThread 类公开所有客户端信息:IP ...)

      然后,您将定期遍历此列表并检查活动连接(类似 ping 命令)并杀死/删除所有僵尸。

      [独立文档] 对等线程连接尝试的事件处理程序。

      属性 OnConnect: TIdServerThreadEvent;

      说明

      OnConnect 是 TIdServerThreadEvents 的事件处理程序。 OnConnect 在 TIdPeerThread 尝试连接到 TIdTCPServer 时发生。

      OnConnect 接收 AThread 作为参数,表示请求连接的 TIdPeerThread 线程。

      将 TIdServerThreadEvent 事件处理程序分配给 OnConnect。 [/indy 文档]

      1. 对于每个客户端,它需要根据它的客户端状态来接收和发送数据: --> 查看聊天客户端和服务器演示源代码以获取详细示例。

      2. 更喜欢使用 GUI 作为服务来管理它:您可以开发一个服务应用程序,将其所有活动记录在一个数据库中,另一个应用程序将访问该数据库并显示所有可用的统计信息(客户编号...)。

      这里是 Indy 9.0 的链接(来源和文档): http://www.indyproject.org/downloads/Indy_9_00_14_src.zip http://www.indyproject.org/downloads/Indy-9-0-Help-WinHelp.zip

      这是一本 Indy 书,虽然我认为您在阅读文档后不需要它:Indy in depth : http://www.atozed.com/Indy/Book/index.EN.aspx

      在这里寻找一个好的教程: http://www.devarticles.com/c/a/Delphi-Kylix/Creating-Chat-Application-with-Borland-DelphiIndy-The-Client/

      祝你好运

      【讨论】:

        【解决方案4】:

        另一个 Delphi 库选项是 synapse,它提供了一个可以轻松扩展的简单框架。提供的文件中有一个IOCPPool 演示,可能会有所帮助。

        Synapse 更像是一个类框架,而不是一个组件库。有一个充满活力和活跃的用户社区,随时准备支持任何挑战。我在 Delphi 2010 中使用这个库没有任何问题(虽然我使用的是 SVN 的最新开发版本)。

        由于它们不是组件,因此在简单的控制台应用程序或 Windows 服务中使用这些类非常容易。

        【讨论】:

        • 谢谢你,我研究了 synapse,但我发现它比 indy 更难,而且没有多少文档或样本可以帮助我入门。
        • 默认安装中有相当多的演示以及贡献的文件应该有所帮助。当所有其他方法都失败时,请在此处或突触列表服务器上提出您的具体问题。
        【解决方案5】:

        kbmMW 和 RemObjects 是一些非常适合您(以及更多)情况的优秀组件集。我认为还有其他一些不错的集合,您可以搜索存档或在 Embarcaderos 的 Delphi 第三方工具新闻组中提问。您可以在此处搜索档案: http://codenewsfast.com/

        【讨论】:

          【解决方案6】:

          您需要使用内部多线程的TidTCPServer。无需您管理线程。一切都是透明的,因此以您为一个客户编写应用程序的方式,以(几乎)为许多客户编写应用程序的方式相同。请参阅OnConnect 事件。有一个 TidContext 参数,里面有一个 TThreadList。您可以使用该事件“注册”/将您的客户添加到您的自定义数组/列表中,并使用OnDisconnect 删除客户。

          OnExecute 事件在服务器收到消息时触发。使用其参数读取发送的消息。

          另外,您需要使用TidTCPClient 作为您的客户端的另一个应用程序。在此应用程序中,您将设置服务器的地址(请参阅 Host 属性)以及应该与服务器的端口匹配的端口。你应该调用Connect(当服务器运行时)并发送你有SendCmd方法的字符串。 (如果需要,请参阅 IOHandler.WriteLn)

          还有其他事情,但我认为这足以让您入门。您也可以在 Indy 团队成员漂浮的 .Delphi.Winsock 论坛中的 Embarcadero 论坛中发帖。或者您可以直接在 .Delphi.Non-Technical 中询问,那里的人会指导您。

          另一种方法是 DataSnap,它是一个比 Indy 更面向对象的层(不要与 DBX 混淆),它为您提供 JSON、REST 和其他好东西。请参阅here 的小评论。

          【讨论】:

          • 如何获取clientID?,因为我有外部偶数,我应该响应特定的客户端。
          • Execute 事件的参数为AContext: TidContext。所以你有AContext.Connection,你有足够的方法向客户端发送数据,例如。 AContext.Connection.SendCmd。此外,您还有AContext.Connection.Socket 属性(IIRC),它的(子)属性之一是发送消息的客户端的IP 地址。如果您想进行日志记录等,可以将其用作“clientID”。
          【解决方案7】:

          100 个套接字并不多。您可能会遇到使用 epoll() 之类的所有麻烦,但对于这种情况,我只需设置一个包含所有套接字的单个 FD_SET,在整个集合上选择(),然后检查每个,并处理它们顺序。如果我必须做一些可能很耗时的事情,我会为消息处理程序使用线程池。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-01-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多