从NetworkStream读取数据出现损坏,多消息场景下问题复现
C++ TCP服务器消息读取损坏问题排查与修复
我看了你这段C#读取TCP消息的代码,遇到高并发消息(超过10条)时出现损坏,核心问题出在消息头解析错误和读取逻辑的几个漏洞上,咱们一步步拆解:
首先说最致命的问题:消息长度解析完全错了
你的C++服务器是用前3字节二进制表示消息长度,但你现在把这3个字节直接转成ASCII字符串再Int32.TryParse,这完全不对。举个例子:如果实际消息长度是120,二进制是0x00 0x00 0x78,转成ASCII后是两个空字符加一个'x',TryParse根本得不到正确的120,最后number会是0,后续读取逻辑直接乱套,这就是大量消息过来时数据损坏的核心原因。
其他几个坑点
Thread.Sleep(10)是非常不靠谱的“凑数”逻辑,网络延迟是不确定的,要么等数据可用,要么用异步读取,靠sleep很容易导致读少或者读多。- 读取完消息体后,你直接把
_temp数组整个转成字符串赋值给message,但_temp的长度是number+1,如果实际没读满,后面的字节都是默认的0,转成ASCII就是乱码或者问号。 - 剩余数据读取的长度计算错误,循环里应该用
number - totalByetsRead,而不是number+1 - numBytesRead,而且没有处理Read返回0的情况(比如连接断开)。
修复后的代码
我重新写了读取逻辑,解决了上面所有问题:
private void RecieveMsg(out string message) { message = string.Empty; byte[] lengthBytes = new byte[3]; int bytesRead = 0; // 先确保读满3字节的长度头 while (bytesRead < 3) { int read = _netStream.Read(lengthBytes, bytesRead, 3 - bytesRead); if (read == 0) { // 连接已断开,直接返回空 return; } bytesRead += read; } // 解析3字节的长度(注意字节序,假设C++服务器是大端序,根据实际情况调整) int messageLength = (lengthBytes[0] << 16) | (lengthBytes[1] << 8) | lengthBytes[2]; if (messageLength <= 0) { // 无效长度,直接返回 return; } byte[] messageBytes = new byte[messageLength]; bytesRead = 0; // 读取完整的消息体 while (bytesRead < messageLength) { int read = _netStream.Read(messageBytes, bytesRead, messageLength - bytesRead); if (read == 0) { // 连接断开,读取不完整,返回已读部分或者空 message = Encoding.ASCII.GetString(messageBytes, 0, bytesRead); return; } bytesRead += read; } // 转成最终的字符串 message = Encoding.ASCII.GetString(messageBytes); }
关键修复点说明
- 正确解析二进制长度头:把3个字节按二进制位拼接成整数,这里假设C服务器用的是大端序(网络字节序),如果你的C代码是小端序,要改成
(lengthBytes[2] << 16) | (lengthBytes[1] << 8) | lengthBytes[0],这个一定要对应上。 - 循环读取确保读满:不管是长度头还是消息体,都用循环读取直到读够指定字节数,避免因为TCP粘包/拆包导致的读取不完整。
- 处理连接断开情况:当
Read返回0时,说明对方已经断开连接,直接终止读取,避免死循环。 - 去掉不靠谱的Sleep:完全依赖
Read的返回值和循环来控制读取,不需要靠sleep等待数据。
这样修改后,不管多少条消息过来,都能正确解析每个消息的长度,然后完整读取消息体,不会再出现乱码或者损坏的情况。
内容的提问来源于stack exchange,提问作者yaodav




