【问题标题】:How to solve this Serialization exception?如何解决这个序列化异常?
【发布时间】:2017-04-04 23:33:21
【问题描述】:

我的代码在这里,我不明白这是什么错误。 有人告诉我问题是在“处理客户端”方法中反序列化“网络”对象时。因为每次有新客户进来时我都会覆盖它。你能帮我解决这个问题吗?我还是不知道该怎么办。

起初它适用于 2-3 条消息,然后崩溃。

我得到的例外是:

序列化异常 - 输入流不是有效的二进制格式。起始内容(以字节为单位)为:FF-FF-FF-FF-06-44-61-76-69-64-3A-20-66-75-63-6B-20 ... .

还有一次我得到了相同的序列化异常,只是它说 - 在顶部对象上。

代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net.Sockets;
using System.Net;
using Message;

namespace Chat
{
    public partial class ChatWindow : Form
    {
    uMessage umsg = new uMessage();
    BinaryFormatter bf = new BinaryFormatter();
    NetworkStream Net;
    TcpListener listener;
    TcpClient client;
    List<NetworkStream> Clients = new List<NetworkStream>();

    public ChatWindow(string ip, int port)
    {
        InitializeComponent();
        umsg.IP = ip;
        umsg.Port = port;
    }

    public void OpenNewThread()
    {
        listener = new TcpListener(IPAddress.Parse(umsg.IP), umsg.Port); 
        listener.Start();
        Thread a = new Thread(Listen);
        a.Start();
    }

    public void Listen()
    {
        do
        {
            client = listener.AcceptTcpClient();
            Net = client.GetStream();
            Clients.Add(Net);
            umsg.Name = bf.Deserialize(Net).ToString();
            lstboxCurrentUsers.Invoke(new Action(() =>
            {
                lstboxCurrentUsers.Items.Add($"{umsg.Name} connected at " + DateTime.Now);
                listboxHistory.Items.Add($"{umsg.Name} connected at " + DateTime.Now);
            }));
            LetSubsKnow(Clients);


                Thread b = new Thread(() => HandleClient(Clients));
                b.Start();


        } while (true);
    }

    public void HandleClient(List<NetworkStream> ClientsStream)
    {
        while (true)
        {
            umsg.Message = bf.Deserialize(Net).ToString();

            foreach (var client in ClientsStream)
                {
                   bf.Serialize(client, umsg.Message);
                }
        }
    }

    public void LetSubsKnow(List<NetworkStream> clientsStream)
    {           
        foreach (var client in clientsStream)
        {
            bf.Serialize(client, $"{umsg.Name} Has Connected.");
        }
    }

【问题讨论】:

  • 确保你的代码是线程安全的。
  • 我不会使用BinaryFormatter 序列化strings,它增加了额外的有效负载,更难调试并且与非.NET 客户端不兼容。您可能想改用Encoding.UTF8

标签: c# .net serialization deserialization


【解决方案1】:

Net 字段不断被最近连接的客户端替换,因此即使每个客户端有一个HandleClient 线程,所有这些线程都从最近获得的NetworkStream 中读取。

同样,每当客户端连接时,umsg.Name 字段就会被覆盖,而每当消息到达时,umsg.Message 字段就会被覆盖。

您可以通过将单个连接的NetworkStream 提供给HandleClient 并为接收到的消息创建一个局部变量来解决这些问题:

public void HandleClient(NetworkStream client)
{
    ...
    string message = bf.Deserialize(client).ToString();
    ...
}

同样,您需要将客户端名称传递给LetSubsKnow 方法,而不是依赖不断更新的umsg 字段。

public void LetSubsKnow(string clientName)
{
    ....
}

此外,即使您将clientsStream 作为参数传递,它们都是对Clients 字段的相同引用,并且如果客户端连接您正在发送数据,foreach 将抛出“集合已被修改”异常。

这可以通过使用锁访问Clients 字段来解决。我不会将整个 foreach 块放入锁定部分,而是拍摄当前连接的客户端的快照:

private readonly object clientsLock = new object();
List<NetworkStream> Clients = new List<NetworkStream>();
...
NetworkStream[] currentClients;
lock(clientsLock)
{
    currentClients = Clients.ToArray();
}

foreach (NetworkStream client in currentClients)
{
    // send stuff
}

请注意,您将需要围绕NetworkStream 访问代码进行一些异常处理。

最后但同样重要的是,我不认为BinaryFormatter 是线程安全的(请参阅this answer)。与其锁定,不如在 HandleClientLetSubsKnow 方法中创建新的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-04
    • 2012-01-08
    相关资源
    最近更新 更多