You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

切换至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();
}

其他需要改进的地方

  1. 异常处理与循环终止:你的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();
  1. 异步替代线程:直接创建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();
    }
}
  1. 资源释放:确保NetworkStream和TcpClient在使用完毕后正确释放,当前的Dispose方法只释放了_tcpClient,其实_networkStream也应该在Dispose中释放:
public void Dispose() {
    _networkStream?.Dispose();
    _tcpClient?.Dispose();
}

总结

最关键的修复是修正MemoryStream.Write的长度参数,这直接解决了数据包解析错误导致的连接被强制关闭问题。在此基础上补充异常处理和资源管理,可以让你的网络客户端更健壮。

内容的提问来源于stack exchange,提问作者user5405648

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 16:58:00