如何使用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); }
问题根源
- TCP流无消息边界:TCP是字节流协议,客户端无法直接判断何时接收完完整的序列化数据,直接调用流反序列化会因数据不完整或读取超出范围失败。
- Socket流不支持Seek:MessagePack默认的流反序列化方法可能需要可搜索的流,而Socket网络流不支持Seek操作,导致反序列化异常。
- 自定义类型配置缺失:若
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
相关产品推荐
相关产品推荐

