如何优雅地关闭异步服务器套接字? C#

Posted

技术标签:

【中文标题】如何优雅地关闭异步服务器套接字? C#【英文标题】:How to gracefully close an Async Server Socket? C# 【发布时间】:2013-10-07 20:39:44 【问题描述】:

我已经看到很多关于在没有对象处理异常的情况下处理套接字的问题,所以我决定尝试一下,看看是否可以完成。这是我的发现。

有问题?

您有一段代码使用 System.Net.Sockets 中的 Socket,它是一个服务器套接字。问题是您想关闭套接字,并且每次尝试都会收到 ObjectDisposedException。

您的代码可能如下所示:

    private static ManualResetEvent allDone = new ManualResetEvent(false);
    private Socket ear;
    public void Start()
    
        new Thread(() => StartListening()).Start();
    

    private void StartListening()
    
        IP = Dns.GetHostAddresses(Dns.GetHostName()).Where(address => address.AddressFamily == AddressFamily.InterNetwork).First();
        port = 11221;
        IPEndPoint localEndPoint = new IPEndPoint(IP, Port);
        ear = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        ear.Bind(localEndPoint);
        ear.Listen(100);
        try
        

            while (true)
            
                allDone.Reset();
                ear.BeginAccept(new AsyncCallback(AcceptCallback), ear);
                allDone.WaitOne();
            
        
        catch (ObjectDisposedException e)
        
            Console.WriteLine("Socket Closed");
        
    

    private void AcceptCallback(IAsyncResult ar)
    
        allDone.Set();
        Socket listener = (Socket)ar.AsyncState;
        Socket handler = listener.EndAccept(ar);

        StateObject state = new StateObject();
        state.workSocket = handler;
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
    
    public static void ReadCallback(IAsyncResult ar) 
    
        String content = String.Empty;

        StateObject state = (StateObject) ar.AsyncState;
        Socket handler = state.workSocket;

        int bytesRead = handler.EndReceive(ar);

        if (bytesRead > 0) 
            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));

            content = state.sb.ToString();
            if (content.IndexOf("<EOF>") > -1) 
                Console.WriteLine("Read 0 bytes from socket. \n Data : 1", content.Length, content );
             
            else 
            
                handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReadCallback), state);
            
        
    

    public void Stop()
    
        ear.Close();
    

运行与上述类似的代码会在 AcceptCallback 尝试结束接收时给您一个错误。即使您发现了这一点,您仍然会在 StartListening() 方法中遇到错误。

【问题讨论】:

【参考方案1】:

所以这是我修复它的方法:

首先我添加了一个名为 IsListening 的布尔值。

接下来我改变了让耳朵听着 While(IsListening) 的条件。

在 stop 方法中,我在调用 ear.close() 之前将 IsListening 变量设置为 false,最后在 Manual SetEvent 之后检查 AcceptCallback 内的耳朵。

最终结果如下所示:

    private static ManualResetEvent allDone = new ManualResetEvent(false);
    private Socket ear;
    public void Start()
    
        new Thread(() => StartListening()).Start();
    

    private void StartListening()
    
        IP = Dns.GetHostAddresses(Dns.GetHostName()).Where(address => address.AddressFamily == AddressFamily.InterNetwork).First();
        port = 11221;
        IPEndPoint localEndPoint = new IPEndPoint(IP, Port);
        ear = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        ear.Bind(localEndPoint);
        ear.Listen(100);

        IsListening = true;

        while (IsListening)
        
            allDone.Reset();
            ear.BeginAccept(new AsyncCallback(AcceptCallback), ear);
            allDone.WaitOne();
        
        Console.WriteLine("Socket Closed");
    

    private void AcceptCallback(IAsyncResult ar)
    
        allDone.Set();
        if (IsListening == false) return;
        Socket listener = (Socket)ar.AsyncState;
        Socket handler = listener.EndAccept(ar);

        StateObject state = new StateObject();
        state.workSocket = handler;
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
    
    public static void ReadCallback(IAsyncResult ar) 
    
        String content = String.Empty;

        StateObject state = (StateObject) ar.AsyncState;
        Socket handler = state.workSocket;

        int bytesRead = handler.EndReceive(ar);

        if (bytesRead > 0) 
            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));

            content = state.sb.ToString();
            if (content.IndexOf("<EOF>") > -1) 
                Console.WriteLine("Read 0 bytes from socket. \n Data : 1", content.Length, content );
             
            else 
            
                handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReadCallback), state);
            
        
    

    public void Stop()
    
        IsListening = false;
        ear.Close();
    

异常现在消失了。我希望这对其他人有帮助!

【讨论】:

您确定这有效,并且不只是赢得比赛条件吗?如果您在操作之前检查 bool 并且另一个线程运行 stop() 会发生什么? Close() 在关闭前向耳朵发送消息。因此,当您使用 Stop() 时,您会完全离开循环。由于某种原因,它没有记录,但确实如此。也许稍后我会做一个完整的例子,然后发回给你看。 我认为在 while 循环之后添加 'ear.Close()' 会更好。这样套接字就不会在工作中关闭。 @IlyaSuzdalnitski 不,你不想这样做,因为 ear.Close() 已经被调用了。这是将 IsListening 设置为 false 的唯一方法。 我能看到的唯一问题是 StartListening 现在会阻塞,这个例子并不是真正的异步。

以上是关于如何优雅地关闭异步服务器套接字? C#的主要内容,如果未能解决你的问题,请参考以下文章

为啥不尝试 I/O 就不可能检测到 TCP 套接字已被对等方优雅地关闭?

在多线程 HTTP 服务器中发送后,如何干净地关闭套接字?

使用 Boost Asio 在 TCP 套接字上执行异步写入操作

TCP/IP网络编程:07优雅地断开套接字连接

如何实现服务器推送(推送技术)http或windows套接字[关闭]

Linux C 捕捉终止信号以优雅终止