System.Text.Json反序列化TCP Socket传输JSON时因末尾多余大括号引发异常的问题求助
解决TCP Socket发送JSON时的粘包导致的JSON反序列化异常
这个问题太常见了——你碰到的是TCP流式传输带来的粘包问题!TCP不会帮你划分消息的边界,连续发送的多个JSON很可能被合并成一段数据接收,或者单个JSON被拆成多段,这就导致你收到的字符串里出现多余的语法符号(比如你例子里多出来的那个},其实是两个JSON粘在一起了:{"Value":3,"Name":"Blood Sugar"}{"Value":4,"Name":"XXX"}),反序列化自然就炸了。
先帮你梳理下当前代码里的几个坑:
- 你完全忽略了
Receive方法的返回值,直接把整个byteMessage数组转成字符串,这会把之前接收的残留数据也包含进去,很容易导致脏数据。 - 接收端用
stringMessage.IndexOf('\0')来截取消息,但发送端根本没发送\0,这个逻辑完全不成立,一旦IndexOf返回-1,Substring直接报错。 - 你默认每次
Receive就能拿到完整的一个JSON,但TCP根本不保证这点,它只按顺序传字节,不管你的消息边界。
下面给你两种靠谱的修复方案,先从最简单的开始:
方案1:用换行符作为消息边界
这个方法简单直观,给每个JSON后面加个换行符\n,接收端累计收到的数据,直到碰到换行符,再处理完整的JSON。
修改后的发送端代码
while (Form.isGenerate) { Data newData = dataType.generate(person.generatingParameters); Thread.Sleep(500); // 序列化后拼接换行符,作为消息结束的标记 string jsonContent = JsonSerializer.Serialize<Data>(newData) + "\n"; byte[] sendBytes = Encoding.UTF8.GetBytes(jsonContent); clientSocket.Send(sendBytes); // 接收响应时也要处理实际收到的字节数,别直接用整个数组 byte[] responseBuffer = new byte[1024]; int receivedBytes = clientSocket.Receive(responseBuffer); string response = Encoding.UTF8.GetString(responseBuffer, 0, receivedBytes); }
修改后的接收端代码
// 用StringBuilder缓存未处理的残留数据,应对拆包情况 StringBuilder messageBuffer = new StringBuilder(); byte[] receiveBuffer = new byte[1024]; while(true) { int actualReceivedBytes = seperateSocketForEachRequest.Receive(receiveBuffer); if (actualReceivedBytes == 0) { // 客户端断开连接,退出循环 break; } // 把收到的有效字节转成字符串,加入缓存 string receivedPart = Encoding.UTF8.GetString(receiveBuffer, 0, actualReceivedBytes); messageBuffer.Append(receivedPart); // 循环处理缓存里所有完整的消息(直到没有换行符为止) int newlinePos; while ((newlinePos = messageBuffer.ToString().IndexOf('\n')) != -1) { // 提取完整的JSON内容(去掉末尾的换行符) string completeJson = messageBuffer.ToString().Substring(0, newlinePos); // 从缓存中移除已经处理的部分 messageBuffer.Remove(0, newlinePos + 1); try { // 反序列化,这里建议用具体的Data类型而不是Object,避免后续类型转换麻烦 Data receivedMessage = JsonSerializer.Deserialize<Data>(completeJson); // 在这里处理你的消息... } catch (JsonException ex) { // 捕获异常,避免程序崩溃,同时可以记录错误日志 Console.WriteLine($"反序列化失败:{ex.Message} | 错误内容:{completeJson}"); } } // 发送响应 seperateSocketForEachRequest.Send(Encoding.UTF8.GetBytes("FF")); }
方案2:用长度前缀标记消息(更严谨)
如果你的场景对消息可靠性要求更高,比如JSON里可能包含换行符,那可以用「先发送消息长度,再发送消息内容」的方式:
- 发送端先把JSON的字节长度转成固定长度的字节(比如4字节的int)发送。
- 接收端先接收4字节,解析出长度,再接收对应长度的字节,这样就能准确拿到完整的JSON。
举个简单的发送端示例:
string json = JsonSerializer.Serialize<Data>(newData); byte[] jsonBytes = Encoding.UTF8.GetBytes(json); // 把字节长度转成4字节的大端字节数组 byte[] lengthBytes = BitConverter.GetBytes(jsonBytes.Length); if (BitConverter.IsLittleEndian) { Array.Reverse(lengthBytes); } // 先发送长度,再发送JSON内容 clientSocket.Send(lengthBytes); clientSocket.Send(jsonBytes);
接收端对应处理:
byte[] lengthBuffer = new byte[4]; // 先接收长度字节 int lengthReceived = seperateSocketForEachRequest.Receive(lengthBuffer); if (lengthReceived != 4) { // 处理异常,比如客户端断开 break; } if (BitConverter.IsLittleEndian) { Array.Reverse(lengthBuffer); } int jsonLength = BitConverter.ToInt32(lengthBuffer, 0); // 接收对应长度的JSON内容 byte[] jsonBuffer = new byte[jsonLength]; int totalReceived = 0; while (totalReceived < jsonLength) { int received = seperateSocketForEachRequest.Receive(jsonBuffer, totalReceived, jsonLength - totalReceived, SocketFlags.None); if (received == 0) { break; } totalReceived += received; } string completeJson = Encoding.UTF8.GetString(jsonBuffer); // 反序列化...
最后几个关键提醒
- 永远不要忽略
Receive的返回值:它告诉你实际收到了多少字节,必须用这个值来截取数组,否则残留的旧数据会搞脏你的消息。 - 别用
\0当边界:你发送端没发这个字符,接收端这么做完全是碰运气,而且JSON规范里也不允许字符串包含\0,这个逻辑彻底废掉。 - 用具体类型反序列化:别用
Object,直接反序列化成Data类型,既高效又避免后续类型转换的麻烦。
内容的提问来源于stack exchange,提问作者storm_167
相关产品推荐
相关产品推荐

