主题
消息传递系统 (ProtoCustomMessage)
ProtoCustomMessage 用于在客户端和服务器之间传递自定义格式的二进制数据。支持双向通信,提供广播、定向发送和条件过滤。
> 定位说明:ProtoCustomMessage 是框架三层跨端通信机制的底层,直接发送字节数组。 > - 更简单? → 使用 双端事件 (DualEndEvent) > - 需要类型安全? → 使用 TypedMessage<T>
核心 API
csharp
public readonly partial struct ProtoCustomMessage
{
public byte[] Message { get; init; }
#if CLIENT
public void Handle(); // 接收时自动调用,触发 EventServerMessage
public bool SendToServer(); // 发送到服务器
#elif SERVER
public void Handle(Player player); // 接收时自动调用,触发 EventClientMessage
public int Broadcast(Func<Player, bool>? filter = null); // 广播,返回接收人数
public bool SendTo(Player player, Func<Player, bool>? filter = null); // 定向发送
#endif
}基本用法
客户端发送消息到服务器
csharp
#if CLIENT
var data = new { Action = "Move", X = 10.5f, Y = 20.3f };
var json = JsonSerializer.Serialize(data);
var message = new ProtoCustomMessage
{
Message = Encoding.UTF8.GetBytes(json)
};
if (message.SendToServer())
{
Game.Logger.LogInformation("消息发送成功");
}
#endif服务器发送消息到客户端
csharp
#if SERVER
var gameState = new { Wave = 5, TimeRemaining = 120 };
var message = new ProtoCustomMessage
{
Message = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(gameState))
};
// 广播给所有玩家
int count = message.Broadcast();
// 发送给特定玩家
message.SendTo(targetPlayer);
// 条件广播
message.Broadcast(player => player.IsOnline);
#endif事件系统集成
客户端接收服务器消息
csharp
#if CLIENT
public class ClientMessageReceiver : IGameClass
{
public static void OnRegisterGameClass()
{
Game.OnGameTriggerInitialization += () =>
{
Trigger<EventServerMessage>.Subscribe(OnServerMessage);
};
}
private static async Task<bool> OnServerMessage(object sender, EventServerMessage e)
{
try
{
var json = Encoding.UTF8.GetString(e.Message);
var data = JsonSerializer.Deserialize<GameStateUpdate>(json);
Game.Logger.LogInformation("收到服务器消息: {Type}", data?.Type);
return true;
}
catch (Exception ex)
{
Game.Logger.LogError(ex, "处理服务器消息失败");
return false;
}
}
}
#endif服务器接收客户端消息
csharp
#if SERVER
public class ServerMessageReceiver : IGameClass
{
public static void OnRegisterGameClass()
{
Game.OnGameTriggerInitialization += () =>
{
Trigger<EventClientMessage>.Subscribe(OnClientMessage);
};
}
private static async Task<bool> OnClientMessage(object sender, EventClientMessage e)
{
var player = e.Player;
try
{
var json = Encoding.UTF8.GetString(e.Message);
var action = JsonSerializer.Deserialize<PlayerAction>(json);
Game.Logger.LogInformation("玩家 {PlayerId} 操作: {Action}", player.Id, action?.Action);
return true;
}
catch (Exception ex)
{
Game.Logger.LogError(ex, "处理客户端消息失败");
return false;
}
}
}
#endif消息格式
推荐的信封格式
csharp
public class MessageEnvelope
{
public string Type { get; set; } = string.Empty;
public string Version { get; set; } = "1.0";
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public object Data { get; set; } = null!;
}
// 发送
public static void SendTypedMessage<T>(string messageType, T data) where T : class
{
var envelope = new MessageEnvelope { Type = messageType, Data = data };
var message = new ProtoCustomMessage
{
Message = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(envelope))
};
message.SendToServer();
}
// 接收时解析
private static MessageEnvelope? ParseMessage(byte[] messageBytes)
{
try
{
var json = Encoding.UTF8.GetString(messageBytes);
return JsonSerializer.Deserialize<MessageEnvelope>(json);
}
catch (Exception ex)
{
Game.Logger.LogError(ex, "消息解析失败");
return null;
}
}序列化格式选择
| 格式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSON | 易调试、跨平台 | 体积大、性能较低 | 开发阶段、低频消息 |
| MessagePack | 高性能、体积小 | 需额外依赖 | 生产环境、高频消息 |
| 自定义二进制 | 最优性能 | 维护成本高 | 性能关键场景 |
MessagePack 示例
csharp
[MessagePackObject]
public class PlayerAction
{
[Key(0)]
public int PlayerId { get; set; }
[Key(1)]
public string Action { get; set; } = string.Empty;
[Key(2)]
public int Target { get; set; }
}
var bytes = MessagePackSerializer.Serialize(action);
var message = new ProtoCustomMessage { Message = bytes };最佳实践
消息大小控制
csharp
private const int MAX_MESSAGE_SIZE = 64 * 1024; // 64KB
if (messageBytes.Length > MAX_MESSAGE_SIZE)
{
Game.Logger.LogWarning("消息过大: {Size} bytes", messageBytes.Length);
return;
}重试机制
csharp
public static async Task<bool> SendWithRetry(ProtoCustomMessage message, int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
if (message.SendToServer())
{
return true;
}
Game.Logger.LogWarning("消息发送失败,重试 {Retry}/{Max}", i + 1, maxRetries);
await Game.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
}
Game.Logger.LogError("消息发送失败,已达最大重试次数");
return false;
}消息频率控制
csharp
public class MessageRateLimiter
{
private readonly Dictionary<string, Queue<DateTime>> _messageTimes = new();
private readonly TimeSpan _timeWindow = TimeSpan.FromSeconds(1);
private readonly int _maxMessagesPerWindow = 10;
public bool CanSendMessage(string messageType)
{
var now = DateTime.UtcNow;
if (!_messageTimes.TryGetValue(messageType, out var times))
{
times = new Queue<DateTime>();
_messageTimes[messageType] = times;
}
while (times.Count > 0 && now - times.Peek() > _timeWindow)
{
times.Dequeue();
}
if (times.Count >= _maxMessagesPerWindow)
{
return false;
}
times.Enqueue(now);
return true;
}
}故障排除
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
SendToServer() 返回 false | 网络断开、消息过大 | 检查网络状态和消息大小 |
| 事件处理器未触发 | 未注册监听器、消息格式错误 | 确认在 OnGameTriggerInitialization 中注册 |
| 内存增长 | 未注销事件监听器 | 使用 trigger.Unregister() 清理 |
相关文档
- 双端事件系统 (DualEndEvent) — 触发器模式跨端通信
- 强类型消息系统 (TypedMessage) — JSON 结构化消息
- 异步编程最佳实践