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

8

我看到很多关于处理套接字时出现对象已释放异常的问题,因此我决定尝试一下,看看是否可以解决。以下是我的发现。

问题?

你有一段代码,使用了来自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个回答

14

这是我是如何解决的:

首先,我添加了一个名为IsListening的布尔变量。

接着,我修改了保持耳朵听取的条件为While(IsListening)。

在stop方法中,在调用ear.close()之前,我将IsListening变量设置为false,最后在手动设置事件后的AcceptCallback中对ear进行检查。

最终结果如下:

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

这个异常现在已经消失了。我希望这能帮到其他人!


1
你确定这个代码能正常工作,而不是仅仅赢得了一个竞争条件吗?如果在你的操作之前检查布尔值时另一个线程运行了stop(),会发生什么? - Carlos
2
Close() 在关闭之前向耳朵发送一条消息。因此,当您使用 Stop() 时,您会完全离开循环。由于某种原因,这是未记录的,但它确实存在。也许以后我会制作一个完整的示例并发布供您查看。 - Aelphaeis
我认为在 while 循环后添加 'ear.Close()' 会更好。这样套接字就不会在任务进行中关闭了。 - Ilya Suzdalnitski
@IlyaSuzdalnitski 不,你不想这样做,因为 ear.Close() 已经被调用过了。这是将 IsListening 设置为 false 的唯一方式。 - Aelphaeis
2
我唯一能看到的问题是StartListening现在会阻塞,这个例子并不真正是异步的。 - Tony Edgecombe
@TonyEdgecombe startListening 是私有的,只能从 start 中访问。 - Aelphaeis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接