【问题标题】:Why I get a SerializationException with BinaryFormatter?为什么我使用 BinaryFormatter 得到 SerializationException?
【发布时间】:2017-06-15 06:50:48
【问题描述】:

我正在开发一个 .NET 应用程序,其中服务器通过 TCP/IP 将 JPG 压缩图像从网络摄像头发送到客户端。对于序列化/反序列化,我使用BinaryFormatter 类。在我的台式计算机(客户端/Windows 10)和我的笔记本电脑(服务器/Windows 10)之间进行测试时,从长远来看,一切正常。使用 LattePanda(服务器/Windows 7)时,运行大约 3-5 分钟后出现以下错误(每秒发送/接收 30 帧):

An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in mscorlib.dll

Additional information: The input stream is not a valid binary format. The starting contents (in bytes) are: 00-00-00-01-00-00-00-FF-FF-FF-FF-01-00-00-00-00-00 ...

这是来自服务器代码的 sn-p:

    private void distribute(Camera camera, Mat frame) {
        if(clientSockets!=null) {
            if(clientSockets.Count > 0) {
                if(camera.Streaming) {
                    // compress and encapsulate raw image with jpg algorithm
                    CameraImage packet = new CameraImage(camera.Id, frame, camera.CodecInfo, camera.EncoderParams);
                    packet.SystemId = Program.Controller.Identity.Id;
                    packet.SequenceNumber = curSeqNum;
                    byte[] content;
                    using(MemoryStream ms = new MemoryStream()) {
                        BinaryFormatter bf = new BinaryFormatter();
                        bf.Serialize(ms, packet);
                        content = ms.ToArray();
                    }
                    byte[] payload = new byte[content.Length+4];
                    // prefix with packet length
                    Array.Copy(BitConverter.GetBytes(content.Length), 0, payload, 0, 4);
                    // append payload after length header
                    Array.Copy(content, 0, payload, 4, content.Length);
                    // distribute to connected clients
                    this.distribute(payload);
                }
            }
        }
    }

    private void distribute(byte[] bytes) {
        if(Program.Launched) {
            lock(syncobj) {
                // distribute to connected clients
                for(int i=clientSockets.Count-1; i>=0; i--) {
                    try {
                        clientSockets[i].Send(bytes, bytes.Length, SocketFlags.None);
                    } catch(SocketException) {
                        clientSockets.RemoveAt(i);
                    }
                }
            }
        }
    }

这是来自客户端代码的 sn-p:

    private void receive() {
        try {
            while(running) {
                if((available = clientSocket.Receive(buffer, 4, SocketFlags.None)) > 0) {
                    // receive bytes from tcp stream
                    int offset = 0;
                    int bytesToRead = BitConverter.ToInt32(buffer, 0);
                    byte[] data = new byte[bytesToRead];
                    while(bytesToRead > 0) {
                        int bytesReceived = clientSocket.Receive(data, offset, bytesToRead, SocketFlags.None);
                        offset += bytesReceived;
                        bytesToRead -= bytesReceived;
                    }
                    // deserialize byte array to network packet
                    NetworkPacket packet = null;
                    using(MemoryStream ms = new MemoryStream(data)) {
                        BinaryFormatter bf = new BinaryFormatter();
                        packet = (NetworkPacket)bf.Deserialize(ms);
                    }
                    // deliver network packet to listeners
                    if(packet!=null) {
                        this.onNetworkPacketReceived?.Invoke(packet);
                    }
                    // update network statistics
                    NetworkStatistics.getInstance().TotalBytesRtpIn += data.Length;
                }
            }
        } catch(SocketException ex) {
            onNetworkClientDisconnected?.Invoke(agent.SystemId);
        } catch(ObjectDisposedException ex) {
            onNetworkClientDisconnected?.Invoke(agent.SystemId);
        } catch(ThreadAbortException ex) {
            // allows your thread to terminate gracefully
            if(ex!=null) Thread.ResetAbort();
        }
    }

任何想法为什么我只在一台机器上而不是在另一台机器上得到SerializationException?安装了不同的mscorlib.dll 库?如何查看相关 (?) 库的版本?

【问题讨论】:

  • 任何机会clientSocket.Receive(buffer, 4, SocketFlags.None)) 返回值< 4> 0?此外,BinaryFormatter 仅用于同一台机器上进程之间的 IPC,不适用于持久存储或网络通信。这是一个非常脆弱的协议。我强烈建议您切换到不同的,例如 ProtoBuf-net,它旨在处理机器之间的差异。
  • 最好不要搜索此错误的来源,而只是切换出 BinaryFormatter。不适合这个任务。
  • 我的钱将完全按照@Scott 所说的那样进行——第一次阅读是安全的。我也是删除 BinaryFormatter 想法的忠实拥护者,并且是公认的建议库的有偏见的倡导者....咳
  • @dbc 除了可疑的第一次读取:是的,它们是 - 它们具有有效负载的长度前缀。它的实施效率不是特别高,但是:修复第一次阅读,理论上它应该可以正常工作。
  • @salocinx 好吧,如果你改变主意...我总是愿意帮助人们。

标签: c# binaryformatter


【解决方案1】:

这是your answer 的调整版本。现在如果clientSocket.Available < 4running == true 你有一个空的while(true) { } 循环。这将占用一个 cpu 核心的 100% 做无用的工作。

在系统 I/O 缓冲区中有 4 个字节之前,不要循环执行任何操作,而是使用您为消息的有效负载执行的循环,并将其加载到您的字节数组中作为标头。 (我还把读取payload数据的循环简化为我不常用的循环。)

private void receive() {
    try {
        while(running) {
            int offset = 0;
            byte[] header = new byte[4];

            // receive header bytes from tcp stream
            while (offset < header.Length) {
                offset += clientSocket.Receive(header, offset, header.Length - offset, SocketFlags.None);
            }
                int bytesToRead = BitConverter.ToInt32(header, 0);
                // receive body bytes from tcp stream
                offset = 0;
                byte[] data = new byte[bytesToRead];
                while(offset < data.Length) {
                    offset += clientSocket.Receive(data, offset, data.Length - offset, SocketFlags.None);
                }
                // deserialize byte array to network packet
                NetworkPacket packet = null;
                using(MemoryStream ms = new MemoryStream(data)) {
                    BinaryFormatter bf = new BinaryFormatter();
                    packet = (NetworkPacket)bf.Deserialize(ms);
                }
                // deliver network packet to listeners
                if(packet!=null) {
                    this.onNetworkPacketReceived?.Invoke(packet);
                }
                // update network statistics 
                NetworkStatistics.getInstance().TotalBytesRtpIn += data.Length;
            }
        }
    } catch(SocketException ex) {
        onNetworkClientDisconnected?.Invoke(agent.SystemId);
    } catch(ObjectDisposedException ex) {
        onNetworkClientDisconnected?.Invoke(agent.SystemId);
    } catch(ThreadAbortException ex) {
        // allows your thread to terminate gracefully
        if(ex!=null) Thread.ResetAbort();
    }
}

但是,如果您从 System.Net.Socket 类切换到 System.Net.TcpClient 类,则可以大大简化您的代码。首先,如果您不需要 TotalBytesRtpIn 进行更新,您可以停止发送标头,反序列化不需要它,因为 BinaryFormatter 已经将其长度存储为有效负载的内部字段。然后您需要做的就是从TcpClient 获取 NetworkStream 并在数据包进入时对其进行处理。

private TcpClient _client; // Set this wherever you had your original Socket set up.

private void receive() {
    try {
        using(var stream = _client.GetStream()) {
            BinaryFormatter bf = new BinaryFormatter();
            while(running) {


#region This part is not needed if you are only going to deserialize the stream and not update TotalBytesRtpIn, make sure the server stops sending the header too!
                    int offset = 0;
                    byte[] header = new byte[4];

                    // receive header bytes from tcp stream
                    while (offset < header.Length) {
                        offset += stream.Read(header, offset, header.Length - offset);
                    }
                    int bytesToRead = BitConverter.ToInt32(header, 0);
#endregion
                    packet = (NetworkPacket)bf.Deserialize(stream);

                    // deliver network packet to listeners
                    if(packet!=null) {
                        this.onNetworkPacketReceived?.Invoke(packet);
                    }
                    // update network statistics
                    NetworkStatistics.getInstance().TotalBytesRtpIn += bytesToRead;
                }
            }
        }
    //These may need to get changed.
    } catch(SocketException ex) {
        onNetworkClientDisconnected?.Invoke(agent.SystemId);
    } catch(ObjectDisposedException ex) {
        onNetworkClientDisconnected?.Invoke(agent.SystemId);
    } catch(ThreadAbortException ex) {
        // allows your thread to terminate gracefully
        if(ex!=null) Thread.ResetAbort();
    }
}

【讨论】:

  • 非常感谢!您的方法要好得多,我的 i7 870 上的整体 CPU 负载从 59% 下降到 30% :-)!有空我会尽快尝试 TCPClient 替代方案。
【解决方案2】:

正如 Scott 建议的那样,我用更安全的方法替换了读取消息标题的不安全行:

    private void receive() {
        try {
            while(running) {
                if(clientSocket.Available>=4) {
                    // receive header bytes from tcp stream
                    byte[] header = new byte[4];
                    clientSocket.Receive(header, 4, SocketFlags.None);
                    int bytesToRead = BitConverter.ToInt32(header, 0);
                    // receive body bytes from tcp stream
                    int offset = 0;
                    byte[] data = new byte[bytesToRead];
                    while(bytesToRead > 0) {
                        int bytesReceived = clientSocket.Receive(data, offset, bytesToRead, SocketFlags.None);
                        offset += bytesReceived;
                        bytesToRead -= bytesReceived;
                    }
                    // deserialize byte array to network packet
                    NetworkPacket packet = null;
                    using(MemoryStream ms = new MemoryStream(data)) {
                        BinaryFormatter bf = new BinaryFormatter();
                        packet = (NetworkPacket)bf.Deserialize(ms);
                    }
                    // deliver network packet to listeners
                    if(packet!=null) {
                        this.onNetworkPacketReceived?.Invoke(packet);
                    }
                    // update network statistics
                    NetworkStatistics.getInstance().TotalBytesRtpIn += data.Length;
                }
            }
        } catch(SocketException ex) {
            onNetworkClientDisconnected?.Invoke(agent.SystemId);
        } catch(ObjectDisposedException ex) {
            onNetworkClientDisconnected?.Invoke(agent.SystemId);
        } catch(ThreadAbortException ex) {
            // allows your thread to terminate gracefully
            if(ex!=null) Thread.ResetAbort();
        }
    }

现在它运行完美:) 非常感谢您的帮助!

【讨论】:

  • 而不是 if(clientSocket.Available&gt;=4),只需在代码中执行与 while(bytesToRead &gt; 0) { 相同的循环,只需将 bytesToRead 设为 4。您当前的方法将有一个紧密循环,用完 100%等待数据包到达时的 CPU。
  • 感谢您的评论。 receive() 函数在线程中运行并且工作正常。我不确定你到底是什么意思。你能用你建议的修改发布一个答案吗?然后我将对其进行测试并删除我自己的答案以接受您的答案。谢谢!
  • 已发布,如果您愿意使用 TcpClient 而不是 Socket,我还提供了一个更简单的替代实现
猜你喜欢
  • 2011-01-08
  • 2019-06-11
  • 1970-01-01
  • 2019-12-27
  • 2021-08-04
  • 2019-03-28
  • 2021-03-05
  • 2021-08-27
  • 2021-10-23
相关资源
最近更新 更多