如何通过WebSocket接收的消息安全执行C#动态类方法?
解决C# WebSocket服务器中JSON消息的类型匹配与方法调用问题
嘿,我完全懂你的困扰——在游戏引擎里用一堆if-else来判断WebSocket消息类型、调用对应方法,后期维护起来简直是灾难,尤其是消息类型越来越多的时候。下面给你几个游戏开发里常用的、干净又易扩展的方案:
方案1:基于字典的策略模式(最直接易上手)
这个方案核心是把消息类型标识和对应的处理方法提前映射到字典里,避免冗余的条件判断,性能也很稳定(字典查找是O(1))。
首先定义所有消息的基类,用来承载消息类型标识:
// 所有游戏消息的基类 public abstract class BaseGameMessage { // 用来区分不同消息类型的字段,比如"UserAuth"、"PlayerMove" public string MsgType { get; set; } } // 你的用户授权消息类 public class UserAuthTicket : BaseGameMessage { public string AuthTicket { get; set; } // 业务逻辑方法 public static bool Validate(UserAuthTicket ticket) { // 这里写你的授权验证逻辑 return !string.IsNullOrEmpty(ticket.AuthTicket); } }
然后实现消息处理器,提前注册所有消息的处理逻辑:
public class GameMessageHandler { private readonly Dictionary<string, Action<BaseGameMessage>> _msgHandlers; public GameMessageHandler() { _msgHandlers = new Dictionary<string, Action<BaseGameMessage>> { // 注册消息类型与对应处理方法 { "UserAuth", msg => UserAuthTicket.Validate((UserAuthTicket)msg) }, // 后续新增消息直接在这里加一行就行,不用改核心逻辑 { "PlayerMove", msg => PlayerMovement.Process((PlayerMovement)msg) } }; } public void HandleReceivedMessage(string jsonData) { // 先反序列化为基类,获取消息类型 var baseMsg = JsonSerializer.Deserialize<BaseGameMessage>(jsonData); if (baseMsg == null || !_msgHandlers.TryGetValue(baseMsg.MsgType, out var handler)) { // 处理未知消息类型(比如打日志、断开连接等) Console.WriteLine($"未知消息类型:{baseMsg?.MsgType}"); return; } // 反序列化为具体的消息类型 var specificMsg = JsonSerializer.Deserialize(jsonData, Type.GetType($"YourNamespace.{baseMsg.MsgType}")); if (specificMsg is BaseGameMessage gameMsg) { handler(gameMsg); } } }
方案2:反射+特性标记(适合大量消息类型)
如果你的游戏后期会有几十上百种消息类型,手动注册字典太麻烦,可以用特性标记+反射自动扫描的方式,启动时自动把所有消息类和处理方法绑定。
首先定义消息类型特性:
// 用来标记消息类对应的类型标识 [AttributeUsage(AttributeTargets.Class)] public class MessageTypeAttribute : Attribute { public string TypeName { get; } public MessageTypeAttribute(string typeName) { TypeName = typeName; } }
给消息类加上特性:
[MessageType("UserAuth")] public class UserAuthTicket : BaseGameMessage { public string AuthTicket { get; set; } public static bool Validate(UserAuthTicket ticket) { return !string.IsNullOrEmpty(ticket.AuthTicket); } }
然后实现自动扫描的处理器:
public class AutoGameMessageHandler { private readonly Dictionary<string, Action<BaseGameMessage>> _msgHandlers; public AutoGameMessageHandler() { _msgHandlers = new Dictionary<string, Action<BaseGameMessage>>(); // 扫描所有继承自BaseGameMessage的非抽象类 var messageTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => typeof(BaseGameMessage).IsAssignableFrom(t) && !t.IsAbstract); foreach (var type in messageTypes) { // 获取消息类上的特性 var attr = type.GetCustomAttribute<MessageTypeAttribute>(); if (attr == null) continue; // 假设每个消息类都有一个静态的处理方法(比如Validate、Process) var handleMethod = type.GetMethod("Validate", new[] { type }); if (handleMethod == null) continue; // 创建委托并加入字典 Action<BaseGameMessage> handler = msg => handleMethod.Invoke(null, new[] { msg }); _msgHandlers.Add(attr.TypeName, handler); } } // 处理消息的方法和方案1一致 public void HandleReceivedMessage(string jsonData) { var baseMsg = JsonSerializer.Deserialize<BaseGameMessage>(jsonData); if (baseMsg == null || !_msgHandlers.TryGetValue(baseMsg.MsgType, out var handler)) { Console.WriteLine($"未知消息类型:{baseMsg?.MsgType}"); return; } var specificMsg = JsonSerializer.Deserialize(jsonData, Type.GetType($"YourNamespace.{baseMsg.MsgType}")); if (specificMsg is BaseGameMessage gameMsg) { handler(gameMsg); } } }
这个方案的好处是新增消息时,只要给类加个特性、写好处理方法就行,不用管注册逻辑,适合大型游戏项目。
方案3:使用MediatR解耦(大型项目首选)
如果你的游戏引擎允许引入第三方库,MediatR是个非常棒的选择——它基于CQRS模式,把消息(命令/查询)和处理逻辑完全解耦,代码会非常干净,后期扩展和维护成本极低。
首先安装MediatR NuGet包,然后定义消息和对应的处理器:
// 定义授权命令(消息) public class UserAuthCommand : IRequest<bool> { public string AuthTicket { get; set; } } // 对应的命令处理器 public class UserAuthCommandHandler : IRequestHandler<UserAuthCommand, bool> { public Task<bool> Handle(UserAuthCommand request, CancellationToken cancellationToken) { // 授权验证逻辑 return Task.FromResult(!string.IsNullOrEmpty(request.AuthTicket)); } }
在服务器端使用时,只需要注入IMediator,然后发送消息即可:
private readonly IMediator _mediator; // 通过构造函数注入IMediator(需要配置依赖注入容器) public WebSocketServer(IMediator mediator) { _mediator = mediator; } public async Task HandleWebSocketMessage(string jsonData) { // 先获取消息类型,反序列化为对应的命令类型 var baseMsg = JsonSerializer.Deserialize<BaseGameMessage>(jsonData); var commandType = Type.GetType($"YourNamespace.{baseMsg.MsgType}Command"); var command = JsonSerializer.Deserialize(jsonData, commandType); if (command is IRequest request) { // 自动找到对应的处理器执行逻辑 await _mediator.Send(request); } }
总结
- 小项目或者刚起步的游戏,优先用策略模式,简单直接,调试方便;
- 消息类型多的项目,用反射+特性,减少手动注册的工作量;
- 大型复杂游戏,推荐用MediatR,彻底解耦消息和逻辑,后期维护超省心。
另外要注意:反序列化时尽量指定命名空间,避免类型查找失败;游戏里还要考虑消息的序列化效率,比如用Protobuf代替JSON(如果对性能要求极高的话)。
内容的提问来源于stack exchange,提问作者Luicy Willowfield
相关产品推荐
相关产品推荐

