Stopping a TcpListener after calling BeginAcceptTcpClient

前端 未结 5 531
花落未央
花落未央 2021-01-31 18:38

I have this code...

internal static void Start()
{
    TcpListener listenerSocket = new TcpListener(IPAddress.Any, 32599);
    listenerSocket.Start();
    listen         


        
相关标签:
5条回答
  • 2021-01-31 19:29

    i think that all tree things are needed and that the restart of BeginAcceptTcpClient should be placed outside the tryctach of 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 讨论(0)
  • 2021-01-31 19:30

    This is a simple example how to start listening, how to process requests asynchronously, and 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
    }
    
    0 讨论(0)
  • 2021-01-31 19:32

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

    You should be able to check this 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 the ideal solution would be for Microsoft to throw a different exception from EndAcceptTcpClient in this case, e.g. ListenCanceledException or something like that.

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

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

    Here's my code. This is early/prototype stuff, ignore 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);
        }
    
    0 讨论(0)
  • 2021-01-31 19:33

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

    private void OnAccept(IAsyncResult pAsyncResult)
    {
        TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
        if(listener.Server == null)
        {
            //stop method was called
            return;
        }
        ...
    }
    
    0 讨论(0)
  • 2021-01-31 19:43

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

    0 讨论(0)
提交回复
热议问题