C# TCP Client使用BeginRead首次响应后返回全0字节问题排查
C# TCP Client BeginRead读取异常问题修复
问题现象
- 基于C# TCP Client的BeginRead异步方法实现设备持续监听逻辑,设备按键触发时会主动推送数据包
- 首次发送10字节请求可正常收到3字节响应,后续发送重复请求时只能拿到长度为1024的全0字节数组
- Wireshark抓包确认设备响应正常,问题出在客户端代码,第二次请求后设备按键推送也无法正常解析
初始代码
internal void StartRecieveContinuous() { Open(); int dataLenght = 1024; this.LastRecievedData = new byte[dataLenght]; NetworkStream stream = Client?.GetStream() ?? throw new Exception("The TCP Client wasn't initialized!"); stream.BeginRead(LastRecievedData, 0, dataLenght, CallbackDataRecieved, stream); ListenerRunning = true; } private void CallbackDataRecieved(IAsyncResult ar) { ListenerRunning = false; // Debug System.Diagnostics.Debug.WriteLine(string.Join(" ",this.LastRecievedData.Take(20).Select(x => x.ToString("X")))); // Debug if((this.LastRecievedData?.Count(x => x > 0) ?? 0) > 0) { EventDataRecieved?.Invoke(this, new DataRecievedArgs(this.LastRecievedData.Take(this.LastRecievedData?.Length ?? 0).ToArray())); } StartRecieveContinuous(); }
调试日志(十六进制)
5 1 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0(首次响应)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0(第二次响应)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0(按键按下事件数据)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0(按键松开事件数据)
补充尝试
添加EndRead调用后问题依旧,修改后的回调代码:
private void CallbackDataRecieved(IAsyncResult ar) { NetworkStream myNetworkStream = (NetworkStream)ar.AsyncState; myNetworkStream.EndRead(ar); ListenerRunning = false; System.Diagnostics.Debug.WriteLine(">>> " + string.Join(" ", this.LastRecievedData.Take(20).Select(x => x.ToString("X")))); if((this.LastRecievedData?.Count(x => x > 0) ?? 0) > 0) { EventDataRecieved?.Invoke(this, new DataRecievedArgs(this.LastRecievedData.Take(this.LastRecievedData?.Length ?? 0).ToArray())); } StartRecieveContinuous(); }
问题根因
- 未处理
EndRead返回值:EndRead的返回值是本次异步读取实际收到的字节数,你当前直接使用整个1024字节的缓冲区,只要实际读取字节数小于3,你统计非0字节就会判定为无有效数据,甚至如果读取还没完成就访问缓冲区,拿到的就是初始的全0值 - 重复执行Open操作:每次调用
StartRecieveContinuous都会执行Open(),已连接状态下重复打开连接会导致连接重置,后续所有读取都会失败 - 缓冲区存在并发风险:用类字段
LastRecievedData存储接收缓冲区,多次异步读取操作会互相覆盖缓冲区内容,导致数据丢失 - 未处理0字节读取:TCP流中
EndRead返回0代表远端已经断开连接,此时继续循环调用BeginRead只会持续返回全0缓冲区
修复后代码
/// <summary> /// 启动持续监听,Open操作仅在初始化连接时调用一次即可,不要放到该方法内重复执行 /// </summary> internal void StartReceiveContinuous() { // 仅在连接断开时重新打开连接 if (Client == null || !Client.Connected) { Open(); } // 缓冲区改为局部变量,随异步状态传递,避免并发覆盖 byte[] receiveBuffer = new byte[1024]; NetworkStream stream = Client.GetStream() ?? throw new Exception("TCP客户端未初始化"); // 将流和缓冲区一起作为异步状态传递 stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, CallbackDataReceived, (stream, receiveBuffer)); ListenerRunning = true; } private void CallbackDataReceived(IAsyncResult ar) { ListenerRunning = false; var (stream, receiveBuffer) = ((NetworkStream, byte[]))ar.AsyncState; try { // 读取本次实际收到的字节数 int bytesRead = stream.EndRead(ar); // 返回0说明远端连接已断开,终止监听循环 if (bytesRead == 0) { // 可在此处添加连接断开的事件通知 return; } // 仅截取实际收到的字节部分,避免冗余的0值 byte[] actualData = receiveBuffer.Take(bytesRead).ToArray(); // 调试输出 System.Diagnostics.Debug.WriteLine(">>> " + string.Join(" ", actualData.Select(x => x.ToString("X2")))); if (actualData.Length > 0) { EventDataRecieved?.Invoke(this, new DataRecievedArgs(actualData)); } } catch (IOException ex) { // 捕获网络异常,避免崩溃 System.Diagnostics.Debug.WriteLine($"接收数据异常:{ex.Message}"); return; } // 正常接收完成,启动下一次监听 StartReceiveContinuous(); }
其他注意事项
发送请求的逻辑不要和接收回调耦合,避免阻塞接收流程导致数据丢失。如果需要按请求响应模式交互,建议维护独立的请求队列和超时机制。
内容的提问来源于stack exchange,提问作者Max N
相关产品推荐
相关产品推荐

