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

Unity多人游戏自定义服务器的数据包管理优化方案咨询

问题背景与需求

我想用Unity制作一款支持玩家自建专用服务器的多人游戏,目前仅完成了基于C#控制台(使用System.Net)的测试服务器和客户端,希望提前搭建好可扩展的正式服务器架构。当前遇到的核心问题是:客户端与服务器间需要传输大量不同类型的数据包,现有的switch分支管理方式在数据包类型增多后会变得难以维护(预计后续会有500+种数据包类型)。

当前实现方式

客户端发送的数据包格式为[PacketCode]Message,其中:

  • PacketCode:每种数据包的唯一编码
  • Message:当前仅为字符串

服务器和客户端分别通过HandleReceivedPacket.Filter方法处理数据包,核心逻辑依赖switch分支匹配PacketCode。

服务器端处理代码

using Server.Utilities;
using System.Net.Sockets;

namespace Server
{
    public static class HandleReceivedPacket
    {
        public static void Filter(TcpClient client, Stream stream, string data)
        {
            int code = int.Parse(data[1].ToString());
            string message = data.Substring(3);

            ServerLogger.Log($"Received data from client: [{code}]: {message}", LogLevel.Info);

            switch (code)
            {
                default:
                    Server.SendToClient(stream, PacketType.MESSAGE, "Message sent, but it's not a command... Nothing happened.");
                    break;
                case 0:
                    Server.SendToClient(stream, PacketType.TEST, "");
                    break;
                case 1:
                    Server.SendToClient(stream, PacketType.MESSAGE, "");
                    break;
                case 2:
                    Server.DisconnectClient(client);
                    break;
            }
        }
    }
}

客户端处理代码

namespace ClientTest
{
    public static class HandleReceivedPacket
    {
        public enum PacketType
        {
            TEST = 0,
            MESSAGE = 1,
            DISCONNECT = 2
        }

        public static void Filter(string data)
        {
            int code = int.Parse(data[1].ToString());
            string message = data.Substring(3);

            switch (code)
            {
                default:
                    Console.WriteLine("Message sent, but it's not a command... Nothing happened.");
                    break;
                case 0:
                    Console.WriteLine("Connection: OK");
                    break;
                case 1:
                    Console.WriteLine("Your message has been received");
                    break;
                case 2:
                    Console.WriteLine("Disconnected!");
                    break;
            }
        }
    }
}

现有方案的问题

当数据包类型达到数百种时,服务器和客户端的switch分支会极度臃肿,枚举也会变得庞大,后续维护、新增或修改数据包逻辑会非常繁琐,还容易出现遗漏或错误。


优化方案:基于策略模式的数据包管理

针对大量数据包类型的场景,推荐使用策略模式+预注册/反射的方式替代switch分支,实现可扩展、低耦合的数据包处理架构。

核心思路

  1. 为每种数据包定义独立的处理类,实现统一的处理接口
  2. 建立PacketCode与处理类的映射关系(可通过预注册或反射自动扫描)
  3. 收到数据包时,根据PacketCode直接调用对应的处理类逻辑

具体实现步骤

1. 定义基础接口与数据包基类

将通用逻辑抽离,确保客户端和服务器的数据包结构一致:

数据包基类(客户端/服务器共用)

// 可放在共享类库中,避免两端PacketCode不一致
public abstract class BasePacket
{
    public abstract int PacketCode { get; }
    // 序列化:将数据包转为传输用的字节数组
    public abstract byte[] Serialize();
    // 反序列化:从传输数据还原为数据包对象
    public abstract void Deserialize(byte[] data);
}

数据包处理接口

服务器端处理接口(带客户端上下文):

public interface IServerPacketHandler
{
    void Handle(TcpClient client, Stream stream, BasePacket packet);
}

客户端处理接口:

public interface IClientPacketHandler
{
    void Handle(BasePacket packet);
}

2. 实现具体数据包与处理类

以TEST数据包为例,其他类型数据包可直接复用此结构:

TEST数据包类

public class TestPacket : BasePacket
{
    public override int PacketCode => 0;
    public string ResponseMessage { get; set; } = "";

    public override byte[] Serialize()
    {
        // 后续可替换为Protobuf/MessagePack等高效序列化方式
        return Encoding.UTF8.GetBytes($"[{PacketCode}]{ResponseMessage}");
    }

    public override void Deserialize(byte[] data)
    {
        string rawData = Encoding.UTF8.GetString(data);
        ResponseMessage = rawData.Substring(3);
    }
}

服务器端TEST数据包处理器

public class TestPacketServerHandler : IServerPacketHandler
{
    public void Handle(TcpClient client, Stream stream, BasePacket packet)
    {
        var testPacket = (TestPacket)packet;
        ServerLogger.Log($"Received Test packet from client", LogLevel.Info);
        // 发送响应数据包
        Server.SendToClient(stream, new TestPacket { ResponseMessage = "Connection: OK" });
    }
}

客户端TEST数据包处理器

public class TestPacketClientHandler : IClientPacketHandler
{
    public void Handle(BasePacket packet)
    {
        var testPacket = (TestPacket)packet;
        Console.WriteLine(testPacket.ResponseMessage);
    }
}

3. 建立处理器映射容器

创建全局管理器,负责注册和查找对应PacketCode的处理器:

服务器端处理器管理器

public static class ServerPacketHandlerManager
{
    private static readonly Dictionary<int, IServerPacketHandler> _handlers = new();

    // 程序启动时调用,完成所有处理器注册
    public static void RegisterHandlers()
    {
        _handlers.Add(0, new TestPacketServerHandler());
        _handlers.Add(1, new MessagePacketServerHandler());
        _handlers.Add(2, new DisconnectPacketServerHandler());
        // 新增数据包只需添加对应注册项
    }

    // 根据PacketCode获取对应的处理器
    public static IServerPacketHandler GetHandler(int packetCode)
    {
        return _handlers.TryGetValue(packetCode, out var handler) ? handler : null;
    }
}

客户端处理器管理器同理

public static class ClientPacketHandlerManager
{
    private static readonly Dictionary<int, IClientPacketHandler> _handlers = new();

    public static void RegisterHandlers()
    {
        _handlers.Add(0, new TestPacketClientHandler());
        _handlers.Add(1, new MessagePacketClientHandler());
        _handlers.Add(2, new DisconnectPacketClientHandler());
    }

    public static IClientPacketHandler GetHandler(int packetCode)
    {
        return _handlers.TryGetValue(packetCode, out var handler) ? handler : null;
    }
}

4. 重构Filter方法

去掉switch分支,改为通过管理器获取处理器并执行逻辑:

服务器端Filter重构

public static class HandleReceivedPacket
{
    public static void Filter(TcpClient client, Stream stream, string data)
    {
        int code = int.Parse(data[1].ToString());
        ServerLogger.Log($"Received data from client: [{code}]: {data.Substring(3)}", LogLevel.Info);

        // 获取对应处理器
        var handler = ServerPacketHandlerManager.GetHandler(code);
        if (handler == null)
        {
            Server.SendToClient(stream, new MessagePacket 
            { 
                Content = "Message sent, but it's not a command... Nothing happened." 
            });
            return;
        }

        // 反序列化为对应数据包对象
        BasePacket packet = CreatePacketByCode(code);
        packet.Deserialize(Encoding.UTF8.GetBytes(data));

        // 执行处理逻辑
        handler.Handle(client, stream, packet);
    }

    // 根据PacketCode创建数据包实例(后续可通过反射自动生成,避免手动维护)
    private static BasePacket CreatePacketByCode(int code)
    {
        return code switch
        {
            0 => new TestPacket(),
            1 => new MessagePacket(),
            2 => new DisconnectPacket(),
            _ => throw new ArgumentOutOfRangeException(nameof(code))
        };
    }
}

客户端Filter重构

public static class HandleReceivedPacket
{
    public static void Filter(string data)
    {
        int code = int.Parse(data[1].ToString());

        var handler = ClientPacketHandlerManager.GetHandler(code);
        if (handler == null)
        {
            Console.WriteLine("Message sent, but it's not a command... Nothing happened.");
            return;
        }

        BasePacket packet = CreatePacketByCode(code);
        packet.Deserialize(Encoding.UTF8.GetBytes(data));

        handler.Handle(packet);
    }

    private static BasePacket CreatePacketByCode(int code)
    {
        return code switch
        {
            0 => new TestPacket(),
            1 => new MessagePacket(),
            2 => new DisconnectPacket(),
            _ => throw new ArgumentOutOfRangeException(nameof(code))
        };
    }
}

进阶优化建议

  1. 使用高效序列化库:替换字符串拼接,采用Protobuf、MessagePack或Unity JsonUtility,提升传输效率和数据结构灵活性。
  2. 反射自动注册:通过反射扫描程序集中所有BasePacket子类和处理器类,自动完成映射,彻底避免手动维护注册列表。
  3. 数据包版本控制:为数据包添加版本号,方便后续迭代时兼容旧版本客户端/服务器。
  4. 异步处理:对耗时的数据包逻辑(如数据库操作)使用异步方法,避免阻塞主线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 07:45:01