0

I have a client and server class in C# that uses socket communication. The Server looks like this:

    public class AsyncTcpServer
    {
        private Socket _server_socket;
        private Socket _client_socket;
        private byte[] _receive_buffer;
        private byte[] _send_buffer;
        private NetworkStream _ns;


        public void Start()
        {
            try
            {
                _server_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _server_socket.Bind(new IPEndPoint(IPAddress.Any, 17999));
                _server_socket.Listen(0);
                _server_socket.BeginAccept(new AsyncCallback(BeginAccept), null);
            }
            catch(Exception e)
            {
                Debug.Print(e.Message);
            }
        }

        private void BeginAccept(IAsyncResult ar)
        {
            try
            {         
                _client_socket = _server_socket.EndAccept(ar);

                _receive_buffer = new byte[_client_socket.ReceiveBufferSize];
                _send_buffer = new byte[_client_socket.ReceiveBufferSize]; 
                _ns = new NetworkStream(_client_socket);

                _client_socket.BeginReceive(_receive_buffer, 0, _receive_buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null);
            }
            catch(Exception e)
            {
                Debug.Print(e.Message);
            }
        }

        private void RecieveCallback(IAsyncResult ar)
        {
            try 
            {
                string text = Encoding.ASCII.GetString(_receive_buffer);
                Debug.Print("Server Received: " + text);                
            }
            catch (Exception e)
            {
                Debug.Print("Unexpected exception: " + e.Message);
            }
        }

        public void Send(byte [] bytes)
        {
            try
            {
                _ns.Write(bytes, 0, bytes.Length);
            }
            catch (Exception e)
            {
                Debug.Print("Unexpected exception: " + e.Message);
            }            
        }
    }

And the client looks like this:

    public class AsyncTcpClient
    {
        private Socket _client_socket;
        private byte[] _buffer;
        private const int HEADER_SIZE = sizeof(int);

        public void Start()
        {
            _client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);         
            _client_socket.BeginConnect(new IPEndPoint(IPAddress.Loopback, 17999), new AsyncCallback(ConnectCallback), null);
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                _client_socket.EndConnect(ar);                
                _buffer = new byte[_client_socket.ReceiveBufferSize];
                StartReceive();
                byte[] buffer = Encoding.ASCII.GetBytes("Connected!");
                _client_socket.Send(buffer);
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
        }

        private void StartReceive(int offset = 0)
        {
            try
            {
                _client_socket.BeginReceive(_buffer, offset, _buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null);
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
        }

        private void RecieveCallback(IAsyncResult ar)
        {
            try
            {
                int bytes_processed = 0;
                int bytes_read = _client_socket.EndReceive(ar);

                if (bytes_read > 0)
                {
                    NetworkStream ns = new NetworkStream(_client_socket);

                    while (ns.DataAvailable && (bytes_processed < bytes_read))
                    {
                        byte[] len_bytes = new byte[HEADER_SIZE];
                        ns.Read(len_bytes, 0, HEADER_SIZE);
                        int current_chunk_size = BitConverter.ToInt32(len_bytes, 0);

                        if (current_chunk_size > 0)
                        {
                            byte[] data_buff = new byte[current_chunk_size];
                            ns.Read(data_buff, 0, current_chunk_size);
                            string s = Encoding.ASCII.GetString(data_buff);
                            bytes_processed += (HEADER_SIZE + current_chunk_size);
                            Debug.WriteLine(s);
                        }
                    }                                        
                }
                StartReceive();         
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }

            StartReceive();
        }
    }

They work together as follows:

  • Server starts
  • Client connects
  • Server sends custom packets to the client for its comsumption

I use the following 'data structure' to package my transmission data on the server side to send to the client:

{[DATA_LENGTH_IN_BYTES][PAYLOAD_BYTES]}

On the client side, I parse the first 4 bytes (sizeof(int)) to determine the payload length and then parse the payload itself. This works the first time I do it but after that the DataAvailable member of the NetworkStream is false and I can't parse the rest of the payload.

Why is DataAvailable false? I'm pretty new to doing this stuff in C# - am I approaching it the wrong way entirely?

Thanks in Advance!

Pat Mustard
  • 1,852
  • 9
  • 31
  • 58

2 Answers2

2

I think you forget the EndReceive in the RecieveCallback. (server code)

    private void RecieveCallback(IAsyncResult ar)
    {
        try 
        {
            int bytesReceived = _client_socket.EndReceive(ar); // <---
            string text = Encoding.ASCII.GetString(_receive_buffer);
            Debug.Print("Server Received: " + text);                
        }
        catch (Exception e)
        {
            Debug.Print("Unexpected exception: " + e.Message);
        }
    }

I advise to create one class that reads/writes to sockets (implementing the protocol). A Base class that handles reads/writes to Sockets, a client socket that is derived from the SocketConnection, but first will connect to an ipendpoint. A server that has a List of SocketConnection. This way you keep your client/server functionality separated of the message handling on socket. But both using the same code to receive/send messages. Here is an example:

Pseudo:

// base class that handle receive/sent packets
class SocketConnection
{

    // the reason for a Start method is that event can be bound before the Start is executed.
    void Start(Socket socket)
    {
        StartReceive();
    }

    void StartReceive()
    {
        socket.BeginReceive(...);
    }

    void EndReceive()
    {
        socket.EndReceive(...);
        // handle received message.
        // call the ondata event or something
        StartReceive();
    }
}

class ClientSocket : SocketConnection
{
    void Connect()
    {
        Socket socket = new Socket(...);
        socket.Connect(..);

        // start receiving from the client socket.
        base.Start(socket);
    }
}

class Server
{
    List<SocketConnection> _clients;

    void Start()
    {
        // Create server socket + listening etc..

        StartAccept();
    }

    void StartAccept()
    {
        serverSocket.BeginAccept(...);
    }

    void EndAccept()
    {
        Socket serverClientSocket = EndAccept(...);

        // create a base socket handler.....
        SocketConnection clientSocket = new SocketConnection();

        _clients.Add(clientSocket);
        // bind the ondata event of the client and pass it to the clientondata event of the server.
        // Start receiving from the socket.
        clientSocket.Start(serverClientSocket);

        // accept new clients.
        StartAccept();
    }
}

UPDATE:

"Regarding how to handle buffers, what would you suggest as the best way to not miss data in separate packets?"

I would send a size first:

// sending part.
string message = "This is a message";
byte[] buffer = Encoding.ASCII.GetBytes(message);

_client_socket.Send(BitConverter.GetBytes(buffer.Length)); // sends a int as 4 bytes.
_client_socket.Send(data);



// receiving part.
// try to receive at least 4 bytes. (no more/ no less)

int length = BitConverter.ToInt32(buffer, 0); 

// try to receive data with `length` size,  (no more/ no less)

This will separate different packets.


Asynchronous example:


You still need to add some exception handling code.

public static class SocketReader
{
    public static void ReadFromSocket(Socket socket, int count, Action<byte[]> endRead)
    {
        // read from socket, construct a new buffer.
        DoReadFromSocket(socket, 0, count, new byte[count], endRead);
    }

    public static void ReadFromSocket(Socket socket, int count, ref byte[] buffer, Action<byte[]> endRead)
    {
        // if you do have a buffer available, you can pass that one. (this way you do not construct new buffers for receiving.
        // the ref is because if the buffer is too small, it will return the newly created buffer.

        // if the buffer is too small, create a new one.
        if (buffer.Length < count)
            buffer = new byte[count];

        DoReadFromSocket(socket, 0, count, buffer, endRead);
    }

    // This method will continues read until count bytes are read. (or socket is closed)
    private static void DoReadFromSocket(Socket socket, int bytesRead, int count, byte[] buffer, Action<byte[]> endRead)
    {
        // Start a BeginReceive.
        socket.BeginReceive(buffer, bytesRead, count - bytesRead, SocketFlags.None, (result) =>
        {
            // Get the bytes read.
            int read = socket.EndReceive(result);

            // if zero bytes received, the socket isn't available anymore.
            if (read == 0)
            {
                endRead(new byte[0]);
                return;
            }

            // increase the bytesRead, (index point for the buffer)
            bytesRead += read;

            // if all bytes are read, call the endRead with the buffer.
            if (bytesRead == count)
                endRead(buffer);
            else
                // if not all bytes received, start another BeginReceive.
                DoReadFromSocket(socket, bytesRead, count, buffer, endRead);

        }, null);
    }

}

Example how to use it:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


// read header. (length of data) (4 bytes)
SocketReader.ReadFromSocket(socket, 4, (headerBuffer) =>
    {
        if (headerBuffer.Length == 0)
        {
            // disconnected;
            return;
        }


        int length = BitConverter.ToInt32(headerBuffer, 0);

        // read bytes specified in length.
        SocketReader.ReadFromSocket(socket, length, (dataBuffer) =>
            {
                if (dataBuffer.Length == 0)
                {
                    // disconnected;
                    return;
                }

                // if you want this in a stream, you can do: This way the stream is readonly and only wraps arround the bytearray.
                using (MemoryStream stream = new MemoryStream(dataBuffer, 0, length))
                using (StreamReader reader = new StreamReader(stream))
                    while (!reader.EndOfStream)
                        Debug.WriteLine(reader.ReadLine());

            });

    });
Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • Hi Jeroen, thanks for the in-depth answer. Unfortunately, calling end receive on the server side didn't work but I has a rethink and decided to go with wrapped sockets in the form of TcpListener and TcpClient. My new solution is listed below... – Pat Mustard Sep 03 '13 at 06:04
2

Here is the solution I settled on:

Server:

public class Listener
    {
        private TcpListener _listener;
        private TcpClient _client;

        public void Start()
        {
            _listener = new TcpListener(IPAddress.Loopback, 17999);
            _listener.Start();
            _listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), _listener);
        }

        private void AcceptTcpClientCallback(IAsyncResult ar)
        {
            try
            {
                Debug.WriteLine("Accepted tcp client callback");
                _client = _listener.EndAcceptTcpClient(ar);               
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }            
        }

        public void Write(string data)
        {
            try
            {
                NetworkStream ns = _client.GetStream();
                byte[] buffer = Encoding.ASCII.GetBytes(data);
                ns.Write(buffer, 0, buffer.Length);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }

    }

And the client:

public class Client
    {
        private TcpClient _client;
        private byte[] _buffer;

        public void Start()
        {
            try
            {
                _client = new TcpClient();
                _client.BeginConnect(IPAddress.Loopback, 17999, new AsyncCallback(ConnectCallback), _client);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                NetworkStream ns = _client.GetStream();
                _buffer = new byte[_client.ReceiveBufferSize];
                ns.BeginRead(_buffer, 0, _buffer.Length, new AsyncCallback(ReadCallback), null);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }

        private void ReadCallback(IAsyncResult ar)
        {
            try
            {
                NetworkStream ns = _client.GetStream();
                int read = ns.EndRead(ar);
                string data = Encoding.ASCII.GetString(_buffer, 0, read);

                var res = data.Split(new [] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

                foreach (var r in res)
                {
                    Debug.WriteLine(r); // process messages
                }
                ns.BeginRead(_buffer, 0, _buffer.Length, ReadCallback, _client);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }            
        }
    }

Where messages from the server to the client are formed as follows:

string message = "This is a message" + "\r\n";
_listener.Send(message);

I like this alternative for its simplicity. Its a lot shorter and (to me at least) much easier to manage.

HTH

Pat Mustard
  • 1,852
  • 9
  • 31
  • 58
  • Only the `NetworkStream networkStream = _client.GetStream();` in the AcceptTcpClientCallback doesn't do anything. Does it work now? And the problem is, when a message is split-up into serveral packets, it goes wrong. tip: if you don't remove the \r\n you could use the Debug.Write method. This way partial message are handled easier. – Jeroen van Langen Sep 03 '13 at 06:23
  • `NetworkStream networkStream = _client.GetStream();` is not necessary, you are correct, this was an over sight and doesn't do anything. I have edited the answer. I only want to be able to write to the client, once it connects. Regarding how to handle buffers, what would you suggest as the best way to not miss data in separate packets? – Pat Mustard Sep 03 '13 at 06:38
  • Adding your edits to my solution I end up reading twice: once with `int bytes_read = ns.EndRead(ar);` and one again with `ns.Read(len_bytes, 0, sizeof(Int32));`. Is your suggestion to not use NetworkStreams at all in favor of managing the `_buffer` and a read index? – Pat Mustard Sep 03 '13 at 07:57
  • 1
    I made an asynchronous example, (update) how i would use it. Try to grasp it. – Jeroen van Langen Sep 03 '13 at 19:09