Search code examples
c#network-programmingserializationtcppacket

Data loss TCP IP C#


Here's my code:

private void OnReceive(IAsyncResult result)
{
NetStateObject state = (NetStateObject)result.AsyncState;

Socket client = state.Socket;

int size = client.EndReceive(result);

byte[] data = state.Buffer;

object data = null;

using (MemoryStream stream = new MemoryStream(data))
{
    BinaryFormatter formatter = new BinaryFormatter();

    data = formatter.Deserialize(stream);
}

//todo: something with data

client.BeginReceive(
    state.Buffer,
    0,
    NetStateObject.BUFFER_SIZE,
    SocketFlags.None,
    OnReceive,
    state
    );
}

state.Buffer has a maximum size of NetStateObject.BUFFER_SIZE (1024). Firstly, is this too big or too small? Second, if I send something larger than that, my deserialize messes up because the object it is trying to deserialize doesnt have all the information (because not all the data was sent). How do I make sure that all my data has been received before I try to construct it and do something with it?

Completed Working Code

        private void OnReceive(IAsyncResult result)
    {
        NetStateObject state = (NetStateObject)result.AsyncState;

        Socket client = state.Socket;

        try
        {
            //get the read data and see how many bytes we received
            int bytesRead = client.EndReceive(result);

            //store the data from the buffer
            byte[] dataReceived = state.Buffer;

            //this will hold the byte data for the number of bytes being received
            byte[] totalBytesData = new byte[4];

            //load the number byte data from the data received
            for (int i = 0; i < 4; i++)
            {
                totalBytesData[i] = dataReceived[i];
            }

            //convert the number byte data to a numan readable integer
            int totalBytes = BitConverter.ToInt32(totalBytesData, 0);

            //create a new array with the length of the total bytes being received
            byte[] data = new byte[totalBytes];

            //load what is in the buffer into the data[]
            for (int i = 0; i < bytesRead - 4; i++)
            {
                data[i] = state.Buffer[i + 4];
            }

            //receive packets from the connection until the number of bytes read is no longer less than we need
            while (bytesRead < totalBytes + 4)
            {
                bytesRead += state.Socket.Receive(data, bytesRead - 4, totalBytes + 4 - bytesRead, SocketFlags.None);
            }

            CommandData commandData;

            using (MemoryStream stream = new MemoryStream(data))
            {
                BinaryFormatter formatter = new BinaryFormatter();

                commandData = (CommandData)formatter.Deserialize(stream);
            }

            ReceivedCommands.Enqueue(commandData);

            client.BeginReceive(
                state.Buffer,
                0,
                NetStateObject.BUFFER_SIZE,
                SocketFlags.None,
                OnReceive,
                state
                );

            dataReceived = null;
            totalBytesData = null;
            data = null;
        }
        catch(Exception e)
        {
            Console.WriteLine("***********************");
            Console.WriteLine(e.Source);
            Console.WriteLine("***********************");
            Console.WriteLine(e.Message);
            Console.WriteLine("***********************");
            Console.WriteLine(e.InnerException);
            Console.WriteLine("***********************");
            Console.WriteLine(e.StackTrace);
        }
    }

Solution

  • TCP is a stream protocol. It has no concept of packets. A single write call can be sent in multiple packets, and multiple write calls can be put into the same packet. So you need to implement your own packetizing logic on top of TCP.

    There are two common ways to packetize:

    1. Delimiter characters, this is usually used in text protocols, with the new-line being a common choice
    2. Prefix the length to each packet, usually a good choice with binary protocols.

    You store the size of a logical packet at the beginning of that packet. Then you read until you received enough bytes to fill the packet and start deserializing.