[ACCEPTED]-Stopping a TcpListener after calling BeginAcceptTcpClient-tcpclient

Accepted answer
Score: 17

I just ran into this issue myself, and I 31 believe your current solution is incomplete/incorrect. There 30 is no guarantee of atomicity between the 29 check for IsBound and the subsequent call to EndAcceptTcpClient(). You 28 can still get an exception if the listener 27 is Stop()'d between those two statements. You 26 didn't say what exception you're getting 25 but I assume it's the same one I'm getting, ObjectDisposedException (complaining 24 that the underlying socket has already been 23 disposed).

You should be able to check this 22 by simulating the thread scheduling:

  • Set a breakpoint on the line after the IsBound check in your callback
  • Freeze the thread that hits the breakpoint (Threads window -> right click, "Freeze")
  • Run/trigger the code that calls TcpListener.Stop()
  • Break in and step through the EndAcceptTcpClient() call. You should see the ObjectDisposedException.

IMO 21 the ideal solution would be for Microsoft 20 to throw a different exception from EndAcceptTcpClient in 19 this case, e.g. ListenCanceledException or something like that.

As 18 it is, we have to infer what's happening 17 from the ObjectDisposedException. Just catch the exception and 16 behave accordingly. In my code I silently 15 eat the exception, since I have code elsewhere 14 that's doing the real shutdown work (i.e. the 13 code that called TcpListener.Stop() in the first place). You 12 should already have exception handling in 11 that area anyway, since you can get various 10 SocketExceptions. This is just tacking another catch handler 9 onto that try block.

I admit I'm uncomfortable 8 with this approach since in principle the 7 catch could be a false positive, with a 6 genuine "bad" object access in there. But 5 on the other hand there aren't too many 4 object accesses in the EndAcceptTcpClient() call that could 3 otherwise trigger this exception. I hope.

Here's 2 my code. This is early/prototype stuff, ignore 1 the Console calls.

    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);
    }
Score: 8

No you're not missing anything. You can 6 check the IsBound property of the Socket 5 object. At least for TCP connections, while 4 the socket is listening this will be set 3 to true and after you call close it's value 2 will be false. Though, your own implementation 1 can work just as well.

Score: 1

try this one. it works fine for me without 1 catching exceptions.

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

i think that all tree things are needed 3 and that the restart of BeginAcceptTcpClient 2 should be placed outside the tryctach of 1 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();
        }            
    }
Score: 0

This is a simple example how to start listening, how 2 to process requests asynchronously, and 1 how to stop listening.

Full example 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
}

More Related questions