TCP客户端无法完整读取服务器消息问题求助
服务器TCP数据发送格式
服务器的数据发送结构分为Header和Data两部分:
------------|-------------------------------- ---Header---| ----------- Data ---------- ------------|--------------------------------
- Header固定40字节,前2字节表示Data的总长度,剩余38字节均为0。
- 当Data总长度超过1500字节时,会分两次发送:
- 第一次:发送完整Header + 1460字节的Data
- 第二次:仅发送剩余的Data(无Header)
示例:若Data总长度为1597字节
first transaction -> -------|-------------------- |Header| data of length 1460 -------|-------------------- second transaction -> |-------------------- data of length 147 (Note this transaction doesn't have header) |--------------------
客户端读取流程
- 先读取40字节的Header,解析出Data的总长度。
- 根据解析出的长度读取对应字节数的Data。
遇到的问题
- 本地运行完全正常,但在其他电脑上,仅**调试模式(逐步执行)**下能正常读取,非调试模式下只能读取消息前半部分,丢弃后续内容,抛出如下错误:
System.Exception
HResult=0x80131500
Message=Exception occurred {0}
Source=SockTest
StackTrace:
at socketTest.sockTest.readData() in C:\usr\testing\SocketTransREcive\SockTest\sockTest.cs:line 125
at socketTest.main.testTrasRecv(sockTest sock, String command) in C:\usr\testing\SocketTransREcive\SockTest\Program.cs:line 13
at socketTest.main.testSend() in C:\usr\testing\SocketTransREcive\SockTest\Program.cs:line 51
at socketTest.main.Main(String[] args) in C:\usr\testing\SocketTransREcive\SockTest\Program.cs:line 63This exception was originally thrown at this call stack:
[External Code]
socketTest.sockTest.readData() in sockTest.csInner Exception 1:
ArgumentOutOfRangeException: Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection. (Parameter 'count')
- 已用Wireshark确认服务器发送的数据完全正常,求解决思路。
客户端代码
namespace socketTest { using System; using System.Net; using System.Net.Sockets; public class sockTest : IDisposable { // defaults private string ip = "192.168.1.100"; private int port = 1234; //private int port = 16666; public int error = -1; public int noError = 0; bool disposed = false; private byte[] buffer; private byte[] header; // updated once instanciated private IPAddress Ip; public TcpClient socketHandle; public NetworkStream tcpStream; // instance check private static readonly Lazy<sockTest> lazy = new Lazy<sockTest>(() => new sockTest()); private sockTest() { try { Console.WriteLine("Program started !"); this.Ip = IPAddress.Parse(ip); } catch (Exception exception) { //throw new Exception("Could not initialize socket erro : {0}", exception); Console.WriteLine("Could not initialize socket error: {0}", exception); } } public static sockTest Instance { get { return lazy.Value; } } public int connect() { try { this.socketHandle = new TcpClient(); this.buffer = new byte[4000]; this.header = new byte[10]; this.socketHandle.Connect(this.Ip, this.port); this.tcpStream = socketHandle.GetStream(); return noError; } catch (Exception exception) { Console.WriteLine(exception.ToString()); return error; } } public int prcessHeader(byte[] header) { int ind = 0; int flag = 0; for (int i = 0; i < 10; i = i + 2) { int value = (header[i + 0]) | (header[i + 1] << 8); if(flag == 0) { ind = value; flag = 1; } } return ind; } public string readData() { try { tcpStream.Read(this.header, 0, 10); var inputString = System.Text.Encoding.ASCII.GetString(this.header); //int msg_len = Int32.Parse(inputString); int msg_len = prcessHeader(this.header); tcpStream.Read(this.buffer, 0, msg_len); return System.Text.Encoding.ASCII.GetString(this.buffer); } catch (Exception exception) { throw new Exception("Exception occurred {0}", exception); //return null; } } public int sendData(string data) { try { byte[] bData = System.Text.Encoding.ASCII.GetBytes(data); tcpStream.Write(bData, 0, bData.Length * sizeof(byte)); return noError; } catch (Exception exception) { return error; } } public void closeConnection() { tcpStream.Close(); this.socketHandle.Close(); } public void Dispose() { // Dispose of unmanaged resources. this.CheckDispose(true); // Suppress finalization. GC.SuppressFinalize(this); } protected virtual void CheckDispose(bool disposing) { if (disposed) return; if (disposing) { this.closeConnection(); } disposed = true; } } }
核心问题分析
- Header读取不完整:服务器Header是40字节,但代码中仅读取10字节,导致解析出的Data长度错误,后续读取时易触发越界异常。
- TCP流读取逻辑错误:
NetworkStream.Read()不保证一次性读取指定长度的字节,调试模式下因执行慢数据已全部到达,能读到完整内容;非调试模式下数据分批到达,直接按指定长度读取会导致内容缺失或越界。 - 分块发送处理缺失:代码未适配服务器分两次发送数据的逻辑,无Header的后续数据会被误当作下一条消息的Header,引发混乱。
修复步骤
1. 修正Header读取逻辑
- 将
header数组大小改为40字节,确保能容纳完整Header:// 在connect方法中修改 this.header = new byte[40]; - 循环读取直到获取完整的40字节Header:
// 替换readData中的Header读取代码 int totalHeaderRead = 0; while (totalHeaderRead < 40) { int read = tcpStream.Read(this.header, totalHeaderRead, 40 - totalHeaderRead); if (read == 0) { throw new IOException("Connection closed while reading header"); } totalHeaderRead += read; }
2. 简化Header解析逻辑
直接解析Header前2字节即可得到Data总长度:
public int prcessHeader(byte[] header) { // 按小端序解析前2字节的Data长度 return BitConverter.ToUInt16(header, 0); }
3. 实现完整的Data读取逻辑
循环读取直到获取全部指定长度的Data,适配分块发送场景:
// 替换readData中的Data读取代码 int totalDataRead = 0; // 确保buffer足够容纳数据 if (msg_len > this.buffer.Length) { Array.Resize(ref this.buffer, msg_len); } while (totalDataRead < msg_len) { int read = tcpStream.Read(this.buffer, totalDataRead, msg_len - totalDataRead); if (read == 0) { throw new IOException("Connection closed while reading data"); } totalDataRead += read; }
4. 修复异常抛出格式
原代码中异常消息格式化错误,修正为:
throw new Exception($"Exception occurred {exception.Message}", exception);
修复后的readData方法示例
public string readData() { try { // 读取完整的40字节Header int totalHeaderRead = 0; while (totalHeaderRead < 40) { int read = tcpStream.Read(this.header, totalHeaderRead, 40 - totalHeaderRead); if (read == 0) { throw new IOException("Connection closed while reading header"); } totalHeaderRead += read; } // 解析Data总长度 int msg_len = prcessHeader(this.header); // 确保buffer足够容纳数据 if (msg_len > this.buffer.Length) { Array.Resize(ref this.buffer, msg_len); } // 读取完整的Data int totalDataRead = 0; while (totalDataRead < msg_len) { int read = tcpStream.Read(this.buffer, totalDataRead, msg_len - totalDataRead); if (read == 0) { throw new IOException("Connection closed while reading data"); } totalDataRead += read; } return System.Text.Encoding.ASCII.GetString(this.buffer, 0, msg_len); } catch (Exception exception) { throw new Exception($"Exception occurred {exception.Message}", exception); } }
额外注意事项
- 编码适配:若服务器使用非ASCII编码,需对应调整解码方式(如UTF-8)。
- Buffer优化:可初始设置更大的buffer,减少动态Resize的开销。
- 异常处理:可根据业务需求添加重试、重连等逻辑。
内容的提问来源于stack exchange,提问作者DeLorean

