切换至TcpClient后第二个数据包触发System.IO.IOException异常排查
问题分析与解决方案
首先,触发这个异常的核心原因是你的GetByteArrayFromNetworkStream方法里存在一个致命的逻辑错误,直接导致了数据解析异常,最终被远程主机强制断开连接。
核心错误点
在GetByteArrayFromNetworkStream方法中,你读取到实际字节数bytesRead后,往MemoryStream写入时使用的是buffer.Length而非bytesRead:
memoryStream.Write(buffer, 0, buffer.Length); // 错误:写入了整个缓冲区,包括未被填充的无效字节
这会导致每次读取后,你都把2048字节全部写入内存流——哪怕实际只读取了比如100字节,剩下的1948字节都是默认的0值。当你解析数据包时,这些无效的0字节会打乱数据包的长度和ID解析逻辑,远程主机收到错误的响应或者发现你解析数据异常后,就会强制关闭连接,也就是你看到的10054错误。
修正后的GetByteArrayFromNetworkStream方法
把写入长度改成实际读取的bytesRead即可:
public byte[] GetByteArrayFromNetworkStream() { var buffer = new byte[2048]; var memoryStream = new MemoryStream(); int bytesRead; try { bytesRead = _networkStream.Read(buffer, 0, buffer.Length); while (bytesRead > 0) { memoryStream.Write(buffer, 0, bytesRead); // 这里改成bytesRead bytesRead = _networkStream.Read(buffer, 0, buffer.Length); } } catch (IOException ex) { // 处理连接断开的情况,比如记录日志 _logger.LogError(ex, "Network stream read failed"); return Array.Empty<byte>(); } return memoryStream.ToArray(); }
其他需要改进的地方
- 异常处理与循环终止:你的
ListenForData线程里是while(true)无限循环,没有处理任何异常,一旦网络流抛出异常,整个程序就会崩溃。建议在循环内添加try-catch块,并且在连接断开后终止循环:
var thread = new Thread(() => { try { while (_tcpClient.Connected) { // 用Connected状态判断循环条件 var data = GetByteArrayFromNetworkStream(); if (data.Length == 0) break; using var br = new BinaryReader(new MemoryStream(data)); if (br.BaseStream.Length < Constants.HabboPacketMinLength) { continue; } var messageLength = BinaryPrimitives.ReadInt32BigEndian(br.ReadBytes(4)); var packetData = br.ReadBytes(messageLength); using var br2 = new BinaryReader(new MemoryStream(packetData)); var packetId = BinaryPrimitives.ReadInt16BigEndian(br2.ReadBytes(2)); await _packetExecutor.TryExecutePacketAsync(this, packetId, packetData); } } catch (Exception ex) { _logger.LogError(ex, "Data listening thread failed"); } finally { Dispose(); // 断开后释放资源 } }); thread.Start();
- 异步替代线程:直接创建
Thread的方式比较过时,建议改用Task.Run结合异步读取,更符合.NET的异步编程模型:
public async void ListenForData(TcpClient tcpClient) { _tcpClient = tcpClient; _networkStream = tcpClient.GetStream(); try { while (_tcpClient.Connected) { var buffer = new byte[2048]; int bytesRead; using var memoryStream = new MemoryStream(); do { bytesRead = await _networkStream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead > 0) { await memoryStream.WriteAsync(buffer, 0, bytesRead); } } while (bytesRead > 0 && _networkStream.DataAvailable); // 结合DataAvailable判断是否还有数据 var data = memoryStream.ToArray(); if (data.Length == 0) break; // 后续的数据包解析逻辑... } } catch (Exception ex) { _logger.LogError(ex, "Data listening failed"); } finally { Dispose(); } }
- 资源释放:确保
NetworkStream和TcpClient在使用完毕后正确释放,当前的Dispose方法只释放了_tcpClient,其实_networkStream也应该在Dispose中释放:
public void Dispose() { _networkStream?.Dispose(); _tcpClient?.Dispose(); }
总结
最关键的修复是修正MemoryStream.Write的长度参数,这直接解决了数据包解析错误导致的连接被强制关闭问题。在此基础上补充异常处理和资源管理,可以让你的网络客户端更健壮。
内容的提问来源于stack exchange,提问作者user5405648
相关产品推荐
相关产品推荐

