TcpClient получает байты, которые не отправляются?

Я пишу программу, которая использует TcpListener в качестве сервера. К нему подключаются несколько клиентов и отправляют/принимают пакеты.

Эти Packets представляют собой просто сериализованные пользовательские классы, содержащие данные. Я использую BinaryFormatter для сериализации и десериализации пакетов:

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

Вот пример пакета, который я отправляю:

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

Теперь, как я отправляю эти данные, я сначала создаю Header, который указывает длину пакета, который мы сериализуем, а затем отправляю этот заголовок в NetworkStream, а затем, после этого, он отправляет фактический пакет.

Я написал класс расширения для таких задач:

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

Теперь, когда я пытаюсь подключиться, он отлично работает с первым подключающимся клиентом, он отправляет и получает все необходимые пакеты. Но когда присоединяется второй клиент, он пытается десериализовать пакеты, которые даже не отправляются.

Я создал попытку, чтобы увидеть, в чем проблема, и я получаю исключение, говорящее:

двоичный поток '0' не содержит допустимого двоичного заголовка

Это метод, который получает данные в клиенте:

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

Вот лог консоли сервера:

введите здесь описание изображения

А вот журнал ошибок:

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

Как может возникнуть ошибка с пакетом длиной 1952797486, если он даже не отправляется?

Любые идеи о том, что я делаю неправильно?


person ThePerplexedOne    schedule 29.03.2017    source источник
comment
Вы выбрасываете нужную информацию. Read (или здесь ReadAsync) возвращает сколько байтов было фактически прочитано, что может быть где-то между 1 и количеством байтов, которое вы запросили. Нет никакой гарантии, что вы получите полный набор байтов, который вызов Write принял на другом конце. Возможно, вам придется вызывать Read несколько раз. Если вам нужен обмен сообщениями, вы должны реализовать это поверх потока байтов, который дает вам TCP, или перейти на протокол более высокого уровня, который уже обеспечивает обмен сообщениями. .   -  person Damien_The_Unbeliever    schedule 29.03.2017
comment
(Таким образом, когда вы выполняете краткое чтение одного из ваших более ранних пакетов, некоторые случайные байты остаются позади, чтобы быть прочитанными в следующий раз, когда вы попытаетесь прочитать длину нового пакета, и это то, что установлено на 1952797486)   -  person Damien_The_Unbeliever    schedule 29.03.2017
comment
Итак, могу ли я выполнить еще один цикл while после чтения заголовка, который считывает пакет только тогда, когда длина больше или равна тому, что мы запрашиваем?   -  person ThePerplexedOne    schedule 29.03.2017
comment
Вам понадобится цикл while при чтении заголовка, даже если он всего 4 байта.   -  person Zang MingJie    schedule 29.03.2017
comment
Если вам нужна сетевая система обмена сообщениями, lidgren — довольно легкий система, которую я использовал в прошлом.   -  person Bradley Uffner    schedule 29.03.2017
comment
Итак, я нашел пример, который исправляет чтение заголовка, а затем чтение пакета. Но у меня по-прежнему возникали подобные проблемы, пока я не заменил WriteAsync на Write, и теперь он работает отлично. Любая информация здесь?   -  person ThePerplexedOne    schedule 29.03.2017