views:

68

answers:

1

Im writing an application which is going to act as a tcp listener for a single client. The client is a java applet and will periodically connect to the listener, send some data and then wait for a response.

The code for the TcpServer class below has been largely lifted from an example provided by a more knowledgeable stackoverflow member in response to a different question.

Everything was going great until I found something in testing which wasnt mentioned in any of the interface documents I have. After the server has responded to the client it must then disconnect the client and start listening again.

My first thought was to call Disconnect() from inside SendData() but this results in a call to ReceiveCompleted() from somewhere and a nasty exception about the socket already being disposed.

Is this requirement easily achievable with the code design I have, and will I run into any problems in reusing the socket for subsequent connections?

sealed class TcpServer : IDisposable
    {
        #region Fields

        private const int SocketBufferSize = 1024;

        private readonly TcpListener tcpListener;

        private Socket connectedSocket;
        private bool disposed = false;

        #endregion Fields

        #region Constructors

        public TcpServer(int port)
        {
            tcpListener = new TcpListener(IPAddress.Any, port);
            tcpListener.Start();
            tcpListener.BeginAcceptSocket(EndAcceptSocket, tcpListener);
        }

        ~TcpServer()
        {
            Dispose(false);
        }

        #endregion Constructors

        #region Events

        public event EventHandler<DataReceivedEventArgs> DataReceived;

        public event EventHandler<IPEndPointEventArgs> SocketConnected;

        public event EventHandler<IPEndPointEventArgs> SocketDisconnected;

        #endregion Events

        #region Methods

        public void Dispose()
        {
            Dispose(true);
        }

        public void SendData(byte[] data)
        {
            if (connectedSocket == null)
            {
                return;
            }
            connectedSocket.Send(data);
        }

        private void BeginReceiveAsync(Socket sock, SocketAsyncEventArgs e)
        {
            if (!sock.ReceiveAsync(e))
            {
                ReceiveCompleted(sock, e);
            }
        }

        private void Connected(Socket socket)
        {
            var endPoint = (IPEndPoint)socket.RemoteEndPoint;

            connectedSocket = socket;

            OnSocketConnected(endPoint);
        }

        private void Disconnect(Socket socket)
        {
            var endPoint = (IPEndPoint)socket.RemoteEndPoint;

            socket.Close();

            connectedSocket = null;

            OnSocketDisconnected(endPoint);

            tcpListener.BeginAcceptSocket(EndAcceptSocket, tcpListener);
        }

        private void Dispose(bool disposing)
        {
            if (this.disposed == false)
            {
                if (disposing)
                {
                    try
                    {
                        if (tcpListener != null)
                        {
                            this.disposed = true;
                            tcpListener.Stop();
                        }
                    }
                    catch (Exception ex)
                    {
                        TraceLog.Error("TcpServer: tcpListener.Stop(): {0}", ex.Message);
                    }

                    try
                    {
                        if (connectedSocket != null)
                        {
                            connectedSocket.Close();
                            connectedSocket = null;
                        }
                    }
                    catch (SocketException ex)
                    {
                        TraceLog.Error("TcpServer: connectedSocket.Close(): {0}", ex);
                    }
                }
                this.disposed = true;
            }
        }

        private void EndAcceptSocket(IAsyncResult asyncResult)
        {
            var listener = (TcpListener)asyncResult.AsyncState;

            if (disposed)
            {
                return;
            }

            try
            {
                Socket sock = listener.EndAcceptSocket(asyncResult);
                Connected(sock);

                var e = new SocketAsyncEventArgs();
                e.Completed += ReceiveCompleted;
                e.SetBuffer(new byte[SocketBufferSize], 0, SocketBufferSize);
                BeginReceiveAsync(sock, e);
            }
            catch (SocketException ex)
            {
                TraceLog.Error("TcpServer.EndAcceptSocket: {0}", ex.Message);
            }
            catch (Exception ex)
            {
                TraceLog.Error("TcpServer.EndAcceptSocket: {0}", ex.Message);
            }
        }

        private void OnDataReceived(byte[] data, IPEndPoint ipEndPoint)
        {
            if (DataReceived != null)
            {
                DataReceived(this, new DataReceivedEventArgs(data, ipEndPoint));
            }
        }

        private void OnSocketConnected(IPEndPoint ipEndPoint)
        {
            if (SocketConnected != null)
            {
                SocketConnected(this, new IPEndPointEventArgs(ipEndPoint));
            }
        }

        private void OnSocketDisconnected(IPEndPoint ipEndPoint)
        {
            if (SocketDisconnected != null)
            {
                SocketDisconnected(this, new IPEndPointEventArgs(ipEndPoint));
            }
        }

        private void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            var sock = (Socket)sender;

            if (!sock.Connected)
            {
                Disconnect(sock);
            }

            try
            {
                int size = e.BytesTransferred;
                if (size == 0)
                {
                    Disconnect(sock);
                }
                else
                {
                    var buf = new byte[size];
                    Array.Copy(e.Buffer, buf, size);
                    ReceiveData(buf, (IPEndPoint)sock.RemoteEndPoint);
                    BeginReceiveAsync(sock, e);
                }
            }
            catch (SocketException ex)
            {
                TraceLog.Error("TcpServer: ReceiveCompleted: {0}", ex.Message);
            }
            catch (Exception ex)
            {
                TraceLog.Error("TcpServer: ReceiveCompleted: {0}", ex.Message);
            }
        }

        private void ReceiveData(byte[] data, IPEndPoint endPoint)
        {
            OnDataReceived(data, endPoint);
        }

        #endregion Methods
    }
A: 

Whenever I'm writing code that wraps around System.Net.Sockets.Socket, I find myself constantly adding try/catch clauses for SocketException and ObjectDisposedException. In most cases, ObjectDisposedException can simply be ignored, as it, in 99% of cases, indicates that the client has simply disconnected.

At least that's my impression of how the Socket API in .NET works. Try adding some exception handlers here and there, and see how it goes. In any case, your Disconnect method should not do more than something like this:

    public void Disconnect()
    {
        try
        {
            connectedSocket.Shutdown(SocketShutdown.Both);
        }
        catch (Exception)
        {
            // Ignore the exception. The client probably already disconnected.
        }

        connectedSocket.Dispose(); // This is safe; a double dispose will simply be ignored.
    }

I hope that sheds some light on the issue...

Zor