.Net 4.5 TCP 服务器可扩展到数千个连接的客户端

.Net 4.5 TCP Server scale to thousands of connected clients

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

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

我还需要能够 select 现有客户端在 windows 表单应用程序中发送和接收消息(大于 500 字节)消息。

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

我的结论是:

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

我不确定的是:

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

感谢您的阅读。我很想听到建设性的想法和/或最好的链接 practices/examples。自从我用 c# 编码以来已经有一段时间了,如果我的一些问题很明显,我深表歉意。任务,async/await 对我来说是新的! :-)

I need to build a TCP server using C# .NET 4.5+

嗯,首先要确定的是是否必须是base-bonesTCP/IP。 如果您可能可以,编写一个使用更高级别抽象的程序,例如 SignalR 或 WebAPI。如果您可以使用 WebSockets (SignalR) 编写一个,那就去做吧,永远不要回头看。


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

SocketAsyncEventArgs - Is complex and really only needed for very large systems, BTW what constitutes a very large system? :-)

就连接数而言,它不是一个 "large" 系统。更多的是系统中有多少流量的问题——每秒reads/writes的数量。

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

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

关于问题:

Should I use a ManualResetEvent when interacting with the TCP Client collection? I presume the asyc events will need to lock access to this collection.

如果您正在使用 TAP-based wrappers, then await will resume on a captured context by default. I explain this in my blog post on async/await

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

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

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

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

Best way to detect a disconnected client after I have called BeginReceive. I've found the call is stuck waiting for a response so this needs to be cleaned up.

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

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

哦,SignalR 会为您处理这件事。 :)

Sending messages to a specific TCP Client. I'm thinking function in custom TCP session class to send a message. Again in an async model, would I need to create a timer based process that inspects a message queue or would I create an event on a TCP Session class that has access to the TcpClient and associated stream? Really interested in opinions here.

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

哦,SignalR 会为您处理这件事。 :)

I'd like to use a thread for the entire service and use non-blocking principals within, are there anythings I should be mindful of espcially in context of 1. ManualResetEvent etc..

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


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