Список десериализации<T> по проблеме сетевого потока

Привет я пытаюсь отправить и получить некоторые данные между клиент-серверным приложением.

Учебный класс

[Serializable]
public class ScanSessie
{
    public string UserId { get; set; }
    public int TotalScanned { get; set; }
    public string Status { get; set; }
    public string DeviceId { get; set; }
}

Методы расширения сериализатора и десериализатора:

public static class SerializerDeserializerExtensions
{
    public static byte[] Serializer(this object _object)
    {
        byte[] bytes;
        using (var _MemoryStream = new MemoryStream())
        {
            IFormatter _BinaryFormatter = new BinaryFormatter();
            _BinaryFormatter.Serialize(_MemoryStream, _object);
            bytes = _MemoryStream.ToArray();
        }
        return bytes;
    }

    public static T Deserializer<T>(this byte[] _byteArray)
    {
        T ReturnValue;

        using (var _MemoryStream = new MemoryStream(_byteArray))
        {
            IFormatter _BinaryFormatter = new BinaryFormatter();
            ReturnValue = (T)_BinaryFormatter.Deserialize(_MemoryStream);
        }
        return ReturnValue;
    }
}

Вот пример данных, которые я пытаюсь отправить и десериализовать:

    List<ScanSessie> scannerCl = new List<ScanSessie>();
    scannerCl.Add(new ScanSessie { DeviceId = "0x00456321", UserId = "123456", Status = "scannen ...", TotalScanned = 0 });
    scannerCl.Add(new ScanSessie { DeviceId = "0x00456321", UserId = "123456", Status = "scannen ...", TotalScanned = 0 });
    scannerCl.Add(new ScanSessie { DeviceId = "0x00456321", UserId = "123456", Status = "scannen ...", TotalScanned = 0 });
    scannerCl.Add(new ScanSessie { DeviceId = "0x00456321", UserId = "123456", Status = "scannen ...", TotalScanned = 0 });
    scannerCl.Add(new ScanSessie { DeviceId = "0x00456321", UserId = "123456", Status = "scannen ...", TotalScanned = 0 });
    scannerCl.Add(new ScanSessie { DeviceId = "0x00456321", UserId = "123456", Status = "scannen ...", TotalScanned = 0 });

Отправка с помощью следующего кода:

        NetworkStream broadcastStream = statusSocket.GetStream();

        Byte[] broadcastBytes = SerializerDeserializerExtensions.Serializer(scannerCl);

        broadcastStream.Write(broadcastBytes, 0, broadcastBytes.Length);
        broadcastStream.Flush();

Получение потокового кода. игнорировать цикл n.DataAvailable. Я отправляю для тестирования очень маленькие пакеты ниже буфера, который передается в одной части.

 using (TcpClient client = new TcpClient())
                {
                    await client.ConnectAsync("10.184.32.39", 8888);

                    ConnectionEstabilished?.Invoke();

                    byte[] message = new byte[1024 * 8];
                    int bytesRead;

                    using (NetworkStream n = client.GetStream())
                    {
                        while (true)
                        {
                            bytesRead = 0;

                            try
                            {
                                if(n.CanRead)
                                {
                                    do
                                    {
                                        bytesRead = await n.ReadAsync(message, 0, message.Length);
                                    }
                                    while (n.DataAvailable);
                                }
                                //bytesRead = await n.ReadAsync(message, 0, 4096);
                                //bytesRead = await n.ReadAsync(message, 0, message.Length);
                            }
                            catch (Exception ex)
                            {
                                // some error hapens here catch it          
                                Debug.WriteLine("DashboardClientConnect " + ex.Message + "\n" + ex.InnerException);
                                break;
                            }
                         }

Получающий код инициирует событие с данными в виде байта [], определенного как сообщение. На моем событии обработки я пытаюсь десериализовать его:

private void Sc_NewDataReceived(byte[] scanner)
{
    try
    {
        var result = scanner.Deserializer<List<ScanSessie>>();
    }
    catch(Exception ex)
    {
        Debug.WriteLine(ex.InnerException);
    }
}

На этапе десериализации будет выдано исключение (Исключение: "System.Runtime.Serialization.SerializationException" в mscorlib.dll). Исключение недопустимо.

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

Я также попытался поиграться с размерами принимающего буфера. Кажется, это тоже не помогает. Примерный размер данных ниже 1024*8.

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

Через 2 дня я сдаюсь. Любая помощь будет оценена. Я попытался сделать вопрос информативным, разместив фрагменты кода.

2 ответа

Этот код имеет несколько проблем:

 do
 {
     bytesRead = await n.ReadAsync(message, 0, message.Length);
 }
 while (n.DataAvailable);

Во-первых, отправитель может отправлять байты сообщения кусками. Сначала вы будете читать один блок, затем вы будете читать другой блок, перезаписывая первый в буфере (потому что вы используете один и тот же буфер для всех операций чтения и всегда записываете в индекс 0).

Кроме того, вы не знаете, когда сообщение действительно было получено полностью. Отправитель может отправить один кусок, тогда по какой-то причине будет задержка, в этот момент ваш while (n.DataAvailable) Цикл существует, и вы пытаетесь десериализовать неполное сообщение.

Кроме того, когда вам повезло получить полное сообщение, оставшаяся часть буфера (при условии, что длина сообщения меньше размера буфера) заполнена нулями, что в любом случае препятствует успешной десериализации.

Кроме того - это не очень хорошая идея для использования BinaryFormatter сериализовать объекты по сети. Вместо этого используйте что-то вроде protobuf.

Чтобы исправить ваш сетевой код - сначала отправьте длину вашего сериализованного сообщения. Если у вас разные типы сообщений - также отправьте тип сообщения. После отправки длины (и при необходимости введите) - отправьте само сообщение:

NetworkStream broadcastStream = statusSocket.GetStream();
Byte[] broadcastBytes = SerializerDeserializerExtensions.Serializer(scannerCl);
var lengthBytes = BitConverter.GetBytes(broadcastBytes.Length);
if (!BitConverter.IsLittleEndian)
     Array.Reverse(lengthBytes);
broadcastStream.Write(lengthBytes, 0, lengthBytes.Length);
broadcastStream.Write(broadcastBytes, 0, broadcastBytes.Length);
broadcastStream.Flush();

Тогда на принимающей стороне:

byte[] lengthBuffer = new byte[sizeof(int)];
var bytesRead = await n.ReadAsync(lengthBuffer, 0, lengthBuffer.Length);
// check if bytesRead equals buffer size
if (!BitConverter.IsLittleEndian)
    Array.Reverse(lengthBuffer);
var length = BitConverter.ToInt32(lengthBuffer, 0);
// check if length is not too big, otherwise you will crash process with out of memory
var messageBuffer = new byte[length];
bytesRead = await n.ReadAsync(messageBuffer, 0, messageBuffer.Length);
// check if bytesRead equals buffer size
// now you can deserialize

Обратите внимание, что этот код не проверен, поэтому будьте осторожны.

Я предлагаю использовать WCF, .NET Remoting или что-то подобное. Они предназначены именно для этой цели.

Если вы пишете свой собственный канал передачи, обычно вы должны встраивать сериализованные данные в протокол передачи данных. Обычно он содержит длину сообщения и другую информацию о передаваемых данных. Это необходимо, например, чтобы получатель знал размер данных / сообщения и тип данных. На стороне получателя, как правило, вы не знаете, сколько байтов нужно получить и каковы они.

Другие вопросы по тегам