2009-07-23 11 views
18

Ho questo codice ...Arresto di un TcpListener dopo aver chiamato BeginAcceptTcpClient

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

Poi funzione mia chiamata di nuovo si presenta così ...

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

Ora, io chiamo BeginAcceptTcpClient, poi qualche tempo più tardi voglio fermare il server. Per fare questo ho chiamato TcpListener.Stop() o TcpListener.Server.Close(). Entrambi tuttavia eseguono la mia funzione AcceptClient. Ciò quindi genera un'eccezione quando chiamo EndAcceptTcpClient. Qual è il modo migliore per fare questo? Potrei semplicemente mettere un flag per fermare l'esecuzione di AcceptClient una volta che ho chiamato stop, ma mi chiedo se mi manca qualcosa.

Update 1

Attualmente ho rattoppato che modificando il codice a guardare come questo.

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

Update 2

ho cambiato in impliment la risposta da 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

Non è necessario mettere in coda un altro thread per eseguire il lavoro di gestione; puoi semplicemente chiamare EndAcceptTcpClient per dire al listener che è gestito, e poi chiamare un BeginAcceptTcpClient subito dopo per pianificare un'altra gestione. Usa il thread corrente per gestire la richiesta appena ricevuta. –

risposta

15

Mi sono appena imbattuto in questo problema, e credo che la tua attuale soluzione sia incompleta/errata. Non vi è alcuna garanzia di atomicità tra il controllo di IsBound e la successiva chiamata a EndAcceptTcpClient(). È ancora possibile ottenere un'eccezione se l'ascoltatore è Stop() 'd tra queste due istruzioni. Non hai detto quale eccezione stai ricevendo, ma presumo sia la stessa che sto ottenendo, ObjectDisposedException (lamentando che il socket sottostante sia già stato eliminato).

Dovreste essere in grado di controllare questo simulando la programmazione dei thread:

  • impostare un punto di interruzione sulla linea dopo il controllo IsBound nella richiamata
  • Congelare il filo che colpisce il punto di interruzione (window Fili - > tasto destro del mouse, "Freeze")
  • Run/attivare il codice che chiama TcpListener.Stop()
  • scassinano e passo attraverso la chiamata EndAcceptTcpClient(). Dovresti vedere lo ObjectDisposedException.

IMO la soluzione ideale sarebbe per Microsoft di lanciare un'eccezione diversa da EndAcceptTcpClient in questo caso, ad esempio, ListenCanceledException o qualcosa del genere.

Così com'è, dobbiamo dedurre cosa sta accadendo dallo ObjectDisposedException. Prendi l'eccezione e comportati di conseguenza. Nel mio codice mangio silenziosamente l'eccezione, dal momento che ho codice altrove che sta facendo il vero lavoro di spegnimento (cioè il codice che ha chiamato TcpListener.Stop() in primo luogo). Dovresti già avere una gestione delle eccezioni in quell'area, dal momento che puoi ottenere vari SocketExceptions. Si tratta semplicemente di attaccare un altro gestore catch su quel blocco try.

Ammetto di non essere a mio agio con questo approccio poiché in linea di principio il problema potrebbe essere un falso positivo, con un vero e proprio accesso "cattivo" all'oggetto. Ma d'altra parte non ci sono troppi accessi agli oggetti nella chiamata EndAcceptTcpClient() che potrebbe altrimenti far scattare questa eccezione. Io spero.

Ecco il mio codice. Questo è presto/prototipo, ignorare le chiamate della console.

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ì, ho finito per mettere un bool di arresto che ho impostato poco prima di chiamare chiudi. Quindi controllo questo prima di chiamare end accept. –

+0

Anche questo approccio soffre del problema di threading. Fondamentalmente, a meno che non ci sia un lock che copra sia il booleano che le chiamate al TcpListener, puoi ottenere la scheduling dei thread che causa l'eccezione: 1) callback controlla il booleano, stiamo ancora ascoltando, cool, 2) quel thread è scambiato per il thread che imposta il valore booleano e chiama Stop(), 3) il thread di callback riprende e chiama EndAccept. –

+0

Perché causi 'ObjectDisposedException' chiamando' EndAcceptTcpClient' su un listener chiuso invece di controllare solo se è ancora attivo per la prima volta e semplicemente tornare dalla richiamata? –

7

No non ti manca nulla. È possibile verificare la proprietà IsBound dell'oggetto Socket. Almeno per le connessioni TCP, mentre il socket è in ascolto, questo sarà impostato su true e dopo aver chiamato close il valore sarà false. Tuttavia, la tua implementazione può funzionare altrettanto bene.

+0

Fantastico, questo è esattamente quello che stavo cercando! Grazie! –

+0

Questa è la risposta; basta controllare 'listener.Server.IsBound' nel callback asincrono e se è falso, basta tornare. Non è necessario chiamare "EndAccept *" e quindi rilevare l'eccezione (prevista e documentata). –

1

provare questo. funziona bene per me senza interferenze.

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

Il problema con questo è che Stop() in realtà ricrea un nuovo socket (non vincolato) per Server. Start() quindi collega questo socket (alla porta IPEndPoint o IPAddress + per cui è stato creato) e lo avvia. Ecco perché IsBound è leggermente più preciso. –

0

penso che tutte le cose degli alberi sono necessari e che il riavvio di BeginAcceptTcpClient deve essere posto al di fuori del tryctach di 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

Questo è un semplice esempio su come iniziare ad ascoltare, come elaborare le richieste in modo asincrono, e come interrompere l'ascolto.

Esempio 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 
} 
Problemi correlati