2009-07-23 9 views
18

tengo este código ...Detención de una TcpListener después de llamar BeginAcceptTcpClient

internal static void Start() 
{ 
    TcpListener listenerSocket = new TcpListener(IPAddress.Any, 32599); 
    listenerSocket.Start(); 
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
} 

Entonces mi llamada de vuelta función se parece a esto ...

private static void AcceptClient(IAsyncResult asyncResult) 
{ 
    MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult)); 
    ThreadPool.QueueUserWorkItem((object state) => handler.Process()); 
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
} 

Ahora, llamo BeginAcceptTcpClient, después de algún tiempo más tarde quiero detener el servidor. Para hacer esto, he estado llamando a TcpListener.Stop() o TcpListener.Server.Close(). Sin embargo, ambos ejecutan mi función AcceptClient. Esto arroja una excepción cuando llamo a EndAcceptTcpClient. ¿Cuál es la mejor forma de evitar esto? Podría poner una bandera para detener la ejecución de AcceptClient una vez que haya llamado "stop", pero me pregunto si me está perdiendo algo.

Actualización 1

Actualmente he parcheado cambiando el código para tener este aspecto.

private static void AcceptClient(IAsyncResult asyncResult) 
{ 
    if (!shutdown) 
    { 
      MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult)); 
      ThreadPool.QueueUserWorkItem((object state) => handler.Process()); 
      listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
    } 
} 

private static bool shutdown = false; 
internal static void Stop() 
{ 
    shutdown = true; 
    listenerSocket.Stop(); 
} 

Actualización 2

lo cambié a impliment la respuesta de Spencer Ruport.

private static void AcceptClient(IAsyncResult asyncResult) 
{ 
    if (listenerSocket.Server.IsBound) 
    { 
      MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult)); 
      ThreadPool.QueueUserWorkItem((object state) => handler.Process()); 
      listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
    } 
} 
+1

No es necesario poner en cola otro hilo para realizar el trabajo de manipulación; simplemente puede llamar al EndAcceptTcpClient para decirle al oyente que se maneja, y luego llamar a BeginAcceptTcpClient justo después para programar otro manejo. Use el hilo actual para manejar la solicitud que acaba de recibir. –

Respuesta

15

Acabo de toparme con este problema yo mismo, y creo que su solución actual es incompleta/incorrecta. No hay garantía de atomicidad entre el cheque para IsBound y la llamada subsiguiente al EndAcceptTcpClient(). Aún puede obtener una excepción si el oyente es Stop() 'd entre esas dos declaraciones. Usted no dijo qué excepción está recibiendo, pero supongo que es la misma que recibo, ObjectDisposedException (quejándose de que el zócalo subyacente ya ha sido eliminado).

Usted debe ser capaz de comprobar esto mediante la simulación de la programación de subprocesos:

  • Establecer un punto de interrupción en la línea después de la comprobación IsBound en su devolución de llamada
  • Congelar el hilo que golpea el punto de interrupción (Hilos ventana - > clic derecho, "Freeze")
  • Run/activar el código que llama TcpListener.Stop()
  • se meten a paso a través de la llamada EndAcceptTcpClient(). Debería ver el ObjectDisposedException.

IMO La solución ideal sería que Microsoft arrojara una excepción diferente de EndAcceptTcpClient en este caso, p. ListenCanceledException o algo así.

Como es, tenemos que inferir lo que está pasando desde el ObjectDisposedException. Simplemente atrape la excepción y compórtese en consecuencia. En mi código, en silencio me como la excepción, ya que tengo un código en otro lugar que está haciendo el trabajo de apagado real (es decir, el código que llamó al TcpListener.Stop() en primer lugar). Ya debes tener manejo de excepciones en esa área, ya que puedes obtener varios SocketExceptions. Esto solo está atacando a otro controlador de captura en ese bloque de prueba.

Admito que no me siento cómodo con este enfoque, ya que, en principio, la captura podría ser un falso positivo, con un verdadero acceso a objetos "malos" allí. Pero, por otro lado, no hay demasiados accesos a objetos en la llamada EndAcceptTcpClient() que de lo contrario podrían desencadenar esta excepción. Espero.

Aquí está mi código. Esto es algo temprano/prototipo, ignora las llamadas de la Consola.

private void OnAccept(IAsyncResult iar) 
    { 
     TcpListener l = (TcpListener) iar.AsyncState; 
     TcpClient c; 
     try 
     { 
      c = l.EndAcceptTcpClient(iar); 
      // keep listening 
      l.BeginAcceptTcpClient(new AsyncCallback(OnAccept), l); 
     } 
     catch (SocketException ex) 
     { 
      Console.WriteLine("Error accepting TCP connection: {0}", ex.Message); 

      // unrecoverable 
      _doneEvent.Set(); 
      return; 
     } 
     catch (ObjectDisposedException) 
     { 
      // The listener was Stop()'d, disposing the underlying socket and 
      // triggering the completion of the callback. We're already exiting, 
      // so just return. 
      Console.WriteLine("Listen canceled."); 
      return; 
     } 

     // meanwhile... 
     SslStream s = new SslStream(c.GetStream()); 
     Console.WriteLine("Authenticating..."); 
     s.BeginAuthenticateAsServer(_cert, new AsyncCallback(OnAuthenticate), s); 
    } 
+0

Sí, terminé poniendo un stop bool que configuré justo antes de llamar de cerca. Luego verifico esto antes de llamar al final aceptar. –

+0

Incluso ese enfoque sufre del problema de enhebrado. Básicamente, a menos que haya un candado que cubra tanto el booleano como las llamadas al TcpListener, puede obtener la programación del hilo que causa la excepción: 1) la devolución de llamada verifica el booleano, todavía estamos escuchando, genial, 2) ese hilo se intercambia por el hilo que establece el booleano y llama a Stop(), 3) el hilo de devolución de llamada se reanuda y llama a EndAccept. –

+0

¿Por qué causa la 'ObjectDisposedException' llamando a' EndAcceptTcpClient' en un escucha cerrado en lugar de simplemente verificar si todavía está activo y simplemente regresando de la devolución de llamada? –

7

No, no le falta nada. Puede verificar la propiedad IsBound del objeto Socket. Al menos para las conexiones TCP, mientras el socket está escuchando, se establecerá en verdadero y después de que se cierre, su valor será falso. Sin embargo, su propia implementación puede funcionar igual de bien.

+0

¡Impresionante, esto es exactamente lo que estaba buscando! ¡Gracias! –

+0

Esta es _la_ respuesta; simplemente marque 'listener.Server.IsBound' en la devolución de llamada asíncrona y si es falso, simplemente devuelva. No es necesario llamar a 'EndAccept * 'y luego atrapar la excepción (esperada y documentada). –

1

prueba este. funciona bien para mí sin detectar excepciones.

private void OnAccept(IAsyncResult pAsyncResult) 
{ 
    TcpListener listener = (TcpListener) pAsyncResult.AsyncState; 
    if(listener.Server == null) 
    { 
     //stop method was called 
     return; 
    } 
    ... 
} 
+1

El problema con esto es que Stop() en realidad vuelve a crear un nuevo socket (no vinculado) para el Servidor. Start() luego vincula este socket (al puerto IPEndPoint o IPAddress + para el que se creó) y lo inicia a escuchar. Es por eso que IsBound es un poco más preciso. –

0

Creo que se necesitan todas las cosas de los árboles y que el reinicio de BeginAcceptTcpClient deben colocarse fuera del tryctach de EndAcceptTcpClient.

private void AcceptTcpClientCallback(IAsyncResult ar) 
    { 
     var listener = (TcpListener)ar.AsyncState; 

     //Sometimes the socket is null and somethimes the socket was set 
     if (listener.Server == null || !listener.Server.IsBound) 
      return; 

     TcpClient client = null; 

     try 
     { 
      client = listener.EndAcceptTcpClient(ar); 
     } 
     catch (SocketException ex) 
     { 
      //the client is corrupt 
      OnError(ex); 
     } 
     catch (ObjectDisposedException) 
     { 
      //Listener canceled 
      return; 
     } 

     //Get the next Client 
     listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), listener); 

     if (client == null) 
      return; //Abort if there was an error with the client 

     MyConnection connection = null; 
     try 
     { 
      //Client-Protocoll init 
      connection = Connect(client.GetStream()); 
     } 
     catch (Exception ex) 
     { 
      //The client is corrupt/invalid 
      OnError(ex); 

      client.Close(); 
     }    
    } 
0

Este es un ejemplo sencillo de cómo empezar a escuchar, cómo procesar las solicitudes de forma asincrónica, y como para dejar de escuchar.

Ejemplo completo here.

public class TcpServer 
{ 
    #region Public.  
    // Create new instance of TcpServer. 
    public TcpServer(string ip, int port) 
    { 
     _listener = new TcpListener(IPAddress.Parse(ip), port); 
    } 

    // Starts receiving incoming requests.  
    public void Start() 
    { 
     _listener.Start(); 
     _ct = _cts.Token; 
     _listener.BeginAcceptTcpClient(ProcessRequest, _listener); 
    } 

    // Stops receiving incoming requests. 
    public void Stop() 
    { 
     // If listening has been cancelled, simply go out from method. 
     if(_ct.IsCancellationRequested) 
     { 
      return; 
     } 

     // Cancels listening. 
     _cts.Cancel(); 

     // Waits a little, to guarantee 
     // that all operation receive information about cancellation. 
     Thread.Sleep(100); 
     _listener.Stop(); 
    } 
    #endregion 

    #region Private. 
    // Process single request. 
    private void ProcessRequest(IAsyncResult ar) 
    { 
     //Stop if operation was cancelled. 
     if(_ct.IsCancellationRequested) 
     { 
      return; 
     } 

     var listener = ar.AsyncState as TcpListener; 
     if(listener == null) 
     { 
      return; 
     } 

     // Check cancellation again. Stop if operation was cancelled. 
     if(_ct.IsCancellationRequested) 
     { 
      return; 
     } 

     // Starts waiting for the next request. 
     listener.BeginAcceptTcpClient(ProcessRequest, listener); 

     // Gets client and starts processing received request. 
     using(TcpClient client = listener.EndAcceptTcpClient(ar)) 
     { 
      var rp = new RequestProcessor(); 
      rp.Proccess(client); 
     } 
    } 
    #endregion 

    #region Fields. 
    private CancellationToken _ct; 
    private CancellationTokenSource _cts = new CancellationTokenSource(); 
    private TcpListener _listener; 
    #endregion 
} 
Cuestiones relacionadas