Udostępnij za pośrednictwem


Simple Message Framing Sample for TCP Socket

A common misunderstanding for developers new to network programming over TCP sockets is how messages are sent and received.  I frequently hear the statement that "my data is not arriving on the other side of the socket in the same format that I sent it."  The most common cause of this is because the TCP protocol does not guarantee that it will keep message boundaries.  In other words, you could send "Hello World" in a single call to Send(), but the other side of the socket stream may have to do multiple Receive() calls on the socket to get all of the data (the first Receive might return "He" and the second "llo World").  Data might also be concatenated together into a single TCP packet on the wire.  The client may send "Hello World"  and "I am in Seattle" in different calls to Send(), but the other side of the socket may receive it with a single call to Receive() in the format "Hello WorldI am in Seattle".

I thought I would give a simple example that shows how to frame your message so that it comes out on the other side of the network stream in the same format in which it was sent.  Your implementation may need to be more complex than what I have given here. 

Also, I have written this in C# and please be aware that this code does not handle errors in the stream and does not do parameter checking.  If the connection get's dropped in the middle of a mesage or if the msg being sent is greater than what can be sent with an integer size, you will see funny results.  Here, I am trying to keep the code simple and to the point.

      static void SendMessage(Socket socket, string msg) { byte[] data = Encoding.ASCII.GetBytes(msg); byte[] sizeinfo = new byte[4];

            //could optionally call BitConverter.GetBytes(data.length); sizeinfo[0] = (byte)data.Length; sizeinfo[1] = (byte)(data.Length >> 8); sizeinfo[2] = (byte)(data.Length >> 16); sizeinfo[3] = (byte)(data.Length >> 24);

            socket.Send(sizeinfo); socket.Send(data); }

        //note, this function does not handle closed connections in the middle of a message... static string ReadMessage(Socket socket) { byte[] sizeinfo = new byte[4];

            //read the size of the message int totalread = 0, currentread = 0;

            currentread = totalread = socket.Receive(sizeinfo);

            while (totalread < sizeinfo.Length && currentread > 0) { currentread = socket.Receive(sizeinfo, totalread, //offset into the buffer sizeinfo.Length - totalread, //max amount to read SocketFlags.None);

                totalread += currentread; }

            int messagesize = 0;

            //could optionally call BitConverter.ToInt32(sizeinfo, 0); messagesize |= sizeinfo[0]; messagesize |= (((int)sizeinfo[1]) << 8); messagesize |= (((int)sizeinfo[2]) << 16); messagesize |= (((int)sizeinfo[3]) << 24); //create a byte array of the correct size //note: there really should be a size restriction on // messagesize because a user could send // Int32.MaxValue and cause an OutOfMemoryException // on the receiving side. maybe consider using a short instead // or just limit the size to some reasonable value byte[] data = new byte[messagesize];

            //read the first chunk of data totalread = 0;            currentread = totalread = socket.Receive(data, totalread, //offset into the buffer data.Length - totalread, //max amount to read SocketFlags.None);

            //if we didn't get the entire message, read some more until we do while (totalread < messagesize && currentread > 0) { currentread = socket.Receive(data, totalread, //offset into the buffer data.Length - totalread, //max amount to read SocketFlags.None); totalread += currentread; }

            return Encoding.ASCII.GetString(data, 0, totalread); }

I would also recommend that you read Malar Chinnusamy's blog on socket programming considerations

Comments

  • Anonymous
    April 06, 2006
    Recently, Jon Cole from the System.Net&amp;nbsp;QA team posted a great article in response to&amp;nbsp;questions...

  • Anonymous
    April 08, 2006
    If you send "Hello World" and receive "he" + "llo world" then you have not a TCP socket.  For TCP one should have received "He" + "llo World". Please, pay attention when working with these,as some novices will understand really bad things

  • Anonymous
    April 10, 2006
    The comment has been removed

  • Anonymous
    April 10, 2006
    Ioan,  You are correct.  I definitely had a type-o in my explanation and I have fixed the mistake.

  • Anonymous
    April 10, 2006
    Alan,  Good feedback.  I considered writing the code as you mention to eliminate the duplicate calls to Send(), but I purposely made the decision to leave the duplicate calls in to emphasize the fact that the message size is sent as a "prefix" to the actual message.  I will add a note to the post to encourage people to read the comments you have posted.

  • Anonymous
    April 11, 2006
    While Alan is correct that this is one scenario is possible with Send(), Send() it makes it appear as if this is deterministic when it is in fact not. If you send two buffers (B1 and B2) the following are some of the possible scenarios:

    1. B1 is sent as one segment and B2 is sent as another over the wire.
    2. B1 and B2 are coalesced into one segment that is sent over the wire
    3. Part of B1 is sent in one segment, and the remaining bits are sent along with B2 in another segment.
    4. B1 and part of B2 are sent in one segment followed by the rest of B2 in another segment.

    Many factors come into play. If the Nagle algorithm is disabled, the behavior will be different. If the connection is waiting for an ACK, buffers will likely be coalesced. etc...

    The important points are:

    1. As Alan stated, the Send(header), Send(payload) is probably not a good idea.
    2. TCP is a stream protocol. Message boundaries are not guaranteed.

  • Anonymous
    April 20, 2006
    Thank you.  That was the most suscint framing of this particular conversation I have ever seen.

    Thank you again.

    Regards.

  • Anonymous
    April 25, 2006
    As a follow up to my last post (http://blogs.msdn.com/joncole/archive/2006/03/20/555721.aspx) I decided...

  • Anonymous
    October 29, 2006
    Hi Jon, Thanks for the example. It was very helpful for me. Also, I think I may have discovered a bug. In the second while loop of ReadMessage shouldn't totalread += currentread come after the socket.Receive. Thanks, Sean

  • Anonymous
    October 30, 2006
    The comment has been removed

  • Anonymous
    June 20, 2008
    Ihadtorecentlywriteaclient-serversocketapplicationandsoIwasmullingoverallthatneeds...