C# TCP游戏服务端如何丢弃无合法OpCode的无效网络数据包
非法数据包处理方案
你定义的数据包前6字节是固定结构(2字节OpCode + 4字节Payload长度),不管OpCode是否合法,都可以先完整读取6字节包头,再根据包头中的长度字段读取对应长度的Payload,即可完成整包丢弃,不会出现流错位的问题。
1. 调整数据包处理流程
修改ProcessPackets方法,统一先读完整包头和Payload,再判断OpCode合法性:
public void ProcessPackets() { Task.Run(() => { // 可根据业务调整最大Payload长度,防止恶意包占满内存 const int MaxPayloadLength = 1024 * 10; while (true) { try { // 第一步:读取2字节OpCode short opCodeVal = Reader.ReadInt16(); // 第二步:无论OpCode是否合法,都读取4字节Payload长度 int payloadLength = Reader.ReadInt32(); // 长度校验不通过直接断开连接,避免攻击 if (payloadLength < 0 || payloadLength > MaxPayloadLength) { Console.WriteLine($"非法包长度 {payloadLength},断开连接"); break; } // 第三步:读取对应长度的Payload,把流指针移动到下一个包的起始位置 byte[] payload = Reader.ReadBytes(payloadLength); // 第四步:OpCode合法则处理,否则直接忽略已读取的数据包即可 if (_packets.TryGetValue((OpCode)opCodeVal, out var packet)) { packet.Process(this, payload); } else { Console.WriteLine($"未知OpCode {opCodeVal},丢弃对应数据包"); } } catch (IOException) { // 连接断开或读取出错,直接退出循环释放资源 Console.WriteLine($"客户端 {_socket.Client.RemoteEndPoint} 连接断开"); break; } catch (Exception ex) { Console.WriteLine($"处理包出错: {ex.Message},断开连接"); break; } } _socket.Close(); }); }
2. 调整IPacket的Process方法签名
不需要在Process里重复读取长度和Payload,直接使用上层已经读取好的Payload数据即可:
public void Process(Connection connection, byte[] payload) { // 直接处理payload即可 Console.WriteLine($"Received and processed Connection Packet,长度 {payload.Length}"); }
3. 修复NetReader的读取逻辑
当前的NetReader没有处理半包读取场景,当网络栈中没有足够字节时,Read方法会返回不足长度的数据,导致解析出错,需要补全读取逻辑确保每次都拿到足额数据:
class NetReader : BinaryReader { private NetworkStream _ns; private byte[] _tempBuffer; public NetReader(NetworkStream ns) : base(ns) { _ns = ns; _tempBuffer = new byte[8]; } public override byte ReadByte() { int val = _ns.ReadByte(); if (val == -1) throw new IOException("连接已关闭"); return (byte)val; } public override short ReadInt16() { int readBytes = 0; while (readBytes < 2) { int n = _ns.Read(_tempBuffer, readBytes, 2 - readBytes); if (n == 0) throw new IOException("连接已关闭"); readBytes += n; } return BitConverter.ToInt16(_tempBuffer, 0); } public override int ReadInt32() { int readBytes = 0; while (readBytes < 4) { int n = _ns.Read(_tempBuffer, readBytes, 4 - readBytes); if (n == 0) throw new IOException("连接已关闭"); readBytes += n; } return BitConverter.ToInt32(_tempBuffer, 0); } public override byte[] ReadBytes(int count) { byte[] bytes = new byte[count]; int readBytes = 0; while (readBytes < count) { int n = _ns.Read(bytes, readBytes, count - readBytes); if (n == 0) throw new IOException("连接已关闭"); readBytes += n; } return bytes; } }
额外注意事项
- 必须设置最大Payload长度限制,防止恶意用户构造超大长度的数据包,导致服务端申请过量内存引发OOM崩溃。
- TCP是流式协议,无数据边界,如果出现非法数据包,说明对端发送的数据不符合约定格式,后续流大概率已经错位,无法继续正确解析,这种场景直接断开连接是性价比最高的处理方式,无需强行尝试恢复解析。
- 所有网络IO操作都要增加异常捕获,避免未处理异常导致服务端进程退出。
内容的提问来源于stack exchange,提问作者Riley Varga
相关产品推荐
相关产品推荐

