Jason Sgalla
Jason Sgalla

Reputation: 176

Problems with TcpListener reading data from socket in c#

I am having trouble reading large messages sent to a TcpListener from a TcpClient using BeginReceive / BeginSend in C#.

I have tried to append a four byte length header to my messages, but on occasion I will not receive it as the first packet which is causing problems.

For example, I sent a serialized object that contains the values [204, 22, 0, 0] as the first four bytes of the byte array in my BeginSend. What I receive on the server in the BeginReceive is [17, 0, 0, 0]. I have checked Wireshark when sending simple strings and the messages are going through, but there's a problem with my code. Another example, is when I send "A b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 0" to test I constantly receive "p q r . . . 8 9 0".

I thought that if packets were received out of order or if one was dropped, TCP would handle the resubmission of the lost packets and/or reorder them before sending them up. This would mean that my first four bytes should always contain my message size in the header. However, looking at the above examples this isn't the case, or it is and my code is messed up somewhere.

After the code below I just look if it's sending a particular command or type of object then responding based on what was received.

One I have the core functionality in place and get a better understanding of the issue I can begin to refactor, but this really has me at a stand still.

I've been banging my head against a wall for days trying to debug this. I have read several articles and questions about similar problems, but I haven't found a way to apply the suggested fixes to this particular instance.

Thanks in advance for your assistance with this.

The YahtzeeClient in the following code is just a wrapper around TcpClient with playerinformation.

    private void ReceiveMessageCallback(IAsyncResult AR)
    {
        byte[] response = new byte[0];
        byte[] header = new byte[4];
        YahtzeeClient c = (YahtzeeClient)AR.AsyncState;
        try
        {
            // If the client is connected
            if (c.ClientSocket.Client.Connected)
            {
                int received = c.ClientSocket.Client.EndReceive(AR);

                // If we didn't receive a message or a message has finished sending
                // reset the messageSize to zero to prepare for the next message.
                if (received == 0)
                {
                    messageSize = 0;

                    // Do we need to do anything else here to prepare for a new message?
                    // Clean up buffers?
                }
                else
                {
                    // Temporary buffer to trim any blanks from the message received.
                    byte[] tempBuff;

                    // Hacky way to track if this is the first message in a series of messages.
                    // If messageSize is currently set to 0 then get the new message size.
                    if (messageSize == 0)
                    {
                        tempBuff = new byte[received - 4];

                        // This will store the body of the message on the *first run only*.
                        byte[] body = new byte[received - 4];

                        // Only copy the first four bytes to get the length of the message.
                        Array.Copy(_buffer, header, 4);

                        // Copy the remainder of the message into the body.
                        Array.Copy(_buffer, 4, body, 0, received - 4);

                        messageSize = BitConverter.ToInt32(header, 0);

                        Array.Copy(body, tempBuff, body.Length);
                    }
                    else
                    {
                        // Since this isn't the first message containing the header packet
                        // we want to copy the entire contents of the byte array.
                        tempBuff = new byte[received];
                        Array.Copy(_buffer, tempBuff, received);
                    }

                    // MessageReceived will store the entire contents of all messages in this tranmission.
                    // If it is an new message then initialize the array.
                    if (messageReceived == null || messageReceived.Length == 0)
                    {
                        Array.Resize(ref messageReceived, 0);
                    }

                    // Store the message in the array.
                    messageReceived = AppendToArray(tempBuff, messageReceived);

                    if (messageReceived.Length < messageSize)
                    {
                        // Begin receiving again to get the rest of the packets for this stream.
                        c.ClientSocket.Client.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveMessageCallback, c);
                        // Break out of the function.  We do not want to proceed until we have a complete transmission.
                        return;
                    }

                    // Send it to the console
                    string message = Encoding.UTF8.GetString(messageReceived);

**Marked as resolved. The solution was to wrap the message in a header and end of message terminator then modify the code to look for these indicators.

The reasoning behind the use of sockets was due to constraints on the project where web services were not an option.

Upvotes: 1

Views: 1556

Answers (2)

usr
usr

Reputation: 171246

You don't seem to have a good understanding of the fact that TCP is a stream of bytes without boundaries. For example your header reading will fail if you get less than 4 bytes in that read.

A very simple way to receive a length prefixed message is using BinaryReader:

var length = br.ReadInt32();
var data = br.ReadBytes(length);

That's all. Make yourself familiar with all the standard BCL IO classes.

Usually it is best not to use sockets at all. Use a higher level protocol. WCF is good.

Upvotes: -1

Alexandre Borela
Alexandre Borela

Reputation: 1626

You are having problems because you don't know the first message size and sometimes you are getting more, sometimes less, sometimes you getting some of what's left in cache...

An easy solution is to ALWAYS send the message size before the actual messages, something like:

[MYHEADER][32 Bit Integer][Message Content]

Lets supose that MYHEADER is ASCII, just an dummy identifier, in this case I would:

1: Try to receive 12 bytes to catch the entire header (MYHEADER + 32Bit Integer) and don't do anything until you do. After that, if the header identifier IS NOT MYHEADER, then I would assume that the message got corrupted and would have something like a reset in the connection.

2: After I confirmed that the header is ok, I would check the 32bit integer for the message size and allocate the necessary buffer. (You might want to limit the memory usage here, something like 6Mb max and if your messages go beyond this, add a index after the 32bit integer to specify the message part...)

3: Try to receive until the message size specified in the header.

Upvotes: 2

Related Questions