tags:

views:

39

answers:

2

Hi guys,

I'm working on a small .dll dedicated to Tcp communication,

In my project I've got a server class that uses a TcpListener to accept incoming connections. Incoming connections are stored in a Dictionary and handled from there.

Each connection's code looks like :

 public class Connection : ConnectionBase<Coder.Coder>
    {
        public Connection(TcpClient client, Guid id) : base()
        {
            Id = id;
            Client = client;
        }

        public void Start()
        {
            IsConnected = true;            
            Client.Client.BeginReceive(m_message, 0, m_message.Length, SocketFlags.None, new AsyncCallback(on_data_received), null);            
        }

        public void Stop()
        {
            try
            { 
                Client.Close();
                handle_connection_lost(new ConnectionLostArgs(Id));
            }
            catch
            { }
        }

        public void Send(byte[] data)
        {
            try
            {
                using (NetworkStream s = Client.GetStream())
                {
                    using (BinaryWriter w = new BinaryWriter(s))
                    {
                        var buffer = m_coder.Encode(data);
                        w.Write(buffer);
                        w.Flush();
                    }
                }

            }
            catch
            { handle_connection_lost(new ConnectionLostArgs(Id)); }
        }

        public Guid Id { get; set; }
        public TcpClient Client { get; set; }

        private byte[] m_message = new byte[1024];        

        private void on_data_received(IAsyncResult ar)
        {
            try
            {
                Client.Client.BeginReceive(m_message, 0, m_message.Length,
                        SocketFlags.None, new AsyncCallback(on_data_received), null);

                int bytesRead = Client.Client.EndReceive(ar);

                if (bytesRead > 0)
                {
                    byte[] data = new byte[bytesRead];
                    Array.Copy(m_message, data, bytesRead);

                    m_coder.Push(data);

                }               
            }
            catch(Exception ex)
            {
                Console.WriteLine("Connection::on_data_received : {0}", ex.Message);
                handle_connection_lost(new ConnectionLostArgs(Id));
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                try
                {
                    Stop();
                }
                catch
                { }
            }

            base.Dispose(disposing);
        }
    }

*Please note that the Coder is a class responsible for decoding and encoding data packets.

Apart from the above, I've got a socket-based TcpClient (which I'm hoping to reuse later with Silverlight), The code is as follows :

 public class TcpSocketClient : TcpClientBase<Coder.Coder>
    {
        public static TcpSocketClient Create(string host, int port)
        {            
            if (port == 0)
                return null;

            return new TcpSocketClient(host, port);
        }

        private TcpSocketClient(string host, int port) : base()
        {
            IsConnected = false;
            RemoteEndpoint = new DnsEndPoint(host, port);
        }

        public void Start()
        {
            m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            byte[] buffer = new byte[1024];

            SocketAsyncEventArgs e = new SocketAsyncEventArgs()
            {
                RemoteEndPoint = RemoteEndpoint,
                UserToken = m_socket,

            };
            e.SetBuffer(buffer, 0, buffer.Length);
            e.Completed += new EventHandler<SocketAsyncEventArgs>(handle_socket_connect_completed);

            m_socket.ConnectAsync(e);
        }        

        public void Stop()
        {
            try
            {
                m_socket.Close();
                m_socket.Dispose();
            }
            catch (ObjectDisposedException)
            { }
        }

        public void Send(byte[] data)
        {
            try
            {
                var buffer = m_coder.Encode(data);
                SocketAsyncEventArgs e = new SocketAsyncEventArgs()
                {
                    BufferList = new List<ArraySegment<byte>>() { new ArraySegment<byte>(buffer) },
                    UserToken = m_socket
                };
                m_socket.SendAsync(e);                
            }
            catch (Exception ex)
            { handle_client_disconnected(ex.Message); }
        }

        #region Properties
        public DnsEndPoint RemoteEndpoint { get; private set; }
        #endregion

        #region Fields
        Socket m_socket;
        #endregion

        void handle_socket_connect_completed(object sender, SocketAsyncEventArgs e)
        {
            if (!m_socket.Connected)
            {
                handle_client_disconnected("Failed to connect");
                return;
            }


            e.Completed -= handle_socket_connect_completed;
            e.Completed += new EventHandler<SocketAsyncEventArgs>(handle_socket_async_receive);

            handle_client_connected();

            m_socket.ReceiveAsync(e);                        
        }

        void handle_socket_async_receive(object sender, SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred == 0)
            {
                handle_client_disconnected("Connection closed by the remote host");
                try { m_socket.Close(); }
                catch { }
                return;
            }

            try 
            {
                byte[] buffer = new byte[e.BytesTransferred];
                Array.Copy(e.Buffer, buffer, e.BytesTransferred);
                m_coder.Push(buffer);                
            }
            catch { }


            m_socket.ReceiveAsync(e);            
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                try
                {
                    RemoteEndpoint = null;
                    m_socket.Close();
                    m_socket.Dispose();
                }
                catch
                { }
            }

            base.Dispose(disposing);
        }
    }

I've created a set of unit tests for both.

In one of the tests I send data from the client to the server. Works. In another test I send data from the server's connection to a client. Epic fail. I keep getting Socket ObjectDisposed exceptions in Connection's on_data_received. To be honest I have no idea what's going on - therefore I require some aid.

I'm using .Net 4, VS 2010, My machine's OS is Win7 (if this info is of any help)

Regards, Maciek

A: 

In your on_data_received handler, you are calling Client.Client.BeginReceive(...) before Client.Client.EndReceive(...).

The BeginReceive may complete synchronously, cause an exception and dispose your Connection, so you should call it after the EndReceive.

    private void on_data_received(IAsyncResult ar)
    {
        try
        {
            int bytesRead = Client.Client.EndReceive(ar);

            if (bytesRead > 0)
            {
                byte[] data = new byte[bytesRead];
                Array.Copy(m_message, data, bytesRead);

                m_coder.Push(data);

                Client.Client.BeginReceive(m_message, 0, m_message.Length,
                    SocketFlags.None, new AsyncCallback(on_data_received), null);
            }
            else
            {
                //TODO Close the connection
            }

        }
        catch(Exception ex)
        {
            Console.WriteLine("Connection::on_data_received : {0}", ex.Message);
            handle_connection_lost(new ConnectionLostArgs(Id));
        }
    }

Note: You should close your connection when receiving 0 bytes, this means the remote endpoint has closed. Not doing so may cause infinite loop.

SelflessCoder
That's a valid point, however it didn't solve the main issue. Server--data-->Client :( Still struggling to get this done
Maciek
A: 

I've figured it out - finally.

The problem was looking pretty innocent, examine the below code.

public void Send(byte[] data)
        {
            try
            {
                using (NetworkStream s = Client.GetStream())
                {
                    using (BinaryWriter w = new BinaryWriter(s))
                    {
                        var buffer = m_coder.Encode(data);
                        w.Write(buffer);
                        w.Flush();
                    }
                }

            }
            catch
            { handle_connection_lost(new ConnectionLostArgs(Id)); }
        }

When disposing (thx to the using keyword) either the BinaryWriter or the NetworkStream the socket would get disposed (I'm not sure if this is the desired behavior) - and thus break the connection. Removing the "using" clauses solved the problem.

Posting the answer here in case anyone else runs into something similar.

Maciek