繁体   English   中英

高性能C#服务器套接字的提示/技巧

[英]Tips / techniques for high-performance C# server sockets

我有一个.NET 2.0服务器似乎遇到了扩展问题,可能是由于套接字处理代码设计不佳,我正在寻找有关如何重新设计它以提高性能的指导。

使用场景: 50到150个客户端,每个客户端的高速率(高达100秒/秒)的小消息(每个10字节)。 客户端连接是长期的 - 通常是几个小时。 (服务器是交易系统的一部分。客户端消息被聚合成组以通过较少数量的“出站”套接字连接发送到交换机,并且当交换机处理每个组时,确认消息被发送回客户端。 。)OS是Windows Server 2003,硬件是2 x 4核X5355。

当前客户端套接字设计: TcpListener生成一个线程,以在客户端连接时读取每个客户端套接字。 线程在Socket.ReceiveSocket.Receive ,解析传入的消息并将它们插入到一组队列中以供核心服务器逻辑处理。 使用来自与交换机侧通信的线程的异步Socket.BeginSend调用,通过客户端套接字发回确认消息。

观察到的问题:随着客户端数量的增加(现在为60-70),我们开始在向客户端发送数据和从客户端接收数据时看到间歇性延迟高达100毫秒。 (我们记录每条确认消息的时间戳,我们可以看到时间戳序列中偶尔存在长时间间隔,这些间隙来自同一组中通常在几毫秒内完成的一组ack。)

整体系统CPU使用率很低(<10%),有足够的空闲RAM,核心逻辑和出站(面向交换)端表现良好,因此问题似乎与面向客户端的套接字代码隔离开来。 服务器和客户端(千兆局域网)之间有足够的网络带宽,我们排除了网络或硬件层问题。

任何有用资源的建议或指示都将不胜感激。 如果有人有任何诊断或调试技巧可以确定出错的地方,那么这些技巧也会很棒。

注意:我有MSDN杂志的文章Winsock:在.NET中使用高性能套接字更接近线路,我已经浏览了Kodart“XF.Server”组件 - 它看起来很粗略。

.NET 3.5环境中的套接字I / O性能得到了改进。 您可以使用ReceiveAsync / SendAsync而不是BeginReceive / BeginSend来获得更好的性能。 把它拿出来:

http://msdn.microsoft.com/en-us/library/bb968780.aspx

其中很多都与系统上运行的许多线程有关,内核为每个线程提供了时间片。 设计简单,但不能很好地扩展。

你可能应该看一下使用Socket.BeginReceive,它将在.net线程池上执行(你可以用某种方式指定它使用的线程数),然后从异步回调中推送到一个队列(可以在任何一个中运行) .NET线程)。 这应该会给你更高的性能。

每个客户端的一个线程看起来非常过分,特别是考虑到这里的总体CPU使用率较低。 通常,您需要一个小的线程池来为所有客户端提供服务,使用BeginReceive等待工作异步 - 然后简单地将处理发送给其中一个工作者(可能只需将工作添加到同步队列中,所有工作人员都在等待)。

我不是一个C#家伙,但对于高性能套接字服务器,最具可扩展性的解决方案是使用I / O完成端口,其中有许多活动线程适合于进程运行的CPU,而不是使用每个连接一个线程的模型。

在您的情况下,使用8核机器,您将需要16个总线程,其中8个并发运行。 (其他8个基本保留。)

正如其他人所建议的那样,实现这一点的最佳方法是使面向客户端的代码全部异步。 在TcpServer()上使用BeginAccept(),这样就不必手动生成线程。 然后在从接受的TcpClient获取的基础网络流上使用BeginRead()/ BeginWrite()。

但是,有一件事我不明白。 你说这些是长期存在的连接,以及大量的客户端。 假设系统已达到稳定状态,您的最大客户端(例如70)已连接。 您有70个线程侦听客户端数据包。 然后,系统仍然应该响应。 除非您的应用程序有内存/处理泄漏,并且您的资源不足以使您的服务器正在分页。 我会在调用Accept()的过程中放置​​一个计时器,在那里启动客户端线程并查看需要多长时间。 此外,我将启动taskmanager和PerfMon,并监控应用程序的“非页面缓冲池”,“虚拟内存”,“处理计数”,并查看该应用程序是否处于资源紧张状态。

虽然确实去Async是正确的方法,但我不相信它是否能真正解决潜在的问题。 我会按照我的建议监视应用程序,并确保没有泄漏内存和句柄的内在问题。 在这方面,上面的“BigBlackMan”是对的 - 你需要更多的仪器才能继续。 不知道为什么他被投票了。

随机间歇〜250毫秒的延迟可能是由于TCP使用的Nagle算法造成的。 尝试禁用它,看看会发生什么。

Socket.BeginConnectSocket.BeginAccept绝对有用。 我相信他们在实现中使用ConnectExAcceptEx调用。 这些调用将初始连接协商和数据传输包装到一个用户/内核转换中。 由于初始发送/接收缓冲区已准备就绪,因此内核可以将其发送到远程主机或用户空间。

它们还有一个准备好的侦听器/连接器队列,这可能通过避免用户空间接受/接收连接并将其切断(以及所有用户/内核切换)所涉及的延迟来提供一些提升。

要将BeginConnect与缓冲区一起使用,似乎必须在连接之前将初始数据写入套接字。

我想要消除的一件事是它不像垃圾收集器运行那么简单。 如果所有消息都在堆上,则每秒生成10000个对象。

每100秒阅读一次垃圾收集

唯一的解决方案是将邮件保留在堆中。

我有7年或8年前的相同问题和100毫秒到1秒暂停,问题是垃圾收集..从4 gig使用大约400兆但但有很多对象。

我最终用C ++存储消息,但您可以使用ASP.NET缓存(以前使用COM并将它们移出堆中)

我没有答案,但为了获得更多信息,我建议将您的代码与计时器一起使用,并记录可疑操作所需的平均和最大时间,例如添加到队列或打开套接字。

至少通过这种方式,您将了解要查看的内容以及从哪里开始。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM