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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 22:54:04