20每秒接收SocketAsyncEventArgs

TCP服务器是使用SocketAsyncEventArgs开发的,它是Windows服务的asynchronous方法。 我在Main的开头有这两行代码:

ThreadPool.SetMaxThreads(15000, 30000); ThreadPool.SetMinThreads(10000, 20000); 

并且都返回true(返回值被logging)。 现在,2000到3000个客户端开始发送消息到这个服务器,它开始接受连接(我计算连接的数量,这是预期的 – 有一个连接池)。 服务器进程的线程数将增长到2050〜3050。 到现在为止还挺好!

现在有一个Receive方法,它将在ReceiveAsync返回true或SocketAsyncEventArgs的Completed事件之后被调用。

在这里,问题就开始了:无论连接多less客户,多less消息发送,接收一秒钟内最多会被调用20次! 而随着客户数量的增加,这个数字(20)下降到10。

环境:TCP服务器和客户端正在同一台机器上进行模拟。 我已经testing了两台机器上的代码,一台有2核CPU和4GB RAM,另一台有8核CPU和12GB RAM。 没有数据丢失(有),有时我收到多个消息在每个接收操作。 没关系。 但是如何增加接收操作的数量呢?

关于实现的其他注意事项:代码很大,并且包含许多不同的逻辑。 总体描述是:我有一个SocketAsyncEventArgs接受新的连接。 它工作很好。 现在为每个新接受的连接创build一个新的SocketAsyncEventArgs来接收数据。 我把这个(为接收创build的SocketAsyncEventArgs)放在一个池中。 它不会被重用,但它的UserToken被用于跟踪连接; 例如那些断开的连接或那些在7分钟内没有发送任何数据的连接将被closures和处理(SocketAsyncEventArgs的AcceptSocket将被closures(两者),closures和处理,SocketAsyncEventArgs对象本身也是如此)。 下面是一个Sudo类,它执行这些任务,但是所有其他的逻辑和日志logging以及错误检查等都被删除,以使它简单明了(也许可以更容易地发现有问题的代码):

 class Sudo { Socket _listener; int _port = 8797; public Sudo() { var ipEndPoint = new IPEndPoint(IPAddress.Any, _port); _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _listener.Bind(ipEndPoint); _listener.Listen(100); Accept(null); } void Accept(SocketAsyncEventArgs acceptEventArg) { if (acceptEventArg == null) { acceptEventArg = new SocketAsyncEventArgs(); acceptEventArg.Completed += AcceptCompleted; } else acceptEventArg.AcceptSocket = null; bool willRaiseEvent = _listener.AcceptAsync(acceptEventArg); ; if (!willRaiseEvent) Accepted(acceptEventArg); } void AcceptCompleted(object sender, SocketAsyncEventArgs e) { Accepted(e); } void Accepted(SocketAsyncEventArgs e) { var acceptSocket = e.AcceptSocket; var readEventArgs = CreateArg(acceptSocket); var willRaiseEvent = acceptSocket.ReceiveAsync(readEventArgs); Accept(e); if (!willRaiseEvent) Received(readEventArgs); } SocketAsyncEventArgs CreateArg(Socket acceptSocket) { var arg = new SocketAsyncEventArgs(); arg.Completed += IOCompleted; var buffer = new byte[64 * 1024]; arg.SetBuffer(buffer, 0, buffer.Length); arg.AcceptSocket = acceptSocket; arg.SocketFlags = SocketFlags.None; return arg; } void IOCompleted(object sender, SocketAsyncEventArgs e) { switch (e.LastOperation) { case SocketAsyncOperation.Receive: Received(e); break; default: break; } } void Received(SocketAsyncEventArgs e) { if (e.SocketError != SocketError.Success || e.BytesTransferred == 0 || e.Buffer == null || e.Buffer.Length == 0) { // Kill(e); return; } var bytesList = new List<byte>(); for (var i = 0; i < e.BytesTransferred; i++) bytesList.Add(e.Buffer[i]); var bytes = bytesList.ToArray(); Process(bytes); ReceiveRest(e); Perf.IncOp(); } void ReceiveRest(SocketAsyncEventArgs e) { e.SocketFlags = SocketFlags.None; for (int i = 0; i < e.Buffer.Length; i++) e.Buffer[i] = 0; e.SetBuffer(0, e.Buffer.Length); var willRaiseEvent = e.AcceptSocket.ReceiveAsync(e); if (!willRaiseEvent) Received(e); } void Process(byte[] bytes) { } } 

它减速的原因是因为这些线程中的每一个都需要上下文切换,而这是一个相对昂贵的操作。 你添加的线程越多,你的CPU占用的CPU占用率就越​​高,只是在上下文切换上,而不是在实际的代码中。

您已经以一种相当奇怪的方式触及了这个单线程的客户端瓶颈。 服务器端异步的要点是减少线程数 – 每个客户端不要有一个线程,但理想情况下,系统中每个逻辑处理器只有一个或两个线程。

你发布的异步代码看起来很好,所以我只能猜测你的Process方法有一些容易忽略的非异步,阻塞它的I / O,即数据库或文件访问。 当I / O阻塞时,.NET线程池检测到这个并自动启动一个新的线程 – 这里的I / O Process中的瓶颈基本上是失控的。

异步管道确实需要100%的异步才能从中获得显着的收益。 半英寸将会让你编写复杂的代码,其性能与简单的同步代码一样差劲。

如果你绝对不能使Process方法纯粹是异步的,你可能会有一些运气来伪造它。 有一些东西在一个队列中等待一个小的有限大小的线程池进行处理。