Nic*_*las 1 sockets tcplistener async-await .net-4.5
我需要使用C#.NET 4.5+构建TCP服务器,它必须能够舒适地处理至少3,000个连接的客户端,这些客户端每10秒发送一次消息,消息大小为250至500字节。
数据将被分流到另一个进程或队列以进行批处理和记录。
我还需要能够选择一个现有客户端,以便在Windows窗体应用程序中发送和接收消息(大于500字节)。
我之前没有构建过这样的应用程序,因此我的知识是基于我在网上找到的各种问题,示例和文档。
我的结论是:
我不确定的是:
感谢您的阅读。我渴望听到建设性的想法或与最佳做法/示例的链接。自从我用C#编码以来已经有一段时间了,如果我的某些问题很明显,我深表歉意。任务,异步/等待对我来说是新的!:-)
我需要使用C#.NET 4.5+构建TCP服务器
好吧,首先要确定的是它是否必须是基本的TCP / IP。如果有可能可以,写一个使用一个更高层次的抽象,如SignalR或的WebAPI。如果您可以使用WebSockets(SignalR)编写一个代码,那么就不要回头。
您的结论听起来不错。只是一些注意事项:
SocketAsyncEventArgs-是复杂的,实际上仅在非常大的系统中才需要,顺便说一句,什么构成一个非常大的系统?:-)
就连接数量而言,它并不是一个“大型”系统。更多的问题是系统中的流量-每秒的读取/写入次数。
唯一要做的SocketAsyncEventArgs就是使您的I / O结构可重用。的Begin*/ End*(APM)的API将创建一个新的IAsyncResult为每个I / O操作,并且这可能导致在垃圾收集器的压力。SocketAsyncEventArgs本质上与相同IAsyncResult,只是可重用。请注意,网上有一些使用SocketAsyncEventArgsAPI 而不重用SocketAsyncEventArgs结构的示例,这完全是荒谬的。
而且这里没有准则:较重的硬件将能够使用APM API来获得更多流量。通常,您应该构建准系统APM服务器并首先对其进行负载测试,并且如果它不能在目标服务器的硬件上运行,则仅移至SAEA。
接下来的问题:
与TCP客户端集合进行交互时,应该使用ManualResetEvent吗?我认为asyc事件将需要锁定对此集合的访问。
如果您使用的是基于TAP的包装器,则await默认情况下将在捕获的上下文中恢复。我在async/的博客文章中await对此进行了解释。
您可以在此处采取两种方法。我已经成功地编写了可靠的高性能单线程TCP / IP服务器;现代代码的等效方法是使用类似我的AsyncContextThread类。它提供了一个上下文,await默认情况下将导致在同一线程上恢复。
单线程服务器的好处是只有一个线程,因此不需要同步或协调。但是,我不确定单线程服务器的扩展能力。您可能需要尝试一下,看看它可以承受多少负载。
如果确实需要多个线程,则可以只async在线程池上使用方法;await将没有捕获的上下文,因此将在线程池线程上继续。在这种情况下,是的,您需要协调对任何共享数据结构(包括TCP客户端集合)的访问。
请注意,SignalR将为您处理所有这一切。:)
在我调用BeginReceive之后,检测断开连接的客户端的最佳方法。我发现呼叫被卡在等待响应,因此需要清理。
这是半开放式的问题,我将在博客中进行详细讨论。解决此问题的最佳方法(IMO)是定期向每个客户端发送“空转” keepalive消息。
如果无法修改协议,那么下一个最佳解决方案是在无通信超时后关闭连接。这就是HTTP“持久” /“保持活动”连接决定关闭的方式。还有另一种可能的解决方案(更改套接字上的keepalive数据包设置),但是它不那么容易(需要p / Invoke),并且存在其他问题(路由器并不总是尊重,也不是所有OS TCP / IP堆栈都支持等)。
哦,SignalR会为您处理。:)
将消息发送到特定的TCP客户端。我在考虑自定义TCP会话类中的功能以发送消息。同样,在异步模型中,我是否需要创建一个基于计时器的进程来检查消息队列,还是在可以访问TcpClient和关联流的TCP Session类上创建事件?真的对这里的意见感兴趣。
如果您的服务器可以将消息发送到任何客户端(即,不仅是请求/响应协议;服务器的任何部分都可以将消息发送到任何客户端而无需客户端请求更新),那么是的,您需要一个适当的队列发送请求的数量,因为您不能(可靠地)在套接字上发出多个并发写操作。但是,我不会让使用者基于计时器。有异步兼容的生产者/消费者队列(例如BufferBlock<T>TPL Dataflow提供的队列,如果您有异步兼容的锁和条件变量,写一个并不难)。
哦,SignalR会为您处理。:)
我想在整个服务中使用一个线程,并在其中使用非阻塞的主体,尤其是在1. ManualResetEvent等的上下文中,我应该特别注意些什么。
如果您的整个服务都是单线程的,那么您根本不需要任何协调原语。但是,如果您确实使用线程池而不是同步回主线程(出于可伸缩性原因),则需要进行协调。我有一个协调原语库,您可能会发现它有用,因为它的类型同时具有同步和异步API。例如,这允许一种方法在锁上进行阻塞,而另一种方法希望在锁上进行异步阻塞。
您可能已经注意到围绕SignalR的主题重复出现。如果可能,请使用它!如果您必须编写一个简单的TCP / IP服务器而不能使用SignalR,则可以将您的初始时间估算值增加三倍。说真的 然后,通过我的TCP / IP常见问题博客系列,您可以开始进行痛苦的TCP开发。