You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用MessagePack与TCP Socket反序列化自定义类型?

TCP Socket传输MessagePack序列化对象反序列化失败解决方案

问题背景

通过TCP Socket传输用MessagePack序列化的自定义Person类型对象(含Name、Age字段),客户端反序列化时提示“Failed to deserialize”错误。

服务器端原代码

var tcpListener = new TcpListener(IPAddress.Any, 8888);
try
{
    tcpListener.Start();
    Console.WriteLine("Server is currently run, waiting for incoming connections");
    Person Tom = new Person("Tom",40);
    while (true)
    {
        using var tcpClient = await tcpListener.AcceptTcpClientAsync();
        var stream = tcpClient.GetStream();
        var data = MessagePackSerializer.Serialize(Tom);
        await stream.WriteAsync(data);
        Console.WriteLine($"Data was sent to {tcpClient.Client.RemoteEndPoint} client");
    }
}
finally
{
    tcpListener.Stop();
}

客户端原代码

using TcpClient tcpClient= new TcpClient();
await tcpClient.ConnectAsync("127.0.0.1",8888);
Console.WriteLine("Client is run");
try
{
    var stream = tcpClient.GetStream();
    var resData = MessagePackSerializer.Deserialize<Person>(stream);
    Console.WriteLine($"Person's name: {resData.Name} /n Person's age: {resData.Age}");
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

问题根源

  1. TCP流无消息边界:TCP是字节流协议,客户端无法直接判断何时接收完完整的序列化数据,直接调用流反序列化会因数据不完整或读取超出范围失败。
  2. Socket流不支持Seek:MessagePack默认的流反序列化方法可能需要可搜索的流,而Socket网络流不支持Seek操作,导致反序列化异常。
  3. 自定义类型配置缺失:若Person类未添加MessagePack的序列化标记,会导致序列化/反序列化规则不匹配。

修复方案

1. 服务器端:先发送数据长度,再发送序列化内容

通过先传递数据长度,让客户端明确知道需要读取的字节数,解决流边界问题:

var tcpListener = new TcpListener(IPAddress.Any, 8888);
try
{
    tcpListener.Start();
    Console.WriteLine("Server is running, waiting for incoming connections");
    Person Tom = new Person("Tom", 40);
    while (true)
    {
        using var tcpClient = await tcpListener.AcceptTcpClientAsync();
        var stream = tcpClient.GetStream();
        var data = MessagePackSerializer.Serialize(Tom);
        
        // 先发送4字节的int表示数据长度
        var lengthBytes = BitConverter.GetBytes(data.Length);
        await stream.WriteAsync(lengthBytes);
        // 再发送序列化后的对象数据
        await stream.WriteAsync(data);
        
        Console.WriteLine($"Data sent to {tcpClient.Client.RemoteEndPoint}");
    }
}
finally
{
    tcpListener.Stop();
}

2. 客户端:先读取长度,再读取完整数据后反序列化

先读取长度,再循环读取到完整的字节数组,最后用字节数组反序列化(避免流Seek问题):

using TcpClient tcpClient = new TcpClient();
await tcpClient.ConnectAsync("127.0.0.1", 8888);
Console.WriteLine("Client running");
try
{
    var stream = tcpClient.GetStream();
    // 读取数据长度(4字节)
    var lengthBuffer = new byte[4];
    int bytesRead = await stream.ReadAsync(lengthBuffer, 0, 4);
    if (bytesRead != 4)
    {
        Console.WriteLine("Failed to read data length");
        return;
    }
    int dataLength = BitConverter.ToInt32(lengthBuffer, 0);
    
    // 读取完整的序列化数据
    var dataBuffer = new byte[dataLength];
    bytesRead = 0;
    while (bytesRead < dataLength)
    {
        int read = await stream.ReadAsync(dataBuffer, bytesRead, dataLength - bytesRead);
        if (read == 0)
        {
            Console.WriteLine("Stream closed before receiving all data");
            return;
        }
        bytesRead += read;
    }
    
    // 用字节数组反序列化,规避流Seek问题
    var resData = MessagePackSerializer.Deserialize<Person>(dataBuffer);
    Console.WriteLine($"Person's name: {resData.Name}\nPerson's age: {resData.Age}");
}
catch (Exception e)
{
    Console.WriteLine($"Error: {e.Message}\n{e.StackTrace}");
}

3. 配置自定义类型的MessagePack序列化规则

给Person类添加必要的标记,确保序列化和反序列化的兼容性(需引用MessagePack包):

using MessagePack;

[MessagePackObject]
public class Person
{
    [Key(0)]
    public string Name { get; set; }
    
    [Key(1)]
    public int Age { get; set; }
    
    // 显式构造函数
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    
    // 无参构造函数(MessagePack默认需要,或通过配置启用非公共构造函数)
    public Person() { }
}

内容的提问来源于stack exchange,提问作者Hedimin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 10:20:31