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

时间:2008-11-26 04:17:02

标签: c# .net performance sockets

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

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

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

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

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

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

注意:我有MSDN杂志文章Winsock: Get Closer to the Wire with High-Performance Sockets in .NET,我已经浏览了Kodart“XF.Server”组件 - 它看起来很粗略。

10 个答案:

答案 0 :(得分:22)

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

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

答案 1 :(得分:18)

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

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

答案 2 :(得分:8)

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

答案 3 :(得分:6)

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

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

答案 4 :(得分:4)

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

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

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

答案 5 :(得分:3)

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

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

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

答案 6 :(得分:3)

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

答案 7 :(得分:1)

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

阅读Garbage Collection every 100 seconds

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

答案 8 :(得分:0)

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

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

答案 9 :(得分:-1)

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

至少你会知道该看什么以及从哪里开始。