0

I'm writing a program that uses a TcpListener as the server. Multiple clients connect to it and send/receive packets.

These Packets are just serialized custom classes containing data. I use a BinaryFormatter to serialize and deserialize packets:

[Serializable]
public class Packet
{
    public static implicit operator byte[](Packet p)
    {
        using (var memoryStream = new MemoryStream())
        {
            new BinaryFormatter().Serialize(memoryStream, p);
            return memoryStream.ToArray();
        }
    }

    protected byte[] Serialize(object o)
    {
        using (var memoryStream = new MemoryStream())
        {
            new BinaryFormatter().Serialize(memoryStream, o);
            return memoryStream.ToArray();
        }
    }

    public static object Deserialize(byte[] data)
    {
        using (var memoryStream = new MemoryStream(data))
            return new BinaryFormatter().Deserialize(memoryStream);
    }
}

Here is an example of a packet I send:

[Serializable]
    public class Message : Packet
    {
        public string Text { get; set; }
        public Message(string text)
        {
            Text = text;
        }
        public byte[] Serialize()
        {
            return Serialize(this);
        }
    }

Now the way I send this data is I first create a Header which specifies the length of the packet we're serializing, and then sends that header to the NetworkStream, and then following that, it sends the actual packet.

I wrote an extension class for such tasks:

public static class StreamExtension
{
    public static async Task WritePacket(this NetworkStream stream, byte[] packet)
    {
        var header = BitConverter.GetBytes(packet.Length); //Get packet length.
        await stream.WriteAsync(header, 0, header.Length); //Write packet length as header.
        await stream.WriteAsync(packet, 0, packet.Length); //Write actual packet.
        Console.WriteLine($"Writing packet with length {packet.Length}");
    }
}

Now when I attempt to connect, it works fine with the first client that connects, it sends and receives all the necessary packets. But when the second client joins, it ends up trying to deserialize packets that aren't even being sent.

I created a try catch to see what the problem was, and I get an exception saying:

binary stream '0' does not contain a valid binaryheader

This is the method that's receiving data in the client:

private async void ReceiveData()
        {
            var header = new byte[4]; //Indicator for how big our actual packet is.
            var stream = Server.GetStream();
            while (await stream.ReadAsync(header, 0, header.Length) != 0)
            {
                var packetLength = BitConverter.ToInt32(header, 0);
                var buffer = new byte[packetLength];
                await stream.ReadAsync(buffer, 0, buffer.Length); //Read packet.
                try {
                    var receivedPacket = Packet.Deserialize(buffer);
                    lbLog.Items.Add($"Received packet: {receivedPacket.GetType()}");
                    if (receivedPacket is SetPiece)
                    {
                        var setPiece = receivedPacket as SetPiece;
                        MyPiece = setPiece.Piece;
                        lblPiece.Text = $"You are {MyPiece}";
                    }
                    else if (receivedPacket is StartNew)
                    {
                        foreach (var box in Controls.OfType<PictureBox>())
                            box.Image = null;
                    }
                    else if (receivedPacket is SetTurn)
                    {
                        var setTurn = receivedPacket as SetTurn;
                        IsTurn = setTurn.IsMine;
                        lblTurn.Text = setTurn.IsMine ? "Your Turn" : $"{setTurn.Name}'s Turn";
                    }
                    else if (receivedPacket is Log)
                    {
                        var log = receivedPacket as Log;
                        lbLog.Items.Add(log.Message);
                    }
                }
                catch
                {
                    File.AppendAllLines("errors.txt", new[] { $"Error occurred with packet. Length of {buffer.Length}" });
                }
            }
        }

Here is the log of the server console:

enter image description here

And here is the error log:

Error occurred with packet. Length of 162
Error occurred with packet. Length of 256
Error occurred with packet. Length of 1952797486

How can we have an error with a packet that has a length of 1952797486, when it never even gets sent?

Any ideas on what I'm doing wrong?

ThePerplexedOne
  • 2,920
  • 15
  • 30
  • 2
    You're throwing away the information you need. `Read` (or here, `ReadAsync`) returns *how many bytes were actually read*, which can be anywhere between 1 and the number of bytes that you *asked for*. There's no guarantee that you'll get the complete set of bytes that a call of `Write` accepted at the other end. You may have to call `Read` multiple times. If you want *messaging*, it's up to you to implement that atop the stream of bytes that is what TCP gives you, or to move to a higher level protocol which already provides *messaging*. – Damien_The_Unbeliever Mar 29 '17 at 12:39
  • (So, when you do a short read of one of your earlier "packets", some random bytes are left behind to be read the next time you attempt to read a new packet's length and that's what's set to 1952797486) – Damien_The_Unbeliever Mar 29 '17 at 12:41
  • So could I do another `while` loop after reading the header, that only reads the packet when the length is greater than or equal to what we're asking for? – ThePerplexedOne Mar 29 '17 at 12:46
  • 3
    You will need a while loop when reading the header, even it is only 4 bytes. – Zang MingJie Mar 29 '17 at 12:49
  • If you want a network based messaging system, [lidgren](https://github.com/lidgren/lidgren-network-gen3/wiki/Basics) is a fairly lightweight system that I've used in the past. – Bradley Uffner Mar 29 '17 at 13:34
  • So I found an example which fixes the reading of the header, and then reading the packet. But I was still getting similar problems **until** I swapped WriteAsync for Write, and now it works perfectly. Any info here? – ThePerplexedOne Mar 29 '17 at 14:45

0 Answers0