How to gracefully close an Async Server Socket? C#

Aelphaeis picture Aelphaeis · Oct 7, 2013 · Viewed 15k times · Source

I've seen a lot of questions about handling Sockets without object disposed exceptions, so I've decided to take a crack at it and see if it could be done. Here are my findings.

Problem?

You have a piece of code that uses a Socket from System.Net.Sockets that is a server socket. The problem is you want to close the socket and every time you try you get an ObjectDisposedException.

Your code may look something like this:

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

Running a Code similar to above will give you an error in the AcceptCallback went trying to end the receive. Even if you catch that, you still get an error in the StartListening() Method.

Answer

Aelphaeis picture Aelphaeis · Oct 7, 2013

So Here is how I fixed it:

First I added a Boolean called IsListening.

Next I changed condition that keeps the ear listening to While(IsListening).

In the stop method, I put set the IsListening variable to false before calling ear.close() and lastly I do a check for the ear inside of the AcceptCallback after the Manual SetEvent.

The end result looks like this:

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

The exception is now gone. I hope this helps someone else!