Data loss TCP IP C#

2019-02-19 14:08发布

问题:

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);
        }
    }

回答1:

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.



回答2:

How do I make sure that all my data has been received before I try to construct it and do something with it?

You have to implement some protocol so you know.

While TCP is reliable, it does not guarantee that the data from single write at one end of the socket will appear as a single read at the other end: retries, packet fragmentation and MTU can all lead to data being received in different sized units by the receiver. You will get the data in the right order.

So you need to include some information when sending that allows the receiver to know when it has the complete message. I would also recommend including what kind of message and what version of the data (this will form the basis of being able to support different client and server versions together).

So the sender sends: - Message type - Message version - Message size (in bytes)

And the receiver will loop, performing a read with a buffer and appending this to a master buffer (MemoryStream is good for this). Once the complete header is received it knows when the complete data has been received.

(Another route is to include some pattern as an "end of message" marker, but then you need to handle the same sequence of bytes occurring in the content—hard to do if the data is binary rather than text.)