1
\$\begingroup\$

I posted this question a year ago and until a weeks ago I solved most of the issues that were happening due to incomplete packets, things like trying to read the packet header again when the payload hasn't been fully received yet.

Actions that this method must do:

  1. Check if the packet header has been read
  2. If not, load only the bytes needed to parse the packet header. If we receive an incomplete header we need to keep asking for the remaining bytes for every time this happens until it has been fully received.
  3. Parse the packet header fields.
  4. Load bytes needed to parse the payload, if we receive an incomplete payload we need to keep asking for the remaining bytes for every time this happens until it has been fully received.
  5. Call ParsePacket and reset the packetHeaderRead boolean to parse the next incoming packet.

Code:

public struct Packet
{
    public short commandType, payloadSize;
    public byte[] payload;

    public Packet(short commandType, short payloadSize, byte[] payload)
    {
        this.commandType = commandType;
        this.payloadSize = payloadSize;
        this.payload = payload;
    }
}

public class Connection
{
    public StreamSocket StreamSocket { get; set; }

    private const int PacketHeaderSize = 4;
    private bool connected = true;
    private IInputStream inputStream = null;
    private DataReader dataReader = null;
    private IOutputStream outputStream = null;
    private DataWriter dataWriter = null;

    public Connection(StreamSocket streamSocket)
    {
        StreamSocket = streamSocket;

        inputStream = StreamSocket.InputStream;
        outputStream = StreamSocket.OutputStream;

        dataReader = new DataReader(inputStream)
        {
            InputStreamOptions = InputStreamOptions.None,
            ByteOrder = ByteOrder.LittleEndian
        };

        dataWriter = new DataWriter(outputStream)
        {
            ByteOrder = ByteOrder.LittleEndian,
            UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8
        };
    }

    public async void ParsePackets()
    {
        short command = 0;
        short payloadSize = 0;
        byte[] payload = null;

        bool packetHeaderRead = false;

        while (connected)
        {
            try
            {
                if (!packetHeaderRead)
                {
                    if (dataReader.UnconsumedBufferLength < PacketHeaderSize)
                    {
                        int headerBytesLeft = PacketHeaderSize - (int)dataReader.UnconsumedBufferLength;

                        if (headerBytesLeft > 0)
                        {
                            await dataReader.LoadAsync((uint)headerBytesLeft);
                            continue;
                        }
                    }
                    else
                    {
                        command = dataReader.ReadInt16();
                        payloadSize = dataReader.ReadInt16();
                        packetHeaderRead = true;
                        continue;
                    }
                }
                else
                {
                    int payloadBytesLeft = payloadSize - (int)dataReader.UnconsumedBufferLength;

                    if (payloadBytesLeft > 0)
                    {
                        await dataReader.LoadAsync((uint)payloadBytesLeft);
                    }

                    if (payloadSize == 0)
                    {
                        Packet packet = new Packet(command, payloadSize, payload);
                        ParsePacket(packet);

                        packetHeaderRead = false;
                    }
                    else if (dataReader.UnconsumedBufferLength >= payloadSize)
                    {
                        payload = new byte[payloadSize];
                        dataReader.ReadBytes(payload);

                        Packet packet = new Packet(command, payloadSize, payload);
                        ParsePacket(packet);

                        packetHeaderRead = false;
                    }
                }
            }
            catch (Exception e)
            {
                dataReader.Dispose();
                dataWriter.Dispose();
                StreamSocket.Dispose();

                break;
            }
        }
    }

    private void ParsePacket(Packet packet)
    {
        Debug.WriteLine("Command " + packet.commandType);

        StringBuilder stringBuilder = new StringBuilder("[ ");

        foreach (var b in packet.payload)
        {
            stringBuilder.Append(b + ", ");
        }

        stringBuilder.Append("]");

        Debug.WriteLine("Payload " + stringBuilder.ToString());
    }
}

Method used to create the packets:

public Tuple<short, byte[]> CreatePacket(short command, byte[] payloadBytes)
{
    byte[] commandBytes = BitConverter.GetBytes(command);

    short payloadSize = 0;

    if (payloadBytes != null)
    {
        payloadSize = (short)payloadBytes.Length;
    }

    byte[] payloadSizeBytes = BitConverter.GetBytes(payloadSize);

    if (payloadBytes == null)
    {
        payloadBytes = new byte[0];
    }

    byte[] packet = new byte[commandBytes.Length + payloadSizeBytes.Length + payloadBytes.Length];

    System.Buffer.BlockCopy(commandBytes, 0, packet, 0, commandBytes.Length);
    System.Buffer.BlockCopy(payloadSizeBytes, 0, packet, commandBytes.Length, payloadSizeBytes.Length);
    System.Buffer.BlockCopy(payloadBytes, 0, packet, commandBytes.Length + payloadSizeBytes.Length, payloadBytes.Length);

    return new Tuple<short, byte[]>(command, packet);
}
\$\endgroup\$
  • \$\begingroup\$ I'm wondering, was there a reason why you're not using events or delegates? \$\endgroup\$ – Svek Aug 5 '17 at 17:25
  • \$\begingroup\$ @Svek can you elaborate? Once the packet reaches the parse packet method, events are used to update the UI and current game state. \$\endgroup\$ – Kenny Aug 5 '17 at 18:14
  • 1
    \$\begingroup\$ Well in your opening statement you say "issues that were happening due to incomplete packets" -- I was wondering if that's happening due to blocking, and I didn't see any events or delegates in the code you posted for review. \$\endgroup\$ – Svek Aug 5 '17 at 18:21
  • \$\begingroup\$ @Svek as I understood from the documentation, the LoadAsync method from the DataReader class may return more or fewer bytes than the ones expected and the options to change this behavior on InputStreamOptions doesn’t change this exactly. That’s why I had to call LoadAsync all the times needed to solve the issues. Hopefully correct on this one. \$\endgroup\$ – Kenny Aug 5 '17 at 18:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.