TcpListener está poniendo en cola las conexiones más rápido de lo que puedo eliminarlas

Como lo entiendo, TcpListener pondrá en cola las conexiones una vez que llame a Start() . Cada vez que llame a AcceptTcpClient (o BeginAcceptTcpClient ), sacará de la cola un elemento de la cola.

Si TcpListener prueba de nuestra aplicación TcpListener enviándole 1.000 conexiones a la vez, la cola se crea mucho más rápido de lo que podemos eliminar, lo que lleva (eventualmente) a los tiempos de espera del cliente porque no obtuvo una respuesta porque su conexión aún estaba en la cola. Sin embargo, el servidor no parece estar bajo mucha presión, nuestra aplicación no consume mucho tiempo de CPU y los otros recursos monitoreados en la máquina no están sudando. Se siente como si no estuviéramos corriendo lo suficientemente eficiente en este momento.

Estamos llamando a BeginAcceptTcpListener e inmediatamente ThreadPool a un hilo de ThreadPool para que realmente haga el trabajo, y luego BeginAcceptTcpClient a BeginAcceptTcpClient nuevo. El trabajo involucrado no parece ejercer ninguna presión sobre la máquina, es básicamente un sueño de 3 segundos seguido de una búsqueda en el diccionario y luego una escritura de 100 bytes en el TcpClient del TcpClient .

Aquí está el código TcpListener que estamos usando:

  // 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; } } 

Supongo que la respuesta estará relacionada con el uso de Sockets lugar de TcpListener , o al menos el uso de TcpListener.AcceptSocket , pero me pregunté cómo TcpListener.AcceptSocket eso.

Una idea que tuvimos fue llamar a AcceptTcpClient e inmediatamente TcpClient en Enqueue el TcpClient en uno de los múltiples objetos Queue . De esa manera, podríamos encuestar esas colas en subprocesos separados (una cola por subproceso), sin ejecutar monitores que podrían bloquear el subproceso mientras se esperan otras operaciones de Dequeue . Cada subproceso de la cola podría usar ThreadPool.QueueUserWorkItem para realizar el trabajo en un subproceso de ThreadPool y luego pasar a la TcpClient en cola del siguiente TcpClient en su cola. ¿Recomendaría este enfoque o nuestro problema es que estamos usando TcpListener y ninguna cantidad de encolado rápido lo solucionará?

He preparado un código que usa sockets directamente, pero me faltan los medios para realizar una prueba de carga con 1000 clientes. ¿Podría intentar probar cómo se compara este código con su solución actual? Me interesarían mucho los resultados, ya que estoy creando un servidor que también necesita aceptar muchas conexiones ahora mismo.

 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(); } 

A menos que me esté perdiendo algo, usted está llamando a BeingAcceptTcpClient, que es asíncrono, pero luego está llamando a WaitOne () para esperar hasta que finalice el código asíncrono, lo que efectivamente hace que el proceso sea sincrónico. Su código solo puede aceptar un cliente a la vez. ¿O estoy totalmente loco? Por lo menos, esto parece mucho cambio de contexto para nada.

Se mencionó, en las otras preguntas, pero sugeriría que en su método tcpListener.Start (), use la sobrecarga que le permite configurar el backlog a un número superior al número máximo de conexiones que espera al mismo tiempo. :

 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; } } 

Básicamente, esta opción establece la cantidad de conexiones TCP “pendientes” que se permiten que están esperando que se llame a un Aceptar. Si no está aceptando las conexiones lo suficientemente rápido, y esta acumulación se llena, las conexiones TCP se rechazarán automáticamente, y ni siquiera tendrá la oportunidad de procesarlas.

Como han mencionado otros, la otra posibilidad es acelerar la velocidad con la que procesa las conexiones entrantes. Sin embargo, aún debe establecer el retraso en un valor más alto, incluso si puede acelerar el tiempo de aceptación.

Solo una sugerencia: ¿por qué no aceptar los clientes de forma síncrona (utilizando AcceptTcpClient lugar de BeginAcceptTcpClient ), y luego procesar el cliente en un nuevo hilo? De esa manera, no tendrá que esperar a que se procese un cliente antes de poder aceptar el siguiente.

Lo primero que debe preguntarse es “es 1000 conexiones a la vez razonables”. Personalmente creo que es poco probable que te metas en esa situación. Es más probable que tengas 1000 conexiones en un corto período de tiempo.

Tengo un progtwig de prueba TCP que uso para probar el marco de mi servidor, puede hacer cosas como conexiones X en total en lotes de Y con un espacio de Z ms entre cada lote; Lo que personalmente encuentro es más real que ‘vasto número de una vez’. Es gratis, podría ayudar, puedes obtenerlo aquí: http://www.lenholgate.com/blog/2005/11/windows-tcpip-server-performance.html

Como han dicho otros, aumente el retraso de escucha, procese las conexiones más rápido, utilice aceptaciones asíncronas si es posible …