【问题标题】:Deserializing List<T> over networkstream issue通过网络流问题反序列化 List<T>
【发布时间】:2017-06-15 07:07:29
【问题描述】:

您好,我正在尝试在客户端/服务器应用程序之间发送和接收一些数据。

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

Serializer 和 Deserializer 扩展方法:

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

接收代码将触发一个事件,其数据为 byte[] 定义为消息。 在我的处理事件中,我尝试使用以下方法对其进行反序列化:

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

在反序列化步骤中,它会抛出异常(抛出异常:mscorlib.dll 中的“System.Runtime.Serialization.SerializationException”) 内部异常为空。

当我使用扩展方法而不通过网络发送一些示例数据时,反序列化工作完美无缺。

我也尝试过使用接收缓冲区大小。这似乎也没有帮助。示例数据大小在 1024*8 以下。

以发送数据长度为 600 字节为例。接收端缓冲区是否也需要相同大小?通常这应该是个问题,因为它是分块读取的。

两天后我放弃了。任何帮助,将不胜感激。我试图通过放置功能代码 sn-ps 来使问题提供信息。

【问题讨论】:

  • 如果您的消息大小小于缓冲区大小 (1024*8) - 缓冲区的其余部分用零填充,这当然会阻止正确的反序列化。更不用说每次读取都会覆盖缓冲区的一部分(在while DataAvailable 循环中)。
  • 不支持尝试使用 NetworkStream.Length,因此您需要定义一个固定的缓冲区大小。大或小。为了测试它,我检查了我的样本数据的大小,它是 606 字节。我也将接收缓冲区调整为 606 字节,同样的异常发生了。 while 循环确实覆盖了它。这是一个示例代码。发送数据远低于缓冲区大小,因此它不会循环它:)
  • 您可以直接将NetworkStream 提供给BinaryFormatter,而不是通过MemoryStream 进行序列化。但是,如果您确实想手动处理数据,请记住Read 操作返回实际读取的字节数,并且它可能会以小于发送数据的块接收数据。收到0字节表示对方已经关闭了发送通道。
  • @C.Evenhuis 我了解缓冲区和块问题。在此示例中,情况并非如此。示例数据为 606 字节。它在一块没有块的情况下转移。如果数据比它可能发生的大。
  • @Shift 在较早的评论中,您说 while 循环正在覆盖您的数据,我很确定您没有接收一个块中的数据,因为循环如果没有要从流中读取的数据,则不会覆盖任何数据。

标签: c# networkstream


【解决方案1】:

这段代码有几个问题:

 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

请注意,此代码未经测试,因此请小心。

【讨论】:

  • 我明白了。数据非常小导致缓冲区问题。我正在发送 606 字节的小数据包。稍后我会在反序列化作品时处理该部分。
  • 但是那里列出了其他问题,例如缓冲区填充为零,因为您不知道缓冲区大小。
  • 正如我上面提到的,接收缓冲区设置为 606(基于我发送的内容)没有零,它仍然不会反序列化它。
  • 那么只要比较你收到的和你发送的,你发送的和你能成功反序列化的,我们不能神奇地找到你的代码在这方面到底哪里出错了。但与其尝试修复糟糕的代码,不如先改进它,也许它会立即解决您的问题。
【解决方案2】:

我建议使用 WCF、.NET Remoting 或类似的东西。它们正是为此目的而设计的。

如果您编写自己的传输通道,通常您必须将序列化的数据嵌入数据通信协议中。通常,这包含消息长度和有关传输数据的其他信息。这是必需的,例如让接收者知道数据/消息的大小和数据类型。在接收方,通常您不知道必须接收多少字节以及它们是什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多