构建多线程TCP/IP服务器

11

我想建立一个TCP/IP服务器,最多可供100个并发客户端使用,但还不确定如何入手。

至少需要满足以下要求:

  1. 监听客户端,并将它们存储在数组或列表中。
  2. 对于每个客户端,它需要根据其客户端状态接收和发送数据。
  3. 当有人连接或断开连接时,服务器应更新客户端列表。
  4. 最好作为服务来运行,并带有GUI以进行管理。

有没有人能帮忙入手,我看了indy示例但它们没有帮助,也找了大多数组件但仍在搜索。


正如其他问题所提到的,如果您的真实客户数量超过1000,则可能会出现巨大的内存使用情况,如果Indy设置为每个并发客户端一个线程。通过异步或基于继续的方法,您可以使用更少的线程处理更多的连接。 - Warren P
1
你可能想要查看Francois Piette的ICS组件并进行比较。有些人发现Indy更适合他们,有些人则发现ICS更适合他们。 - Warren P
我认为在大多数情况下,阻塞方法都是错误的。让一个线程在整个会话/对象的生命周期内保持活动状态只是纯粹的错误。你只需要一个任务/线程池,对于每个单独的请求,你使用来自该池的线程。这样,你将拥有与同时运行的并发请求数量相同的线程。祝你好运,即使达到100个也不容易。 - Runner
1
@Runner,Indy提供了3种类型的调度器:基于线程、带有线程池的基于线程和基于纤程。默认情况下,Indy使用简单的基于线程的调度器。如果您需要一个线程池,您只需要将TIdSchedulerOfThreadPool实例分配给您的IdTCPServer.Scheduler属性即可。 - vcldeveloper
7个回答

8
您需要使用内部多线程的TidTCPServer。您无需管理线程,一切都是透明的,因此您编写单个客户端应用程序的方式(几乎)与编写多个客户端的方式相同。请参见OnConnect事件。其中有一个TidContext参数,其中包含一个TThreadList。您可以使用该事件将客户端“注册”/添加到自定义数组/列表中,并使用OnDisconnect删除客户端。
当服务器收到消息时,将触发OnExecute事件。使用其参数读取发送的消息。
此外,您需要另一个应用程序作为客户端,使用TidTCPClient。在此应用程序中,您将设置服务器的地址(请参阅Host属性)以及应与服务器的端口匹配的端口。在服务器运行时,应调用Connect并使用SendCmd方法发送字符串。(如果需要,还可以查看IOHandler.WriteLn
还有其他事情,但我认为这已足以让您入门。您还可以在Embarcadero的论坛中发布.Delphi.Winsock论坛,在那里Indy团队成员会浮现。或者您可以直接在.Delphi.Non-Technical中询问,那里的人会指导您。
另一种方法是DataSnap,它是Indy上的更面向对象的层(不要与DBX混淆),可为您提供JSON、REST和其他好处。请参见此处进行简要评论。

我该如何获取客户端ID?因为我有外部事件,我需要响应特定的客户端。 - DelphiDev
Execute 事件有一个参数 AContext:TidContext。因此,您可以使用 AContext.Connection,其中有足够的方法向客户端发送数据,例如 AContext.Connection.SendCmd。此外,您还可以使用 AContext.Connection.Socket 属性(如我所记得的那样),其中包含其(子)属性之一是发送消息的客户端的 IP 地址。如果您想记录日志等,可以将其用作“clientID”。 - John Thomas

3
在Windows平台上,如果有大量并发连接(虽然100个连接不算多),最好避免使用select。然而,avirtuos是正确的,你要避免任何“每个连接一个线程”的模型。在Windows上最有效的方法是使用重叠I/O和I/O完成端口。这样可以用少量线程(可能是2或3个)管理数万个连接。
我没有Delphi经验,所以不知道如何与C++代码交互,但我有一个免费的C++ I/O完成端口服务器框架(可从这里获取),它至少会向你展示标准Win32 I/O完成端口API的操作方式。这可能对你有用,你可能可以在Delphi中做类似的事情。

3
另一个 Delphi 库选项是 synapse,它提供了一个简单的框架,可以轻松扩展。在贡献的文件中提供了 IOCPPool 演示,可能会有所帮助。
Synapse 更像是一组类的框架,而不是组件库。拥有活跃的用户社区,随时准备支持任何挑战。我在 Delphi 2010 中使用这个库没有任何问题(尽管我使用了来自 SVN 的最新开发版本)。
由于它们不是组件,因此在简单的控制台应用程序或 Windows 服务中使用这些类非常容易。

谢谢,我看了Synapse,但我发现它比Indy更难,而且没有太多文档或示例可以让我开始。 - DelphiDev
默认安装中有相当多的演示可以帮助您,还有一些贡献文件。如果其他方法都失败了,在这里或同步列表中提出您具体的问题。 - skamradt

2
Indy是您的最佳选择:1000个客户并不多。我曾经开发过一个服务器,需要为4-5k个客户提供服务,而且它运行得非常好。
1. 监听客户端,并将所有客户存储在数组或列表中。+3. 当有人连接或断开连接时,服务器应更新客户端列表。
-> 关于客户端列表,您可以循环遍历TidTCPServer(版本9.0)的TThreadList成员,该成员存储所有“活动”的线程,每个线程“相当于”一个客户端连接,尽管线程可能会超过客户端的连接,但您可以通过设置适当的连接超时值来解决这个问题。如果您想要自己维护客户端列表(例如从TList继承或创建Generics.Collection),则可以在onConnect事件之后添加客户端信息(tidPeerThread类公开所有客户端信息:IP等)。
然后,您会定期循环遍历此列表,并检查所有活动连接(如ping命令),并杀死/删除所有僵尸进程。
[indy文档] 对等线程连接尝试的事件处理程序。
属性OnConnect:TIdServerThreadEvent;
描述

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

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

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

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

  2. 最好作为带有GUI的服务进行管理:您可以开发一个服务应用程序,将其所有活动记录在DB中,并开发第二个应用程序,该应用程序将访问该DB并显示所有可用统计信息(客户端数量...)。

以下是 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 : 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/ 祝您好运!

1

100个套接字并不算多。你可以费尽心思地使用像epoll()这样的东西,但对于这种情况,我只会设置一个包含所有套接字的FD_SET,选择整个集合,然后逐个检查每个套接字,并按顺序处理它们。如果我必须执行一些可能耗时的操作,我会为消息处理程序使用线程池。


0

我最近遇到了这个问题。这里有一个使用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肯定是一个有效的选项。


0

一些非常优秀的组件集,适用于像你这样的情况(以及更多),包括 kbmMWRemObjects。还有其他一些不错的组件集,你可以在 Embarcadero 的 Delphi 第三方工具新闻组中搜索存档或提问。你可以在这里搜索存档:http://codenewsfast.com/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接