处理异步套接字(.Net)后仍然会调用回调

Posted

技术标签:

【中文标题】处理异步套接字(.Net)后仍然会调用回调【英文标题】:After disposing async socket (.Net) callbacks still get called 【发布时间】:2016-05-10 10:23:46 【问题描述】:

我觉得,我对 .Net 中的异步套接字有一些误解。情况如下:我有 1 个异步套接字客户端和 1 个异步套接字服务器。他们在没有任何明显问题的情况下进行通信,但是当我关闭侦听器并断开客户端连接时,绑定到“BeginAccept”作为回调的“OnConnectRequest”仍然至少被调用一次。 “BeginReceive”、“OnConnectRequest”、“Disconnect”和“Dispose”方法是:

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

我正在做的是调用“Dispose”来关闭侦听器并关闭所有客户端套接字。然后客户端仍然处于活动状态,它会尝试重新连接,但我期望发生的是服务器在相应的 IP 和端口上完全不可用。我看到的是被调用的“OnConnectRequest”回调,由于尝试使用已经释放的套接字而崩溃。您能否解释一下,这里出了什么问题,以及如何优雅地关闭侦听器和所有接受的连接?

【问题讨论】:

在以下网页上查看示例:msdn.microsoft.com/en-us/library/w89fhyex(v=vs.110).aspx 为什么不使用 async/await? 【参考方案1】:

不,这是正确的——你在Begin...操作中指定的回调将总是被调用,即使你关闭了套接字(如果你关闭了套接字,它将被调用因为)。您应该在EndAccept 上捕获ObjectDisposedException,然后无需进一步操作即可返回。关闭/处置套接字/侦听器是取消对其进行异步操作的唯一方法。 (EndAccept也可以产生SocketException,应该正常处理。)

使用您自己维护的标志来检查侦听器是否仍然可用是自找麻烦,因为您正在引入需要同步的共享状态(易失性读取等)。您可以通过这种方式轻松引入竞争条件。侦听器已经在内部为您维护了这样一个标志,它用来抛出ObjectDisposedException,所以我就使用它。确实,在正常情况下捕获 ObjectDisposedException 可能是编码错误的迹象(因为您应该知道对象何时被释放),但对于异步代码,这是相当标准的。

【讨论】:

好的,所以关闭监听器实际上是调用回调的原因。非常感谢!!但是除了捕获 ObjectDisposedException 之外,我还可以使用一些布尔变量(比如说“IsListening”),在侦听器关闭时将其设置为 false,然后在回调中检查它的值,对吗?或者在异步编程异常的世界中是更好的选择? 我不认为这是一个很好的答案,因为现在您无法判断是否由于预期的正常断开连接或您实际上在异步函数中的某个位置出现了另一个错误而导致对象被释放无意中使用了已丢弃的东西。这不可能是生产模式。我知道在 C++ 中我会跟踪发出的未完成异步 IO 操作的数量,并且在断开连接时我会调用关闭(使异步套接字调用响应其相应的错误)然后等待超出调用的数量达到 0,然后再销毁需要成员。 @ChristopherPisz:如果您愿意,您可以在 C# 中使用完全相同的模式 - 是的,这将允许您调试对放置在您没想到的地方的对象的调用被处置。随意写下来作为答案。不过,在过去十年的 .NET 开发中,我并没有这样做过——额外的每个套接字状态管理是一种痛苦,而且让您更轻松地对错误代码进行故障排除的预期好处并不是我觉得在实践中需要。 我在这里写了一个单独的问题,更具体地说是关于如何等待联锁计数,因为坦率地说,我不知道如何。 ***.com/questions/47311561/waiting-on-interlocked-0

以上是关于处理异步套接字(.Net)后仍然会调用回调的主要内容,如果未能解决你的问题,请参考以下文章

.Net 异步套接字操作限制?

tcp异步机制

java同异步请求和阻塞非阻塞的区别

boost::asio,为啥我的套接字在调用 async_receive_from 后立即运行回调函数?

在所有异步套接字回调上强制线程 CultureInfo

Linux 中的异步套接字——轮询与回调通过