2010-10-17 13 views
12

Con respecto al método System.Net.Sockets.Socket.AcceptAsync de C# y .NET, se necesitaría uno para manejar un valor devuelto de "falso" para manejar el estado SocketAsyncEventArgs inmediatamente disponible desde la conexión procesada sincrónicamente. Microsoft proporciona ejemplos (que se encuentran en la página de la clase System.Net.Sockets.SocketAsyncEventArgs) que causarán un desbordamiento de la pila si hay una gran cantidad de conexiones pendientes, que pueden explotarse en cualquier sistema que implemente su modelo de manejo.Desbordamiento de pila al utilizar el modelo System.Net.Sockets.Socket.AcceptAsync

Otras ideas para evitar este problema son crear un bucle que llama al método del controlador, con la condición de que el valor Socket.AcceptAsync devuelve es igual a falso y romper el bucle (para permitir el procesamiento diferido) si el valor indica que la operación se está completando de forma asíncrona (verdadera). Sin embargo, esta solución también causa una vulnerabilidad de desbordamiento de pila debido al hecho de que la devolución de llamada asociada con SocketAsyncEventArgs pasada a Socket.AcceptAsync tiene al final del método, una llamada a Socket.AcceptAsync, que también tiene un bucle para conexiones inmediatamente disponibles, sincronizadas aceptadas .

Como puede ver, este es un problema bastante sólido, y aún no he encontrado una buena solución que no implique System.Threading.ThreadPool y la creación de toneladas de otros métodos y el procesamiento de la programación. Por lo que puedo ver, el modelo de socket asíncrono relacionado con Socket.AcceptAsync requiere más de lo que se demuestra en los ejemplos en MSDN.

¿Alguien tiene una solución limpia y eficiente para manejar conexiones inmediatamente pendientes que se aceptan sincrónicamente desde Socket.AcceptAsync sin tener que crear subprocesos separados para manejar las conexiones y sin utilizar la recursión?

+0

Sí, los ejemplos de MSDN no son siempre la mejor (o las mejores prácticas, para el caso). ¿Puedes aclarar tu pregunta? ¿Estás buscando una forma de trabajar con conectores asyc que no causen StackOverflowExceptoin? – Oded

+0

Eso es correcto; Estoy buscando una forma no recursiva para manejar conexiones inmediatamente pendientes. –

+0

Véase también http://stackoverflow.com/questions/2002143/synchronous-sockets-handling-false-socket-acceptasync-values ​​ – Lucero

Respuesta

7

No utilizaría AcceptAsync, sino más bien BeginAccept/EndAccept, e implementa correctamente el patrón asíncrono común, es decir, verificando CompletedSynchronously para evitar devoluciones de llamada en el hilo de devolución de llamada en las operaciones que se completaron.

Ver también AsyncCallBack CompletedSynchronously


Editar con respecto a la obligación de utilizar AcceptAsync:

El MSDN documentation dice explícitamente que la devolución de llamada no se invocará para las operaciones que completaban de forma sincrónica. Esto es diferente al patrón asíncrono común donde siempre se invoca la devolución de llamada.

Devuelve verdadero si la operación de E/S está pendiente. El evento SocketAsyncEventArgs.Completed en el parámetro e se generará al completar la operación . Devuelve falso si la operación de E/S completó de forma síncrona. El SocketAsyncEventArgs.Completed caso en el parámetro de correo no será levantado y el objeto e pasado como parámetro puede ser examinado inmediatamente después de la llamada al método vuelve a recuperar el resultado de la operación.

Actualmente no veo cómo un bucle no resolvería el problema de desbordamiento de pila. ¿Tal vez puede ser más específico sobre el código que causa el problema?


Edición 2: Estoy pensando en código como este (sólo en lo que respecta a AcceptAsync, el resto era sólo para obtener una aplicación de trabajo a intentarlo con):

static void Main(string[] args) { 
    Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
    listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 4444)); 
    listenSocket.Listen(100); 
    SocketAsyncEventArgs e = new SocketAsyncEventArgs(); 
    e.Completed += AcceptCallback; 
    if (!listenSocket.AcceptAsync(e)) { 
     AcceptCallback(listenSocket, e); 
    } 
    Console.ReadKey(true); 
} 

private static void AcceptCallback(object sender, SocketAsyncEventArgs e) { 
    Socket listenSocket = (Socket)sender; 
    do { 
     try { 
      Socket newSocket = e.AcceptSocket; 
      Debug.Assert(newSocket != null); 
      // do your magic here with the new socket 
      newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
      newSocket.Disconnect(false); 
      newSocket.Close(); 
     } catch { 
      // handle any exceptions here; 
     } finally { 
      e.AcceptSocket = null; // to enable reuse 
     } 
    } while (!listenSocket.AcceptAsync(e)); 
} 
+0

Lamentablemente BeginAccept/EndAccept no se ajusta a mis necesidades, esto se debe al requisito de instanciar los objetos IAsyncResult para cada operación. Mi servidor requiere un gran volumen de operaciones, y el perfil reveló que este era un punto estresante en la aplicación. Y aun así, la solución provista derrota el propósito de la finalización sincrónica invocando la devolución de llamada en un hilo diferente, o teniendo algún sistema extraño para mantener las llamadas a la velocidad de un hilo separado. La otra respuesta que alguien hizo es esencialmente idéntica a la tuya. –

+0

En respuesta a su edición, surge el problema en el caso de manejar los SocketAsyncEventArgs inmediatamente disponibles llamando al manejador AcceptAsync desde el mismo hilo, por ejemplo, verifica si el valor devuelto es falso, y luego simplemente llama al manejador con el argumento del SocketAsyncEventArgs que se usó en Socket.AcceptAsync.Y luego, dentro del controlador de aceptación, usa ese mismo patrón al final para asegurarse de que todas las conexiones (síncronas o asincrónicas) se manejen correctamente. –

+0

En respuesta a su segunda edición con el ejemplo de código, eso es exactamente lo que tenía en mente cuando publiqué mi respuesta a mi pregunta. Gracias por publicar el código, porque algunas personas pueden no haber entendido lo que estaba tratando de transmitir textualmente. Creo que fue su respuesta la que provocó mi pensamiento sobre cómo resolver el problema en primer lugar; gracias de nuevo. Aunque debo decir que mezclar el trabajo de socket síncrono y asincrónico es odioso, je je. Pero bueno, la idea se entiende. –

1

No he mirado con cuidado, pero huele a esto podría ser útil (véase la sección denominada "buceo pila"):

http://blogs.msdn.com/b/mjm/archive/2005/05/04/414793.aspx

+0

No estamos hablando de Windows Communication Foundation. Aunque este es un problema similar, es bastante irrelevante. También parece que simplemente están derrotando el propósito de tener acciones completas sincrónicamente, iniciando el controlador en un hilo separado. A su vez, tenerlo en un hilo separado puede causar un desbordamiento de pila en el hilo del controlador. –

+0

Para ser claros, la parte que llamé no se trata solo de WCF, se trata de escribir cualquier ciclo asíncrono con el patrón de inicio y fin. – Brian

+0

Lo volví a leer un par de veces e hice un check-in, y luego aclaré mi comentario. Mis disculpas si salió como desdeñoso. Simplemente no es lo que estoy buscando, y agradezco su aporte. –

3

tengo resolvió este problema simplemente cambiando la ubicación del ciclo. En lugar de invocar recursivamente el controlador accept desde dentro de sí mismo, envolver el código en un ciclo do-while con la condición siendo "! Socket.AcceptAsync (args)" evita un desbordamiento de la pila.

El razonamiento detrás de esto es que utiliza el hilo de devolución de llamada para procesar las conexiones que están inmediatamente disponibles, antes de molestarse en esperar de forma asíncrona para que aparezcan otras conexiones. Está reutilizando un hilo agrupado, efectivamente.

Agradezco las respuestas, pero por alguna razón ninguno de ellos hizo clic en mí y realmente no resolvió el problema. Sin embargo, parece que algo allí activó mi mente para pensar en esa idea. Evita trabajar manualmente con la clase ThreadPool y no usa recursividad.

Por supuesto, si alguien tiene una mejor solución o incluso una alternativa, estaría encantado de escucharla.

+0

Suena bastante parecido a lo que he estado pirateando juntos mientras escribías ese comentario. Es por eso que pregunté cómo un bucle no resolvería el problema, ya que esa devolución de llamada no se realiza mientras estás bucle no hay recursión ... – Lucero

+0

@Michael Me doy cuenta de que tengo casi 4 años, pero actualmente estoy abordando el mismo problema y realmente no podía entender exactamente lo que hiciste. ¿Recuerdas? – Rotem

+0

@Rotem ¡Sí, lo creo! Básicamente originalmente estaba llamando a 'AcceptAsync' dentro de la devolución de llamada del evento' Completed' y, en el caso de que se completara de forma sincrónica, solo estaba invocando la misma devolución de llamada del evento. Esto causará un desbordamiento de la pila si muchas llamadas se completan sincrónicamente, como es común en cargas frecuentes. En lugar de hacerlo de esta forma, hice un bucle como: 'while (! Acceptor.AcceptAsync (state)) {/ * do stuff * /}' –

0
newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
newSocket.Disconnect(false); 
newSocket.Close(); 

El problema con este fragmento de arriba es que esto bloqueará su próxima operación de aceptación.

Una mejor manera es la siguiente:

while (true) 
{ 
    if (e.SocketError == SocketError.Success) 
    { 
     //ReadEventArg object user token 
     SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop(); 
     Socket socket = ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket; 

     if (!socket.ReceiveAsync(readEventArgs)) 
     ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessReceiveEx), readEventArgs); . 
    } 
    else 
    { 
     HadleBadAccept(e); 
    } 

    e.AcceptSocket = null; 

    m_maxNumberAcceptedClients.WaitOne(); 
    if (listenSocket.AcceptAsync(e)) 
     break; 
} 
+0

Este es un comentario para mi downvote de 2 años. He desvalorizado esto porque no tiene sentido para el contexto de la pregunta. No publiqué un ejemplo de código, por lo que parece que estás respondiendo a una respuesta a esta pregunta y no a la pregunta en sí. Esto debería haber sido un comentario de dos oraciones sobre la respuesta aceptada. –

Cuestiones relacionadas