TcpListener排队连接的速度比我清除它们的速度快

据我了解,一旦调用Start()TcpListener将对连接进行排队。 每次调用AcceptTcpClient (或BeginAcceptTcpClient )时,它都会从队列中取出一个项目。

如果我们通过立即向它发送1,000个连接来加载测试我们的TcpListener应用程序,那么队列构建的速度远远超过我们清除它的速度,导致(最终)从客户端超时,因为它没有得到响应,因为它的连接仍在队列。 然而,服务器似乎没有太大的压力,我们的应用程序没有消耗太多的CPU时间,并且机器上的其他受监控资源也没有出汗。 感觉我们现在还没有足够高效地运行。

我们调用BeginAcceptTcpListener然后立即切换到ThreadPool线程来实际完成工作,然后再次调用BeginAcceptTcpClient 。 所涉及的工作似乎没有对机器施加任何压力,它基本上只是一个3秒的睡眠,然后是字典查找,然后是100字节写入TcpClient的流。

这是我们使用的TcpListener代码:

  // Thread signal. private static ManualResetEvent tcpClientConnected = new ManualResetEvent(false); public void DoBeginAcceptTcpClient(TcpListener listener) { // Set the event to nonsignaled state. tcpClientConnected.Reset(); listener.BeginAcceptTcpClient( new AsyncCallback(DoAcceptTcpClientCallback), listener); // Wait for signal tcpClientConnected.WaitOne(); } public void DoAcceptTcpClientCallback(IAsyncResult ar) { // Get the listener that handles the client request, and the TcpClient TcpListener listener = (TcpListener)ar.AsyncState; TcpClient client = listener.EndAcceptTcpClient(ar); if (inProduction) ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client, serverCertificate)); // With SSL else ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client)); // Without SSL // Signal the calling thread to continue. tcpClientConnected.Set(); } public void Start() { currentHandledRequests = 0; tcpListener = new TcpListener(IPAddress.Any, 10000); try { tcpListener.Start(); while (true) DoBeginAcceptTcpClient(tcpListener); } catch (SocketException) { // The TcpListener is shutting down, exit gracefully CheckBuffer(); return; } } 

我假设答案将与使用Sockets而不是TcpListener ,或至少使用TcpListener.AcceptSocket ,但我想知道我们将如何做到这一点?

我们的一个想法是调用AcceptTcpClient并立即将TcpClient排入多个Queue对象之一。 这样,我们可以在不同的线程(每个线程一个队列)上轮询这些队列,而不会在等待其他Dequeue操作时运行到可能阻塞线程的监视器。 然后,每个队列线程可以使用ThreadPool.QueueUserWorkItemThreadPool线程中完成工作,然后继续将队列中的下一个TcpClient出列。 你会推荐这种方法,还是我们的问题,我们正在使用TcpListener ,没有多少快速出列将解决这个问题?

我已经编写了一些直接使用套接字的代码,但我缺乏使用1000个客户端执行负载测试的方法。 您能否尝试测试此代码与当前解决方案的对比情况? 我对结果非常感兴趣,因为我现在正在构建一个需要接受大量连接的服务器。

 static WaitCallback handleTcpRequest = new WaitCallback(HandleTcpRequest); static void Main() { var e = new SocketAsyncEventArgs(); e.Completed += new EventHandler(e_Completed); var socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Loopback, 8181)); socket.Listen((int)SocketOptionName.MaxConnections); socket.AcceptAsync(e); Console.WriteLine("--ready--"); Console.ReadLine(); socket.Close(); } static void e_Completed(object sender, SocketAsyncEventArgs e) { var socket = (Socket)sender; ThreadPool.QueueUserWorkItem(handleTcpRequest, e.AcceptSocket); e.AcceptSocket = null; socket.AcceptAsync(e); } static void HandleTcpRequest(object state) { var socket = (Socket)state; Thread.Sleep(100); // do work socket.Close(); } 

除非我遗漏了某些东西,否则你正在调用异步的BeingAcceptTcpClient,但是你正在调用WaitOne()以等待异步代码完成,这有效地使进程同步。 您的代码一次只能接受一个客户端。 还是我完全疯了? 至少,这似乎是很多上下文切换的结果。

在其他问题中提到过,但我建议在你的tcpListener.Start()方法中,使用重载,允许你将backlog设置为一个高于你一次预期的最大连接数的数字:

 public void Start() { currentHandledRequests = 0; tcpListener = new TcpListener(IPAddress.Any, 10000); try { tcpListener.Start(1100); // This is the backlog parameter while (true) DoBeginAcceptTcpClient(tcpListener); } catch (SocketException) { // The TcpListener is shutting down, exit gracefully CheckBuffer(); return; } } 

基本上,此选项设置允许等待调用Accept的“挂起”TCP连接数。 如果您没有足够快地接受连接,并且此待办事项已填满,则TCP连接将自动被拒绝,您甚至无法处理它们。

正如其他人所提到的,另一种可能性是加快处理传入连接的速度。 但是,您仍然应该将积压设置为更高的值,即使您可以加快接受时间。

只是一个建议:为什么不同步接受客户端(通过使用AcceptTcpClient而不是BeginAcceptTcpClient ),然后在新线程上处理客户端? 这样,您就不必等待客户端处理,然后才能接受下一个客户端。

首先要问自己的是“一次性合理的1000个连接”。 我个人认为你不太可能进入这种情况。 您很可能在短时间内发生了1000次连接。

我有一个用于测试我的服务器框架的TCP测试程序,它可以在Y批次中总共执行X连接,每个批次之间的间隔为Z ms; 我个人觉得这个世界比“大量一次”更真实。 它是免费的,它可能有所帮助,你可以从这里得到它: http : //www.lenholgate.com/blog/2005/11/windows-tcpip-server-performance.html

正如其他人所说,增加监听积压,更快地处理连接,尽可能使用异步接受…