Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
587 views
in Technique[技术] by (71.8m points)

c# - After disposing async socket (.Net) callbacks still get called

I feel, that I am misunderstanding something about async sockets in .Net. The situation is as follows : I have 1 async socket client and 1 async socket server. They communicate without any visible problems, but when I close listener and disconnect clients, the "OnConnectRequest" which is bound to "BeginAccept" as a callback, still gets called at least once. The "BeginReceive", "OnConnectRequest", "Disconnect" and "Dispose" methods are :

  public void BeginReceive()
    {
        _listener.Bind(_endpoint);
        _listener.Listen(_maxConnections);
        try
        {
            _listener.BeginAccept(new AsyncCallback(OnConnectRequest), _listener);
        }
        catch (SocketException se)
        {
            OnListeningError(this, new Exception("Server cannot accept connections due to network shutdown or some fatal failure", se));
        }
    }



     protected void OnConnectRequest(IAsyncResult ar)
    {
        Socket listener = (Socket)ar.AsyncState;

        Socket client = listener.EndAccept(ar);
        var remoteEndpoint = client.RemoteEndPoint;

        IDuplexStateObject state = new DuplexStateObject();
        state.WorkSocket = client;

        if (_clients.Count <= _maxConnections)
        {
            lock (_clients)
            {
                _clients.Add(state);
            }

            OnConnected(this, state);
        }
        else
        {
            //denying connection
            client.Close();
            AcceptingError(this, null, new Exception(string.Format("Maximal connection count reached, connection attempt  {0} has been denied", (remoteEndpoint != null) ? remoteEndpoint.ToString() : null)));
        }

        //accept connections from other clients
        try
        {
            listener.BeginAccept(new AsyncCallback(OnConnectRequest), listener);
        }
        catch (SocketException se)
        {
            if (se.SocketErrorCode == SocketError.TooManyOpenSockets)
            {
                OnListeningError(this, new Exception("Maximal connection count reached, not possible to create any more connections"));
            }
            else
            {
                OnListeningError(this, new Exception("Server cannot accept connections due to network shutdown or some fatal failure"));
            }
        }
    }


         public void Disconnect(IStateObject state)
    {
        if (state.WorkSocket == null)
        {
            //OnDisconnectError(this, state.ClientInfo,
            //    new Exception("No underlying work socket found for client. Already disconnected, disposing connection..."));

            OnDisconnected(this, state.ClientInfo);
            return;
        }

        try
        {
            if (state.WorkSocket.Connected)
            {
                state.WorkSocket.Shutdown(SocketShutdown.Both);
            }
            state.WorkSocket.Close();
        }
        catch (SocketException se)
        {
            OnDisconnectError(this, state.ClientInfo, se);
        }
        OnDisconnected(this, state.ClientInfo); 
        lock (_clients)
        {
            _clients.Remove(state);
        }
    }

      public void Dispose()
    {
        _listener.Close();

        //keys are cloned before disconnecting
        foreach (var client in _clients.ToList())
        {
            Disconnect(client);
        }
    }

What I am doing is calling "Dispose" to closes listener and shut down all client sockets. The client is then still active, and it tries to reconnect, but what I expected to happen was server being completely unavailable on corresponding IP and port. What I see instead is "OnConnectRequest" callback being called, which crashes because of attempt to use already disposed socket. Can you please explain, what is wrong here, and how graceful shutdown of a listener and all accepted connections should look like ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

No, this is correct -- the callback you specify in a Begin... operation will always be called, even if you close the socket (if you close the socket, it will be called because of that). You should be catching the ObjectDisposedException you get on the EndAccept and then return without further action. Closing/disposing a socket/listener is the only way to cancel an asynchronous operation on it. (EndAccept can also produce SocketException, which should be handled normally.)

Using a flag you maintain yourself to check if the listener is still available is asking for trouble, because you're introducing shared state that needs to be synchronized (volatile reads and the like). You can easily introduce race conditions that way. The listener already maintains such a flag for you internally, which it uses to throw ObjectDisposedException, so I'd just use that. It's true that under normal circumstances catching ObjectDisposedException is a possible sign of a coding error (since you're supposed to know when an object is disposed), but with asynchronous code it's pretty standard.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...