Search code examples
socketsserversocketasyncsockettcplistener

Wait until the complete message has arrived


I have to integrate 2 systems in a project with TCP communication - a custom protocol was developed.

A message sent over the protocol will look as follows:

MsgLength - 10 positions (in chars, not bytes)
MessageType - 20 positions
Message Number - 9 positions
Timestamp - 19 positions
version - 2 positions
Body - variable (depending on first field)

I am looking into Socket programming but I am wondering what's the best approach to wait for the complete msg to arrive. Probably I need to do a conversion of bytes to number of characters left. Because the socket API only works with bytes.

All tips, code samples, blog posts are welcome :-)

Edit: This is my first version of my code, I am using Async methods because multiple clients can connect at the same time.

  private static void Listen()
        {
            while (true)
            {
                //TcpClient client = _listener.AcceptTcpClient();
                allDone.Reset();
                _listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), _listener);
                allDone.WaitOne();
            }
        }


public static void AcceptCallback(IAsyncResult ar)
        {
            // Signal the main thread to continue.
            allDone.Set();

            // Get the socket that handles the client request.
            //TcpListener listener = (TcpListener)ar.AsyncState;
            TcpClient handler = _listener.EndAcceptTcpClient(ar);

            // Create the state object.
            StateObject state = new StateObject();
            state.client = handler;

            handler.GetStream().BeginRead(state.buffer, 0, StateObject.BufferSize, new AsyncCallback(ReadCallback), state);

        }

public static void ReadCallback(IAsyncResult ar)
        {
            String content = String.Empty;
            Encoding enc = Encoding.GetEncoding("ISO-8859-1");

            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            TcpClient handler = state.client;

               int bytesRead = handler.GetStream().EndRead(ar);
        state.TotalHeaderBytesRead += bytesRead;

            if (bytesRead == 60)
            {
                string header = enc.GetString(state.buffer);
                Header h = HeaderParser.Parse(header);
                //todo read the body

                byte[] bodyBuffer = new byte[((HeaderPlaPo)h).Length - 60];
                state.buffer = bodyBuffer;
                handler.GetStream().BeginRead(state.buffer, 0, bodyBuffer.Length, new AsyncCallback(ReadBodyCallback), state);
                Logger.Log(string.Format("received header {0}", header), System.Diagnostics.TraceLevel.Info);
            }
            else
            {
                // Not all data of header received. Get more.
                handler.GetStream().BeginRead(state.buffer, 0, StateObject.BufferSize - state.TotalHeaderBytesRead, new AsyncCallback(ReadHeaderCallback), state);
            }
        }



public static void ReadBodyCallback(IAsyncResult ar)
        {
            Encoding enc = Encoding.GetEncoding("ISO-8859-1");

            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            TcpClient handler = state.client;

            int bytesRead = handler.GetStream().EndRead(ar);
 int bytesRead = handler.GetStream().EndRead(ar);
        state.TotalBodyBytesRead += bytesRead;

        if (state.buffer.Length == state.TotalBodyBytesRead)

{ //todo we received all string body = enc.GetString(state.buffer); Logger.Log(string.Format("received body {0}", body), System.Diagnostics.TraceLevel.Info); } else { handler.GetStream().BeginRead(state.buffer, bytesRead, state.buffer.Length - state.TotalBodyBytesRead, ReadBodyCallback, state); } }

public class StateObject { // Client socket. public TcpClient client = null; // Size of receive buffer. public static int BufferSize = 60; // Receive buffer. public byte[] buffer = new byte[BufferSize]; // Received data string public StringBuilder sb = new StringBuilder(); //Header public Header header; public Body body; public Int32 TotalBodyBytesRead; public Int32 TotalHeaderBytesRead; }

This code works for clients that send data and close the connection, but when using a connected client to send multiple times, the data is not read -> should I close the connection after reading the complete body?


Solution

  • You do not mention what language you use, I give the examples in Java, if you need it in other language let me know. Anyway, you need to read 60 (10+20+9+19+2) bytes in one shut. Convert the 10 first bytes (chars) into a integer then read that number of byte into a byte[] array.

    For example:

    try
    {
        byte[] buffer = new byte[60];
        mySocket.getInputStream().read(buffer, 0, 60);
        String header = buffer.toString();
        int length = Integer.parseInt(header.substring(0, 9));
        byte[] body = new byte[length];
        mySocket.getInputStream().read(body, 0, length);
    }
    catch (Exception e)
    {
       e.printStackTrace();
    }
    

    EDIT: change to C#

     byte[] buffer = new byte[60];
     mySocket.Receive(buffer, 60, SocketFlags.None);
     // You should ckeck this, UTF7 or UTF8 or UNICODE, etc
     string header = Encoding.UTF7.GetString(buffer, 0, 60);
     int length = Convert.ToInt32(header.Substring(0, 10));
     byte[] body = new byte[length];
     int offset=0;
     // Keep reading the socket until all bytes has been received
     while (length > 0) 
     {
         int ret=mySocket.Receive(body, offset, length, SocketFlags.None);
         if (ret > 0) 
         {
            offset += ret;
            length -= ret;
         }
         else 
            if (ret == 0) 
            {
                // peer has close the socket
            }
            else
            {
               // there is an error in the socket.
            }
    
      }