【问题标题】:.Net 4.5 TCP Server scale to thousands of connected clients.Net 4.5 TCP 服务器可扩展到数千个连接的客户端
【发布时间】:2016-08-05 16:31:33
【问题描述】:

我需要使用 C# .NET 4.5+ 构建一个 TCP 服务器,它必须能够轻松处理至少 3,000 个连接的客户端,这些客户端将每 10 秒发送一次消息,消息大小为 250 到 500 字节。

数据将被卸载到另一个进程或队列进行批处理和记录。

我还需要能够选择现有客户端以在 Windows 窗体应用程序中发送和接收消息(大于 500 字节)消息。

我以前没有构建过这样的应用程序,所以我的知识基于我在网上找到的各种问题、示例和文档。

我的结论是:

  1. 非阻塞异步是要走的路。远离创建多个线程和阻塞 IO。
  2. SocketAsyncEventArgs - 很复杂,真的只需要非常大的系统,顺便说一句,什么是非常大的系统? :-)
  3. BeginXXX 方法就足够了 (EAP)。
  4. 使用 TAP 我可以简化 3. 通过使用 Task.Factory.FromAsync,但它只会产生相同的结果。
  5. 使用全局集合来跟踪连接的 tcp 客户端

我不确定的:

  1. 与 TCP 客户端集合交互时是否应该使用 ManualResetEvent?我认为 asyc 事件需要锁定对这个集合的访问。
  2. 我调用了 BeginReceive 之后检测断开连接的客户端的最佳方法。我发现呼叫卡在等待响应,因此需要清理。
  3. 向特定 TCP 客户端发送消息。我正在考虑自定义 TCP 会话类中的函数来发送消息。再次在异步模型中,我是否需要创建一个基于计时器的进程来检查消息队列,或者我是否需要在可以访问 TcpClient 和关联流的 TCP Session 类上创建一个事件?真的很感兴趣这里的意见。
  4. 我想为整个服务使用一个线程并在其中使用非阻塞主体,有什么我应该特别注意的,尤其是在 1.ManualResetEvent 等的上下文中。

感谢您的阅读。我很想听到建设性的想法和/或最佳实践/示例的链接。自从我用 c# 编写代码以来已经有一段时间了,如果我的一些问题很明显,我深表歉意。任务,异步/等待对我来说是新的! :-)

【问题讨论】:

  • 在 Internet 上搜索“IO Completion Ports .NET”
  • @selbie 抱歉,我可能不清楚,我想在 C# 中执行此操作。您是否建议我使用 C++ 或在 .NET 中实现的 IOCP 概念,我只是认为在 .NET 4.5 中会有更简单且性能更好的替代方案。有一个代码项目示例可以做到这一点,但我再次认为事件/任务驱动的方法就足够了。
  • 所有异步套接字 API 都已使用 IOCP。

标签: sockets async-await .net-4.5 tcplistener


【解决方案1】:

我需要使用 C# .NET 4.5+ 构建一个 TCP 服务器

嗯,首先要确定的是它是否必须是基础 TCP/IP。 如果您可能可以,请编写一个使用更高级别抽象的代码,例如 SignalR 或 WebAPI。如果您可以使用 WebSockets (SignalR) 编写一个,那么就这样做,永远不要回头。


你的结论听起来不错。只是一些注意事项:

SocketAsyncEventArgs - 很复杂,真的只需要非常大的系统,顺便说一句,什么构成了非常大的系统? :-)

就连接数量而言,它并不是一个“大型”系统。更多的是系统中有多少流量的问题——每秒读取/写入的次数。

SocketAsyncEventArgs 所做的唯一一件事就是让您的 I/O 结构可重用。 Begin*/End* (APM) API 将为每个 I/O 操作创建一个新的 IAsyncResult,这可能会对垃圾收集器造成压力。 SocketAsyncEventArgs 本质上与IAsyncResult 相同,只是它是可重复使用的。请注意,'net 上有一些使用 SocketAsyncEventArgs API 的示例没有重用 SocketAsyncEventArgs 结构,这完全是荒谬的。

这里没有任何指导方针:较重的硬件将能够使用 APM API 获得更多流量。作为一般规则,您应该先构建一个准系统 APM 服务器并对其进行负载测试,并且仅当它在您的目标服务器的硬件上不起作用时才迁移到 SAEA。

关于问题:

在与 TCP 客户端集合交互时是否应该使用 ManualResetEvent?我认为 asyc 事件需要锁定对这个集合的访问。

如果您使用基于 TAP 的包装器,则默认情况下 await 将在捕获的上下文中恢复。我在my blog post on async/await 中解释了这一点。

您可以在这里采取几种方法。我已经成功编写了一个可靠且高性能的单线程 TCP/IP 服务器;现代代码的等价物是使用my AsyncContextThread class之类的东西。它提供了一个上下文,默认情况下会导致await 在同一线程上恢复。

单线程服务器的好处是只有一个线程,因此不需要同步或协调。但是,我不确定单线程服务器的扩展性如何。您可能想尝试一下,看看它可以承受多少负载。

如果你确实需要多个线程,那么你可以在线程池上使用async 方法; await 将没有捕获的上下文,因此将在线程池线程上恢复。在这种情况下,是的,您需要协调对任何共享数据结构的访问,包括您的 TCP 客户端集合。

请注意,SignalR 将为您处理所有这些。 :)

在我调用 BeginReceive 之后检测断开连接的客户端的最佳方法。我发现呼叫卡在等待响应,因此需要清理。

这是half-open problem,我会在我的博客上详细讨论。解决此问题的最佳方法(IMO)是定期向每个客户端发送“noop”keepalive 消息。

如果无法修改协议,那么下一个最佳解决方案是在无通信超时后关闭连接。这就是 HTTP“持久”/“保持活动”连接决定关闭的方式。还有另一种可能的解决方案(更改套接字上的 keepalive 数据包设置),但它并不容易(需要 p/Invoke)并且存在其他问题(路由器并不总是尊重,并非所有 OS TCP/IP 堆栈都支持等)。

哦,SignalR 会为您处理这个问题。 :)

向特定的 TCP 客户端发送消息。我正在考虑自定义 TCP 会话类中的函数来发送消息。再次在异步模型中,我是否需要创建一个基于计时器的进程来检查消息队列,或者我是否需要在可以访问 TcpClient 和关联流的 TCP Session 类上创建一个事件?真的很感兴趣这里的意见。

如果您的服务器可以向任何客户端发送消息(即,它不仅仅是一个请求/响应协议;服务器的任何部分都可以向任何客户端发送消息而无需客户端请求更新),那么是的,您需要正确的传出请求队列,因为您不能(可靠地)在套接字上发出多个并发写入。不过,我不会让消费者基于计时器。有可用的异步兼容生产者/消费者队列(如BufferBlock<T> from TPL Dataflowit's not that hard to write one,如果你有async-compatible locks and condition variables)。

哦,SignalR 会为您处理这个问题。 :)

我想为整个服务使用一个线程并在其中使用非阻塞主体,有什么我应该特别注意的,尤其是在 1.ManualResetEvent 等的上下文中。

如果您的整个服务是单线程的,那么您根本不需要任何协调原语。但是,如果您确实使用线程池而不是同步回主线程(出于可伸缩性原因),那么您将需要进行协调。我有一个coordination primitives library,您可能会觉得它很有用,因为它的类型同时具有同步和异步 API。例如,这允许一种方法阻塞锁,而另一种方法想要异步阻塞锁。


您可能已经注意到 SignalR 的一个反复出现的主题。如果可以,请使用它!如果您必须编写一个简单的 TCP/IP 服务器并且不能使用 SignalR,那么将您的初始时间估算值加倍。严重地。然后你可以通过我的TCP/IP FAQ blog series 开始痛苦的 TCP 之路。

【讨论】:

  • 谢谢,非常感谢您花时间回答我的问题。请在我明天消化这些信息并回复您的同时与我交谈。我也会研究 SignalR,我没有意识到 ASP.NET 会是一个合适的竞争者。为了澄清,我需要使用相同的套接字将消息发送回客户端,它们建立连接而不是服务器。
  • 您知道任何基于 SignalR 构建的“TCP”服务器示例吗?我需要访问原始 TCP 套接字,因为客户端只能通过 TCP 进行通信,因此我的响应会写回它们的初始连接/套接字。
  • SignalR 使用 WebSocket 协议,因此不能与原始 TCP/IP 客户端一起使用。需要更改客户端以使用 SignalR(或至少 WebSockets)。
  • @Nicholas:对不起,我不明白你的要求。
  • @StephenCleary 是否有任何不使用 SignalR 客户端库的示例? (基本上使用 System.Net.Websockets)
猜你喜欢
  • 1970-01-01
  • 2021-08-12
  • 2022-01-16
  • 2019-06-06
  • 2019-06-18
  • 2017-02-24
  • 2022-01-17
  • 2017-05-06
  • 1970-01-01
相关资源
最近更新 更多