5.聊天系统跨服调用
5.1 知识点
设计思路
在大型游戏架构中,除了 Gate 与 Chat 服务器外,诸如 Map、战斗、经济等后端服也需要在相关业务逻辑触发时推送聊天消息。为了保持耦合度低、调用简单、统一协议,我们需要:
- 统一入口:所有非客户端发起的聊天均通过 Chat 服提供的路由接口;
- 其他服上线时:要使用聊天服其他服上线时时,Gate要发包含chatUnitRouteId消息,让其他服记录chatUnitRouteId,这样其他服调用聊天发消息接口时才有目标;
- 轻量调用:业务服无需了解聊天细节,只需构造
ChatInfoTree
并调用ChatHelper.SendChatMessage()
; - 路由透明:Chat 服负责反序列化、分发、回传 Gate,无需业务服关注;
- 可扩展性:新增频道或消息格式时,业务服调用方式不变;
这种设计确保各业务服只需在必要时触发一次调用,就能借助 Chat 服完成全服或定向聊天推送。
跨服调用聊天核心流程图
sequenceDiagram autonumber participant MapScene as MapScene participant MapSession as MapSession participant ChatScene as ChatScene participant ChatSession as ChatSession participant GateServer as GateSession participant Client as ClientSession %% —— 0. Gate→Map 登录登记 —— GateServer->>MapSession: 0. G2M_LoginRequest { ChatUnitRouteId } MapSession-->>GateServer: 0.1 M2G_LoginResponse { MapRouteId } %% —— 1. 业务触发调用 —— MapScene->>MapSession: 1. ChatHelper.SendChatMessage(chatUnitRouteId, tree) %% —— 2. Map→Chat 路由 —— MapSession->>ChatSession: 2. Other2Chat_ChatMessage { ChatInfoTree } %% —— 3. Chat 服分发 —— ChatSession->>ChatScene: 3. Deserialize & Prepare ChatSession->>ChatScene: 4. Distribution(chatUnit, tree) alt 全服广播 ChatScene->>ChatChannelCenter: 5a. Apply(WorldChannelId) ChatChannelCenter->>ChatChannel: 6a. Send(tree) ChatChannel-->>ChatSession: 7a. Chat2G_ChatMessage { tree } else 私聊/定向频道 ChatScene->>ChatChannel: 5b. Private/Channel(tree) ChatChannel-->>ChatSession: 6b. Chat2C_ChatMessage { tree, TargetList } end %% —— 4. Chat→Gate 回传 —— ChatSession-->>GateServer: 8. Chat2G_ChatMessage or Chat2C_ChatMessage { tree, TargetList? } %% —— 5. Gate→客户端下发 —— GateServer->>Client: 9. Chat2C_Message { tree }
跨服调用聊天核心流程说明
Gate→Map 登录登记
在用户通过 Gate 登录的流程中,GateSession 向 MapSession 发送G2M_LoginRequest
,其中包含该用户在 Chat 服的路由 ID(ChatUnitRouteId
)。MapSession 接收到后,记录这个ChatUnitRouteId
,并以M2G_LoginResponse
返回自己的MapRouteId
作为路由。此步骤确保 Map 服拥有发送聊天的目标路由。业务触发调用
当 MapScene 中发生需要推送聊天的业务事件(如玩家击杀、系统公告等)时,调用ChatHelper.SendChatMessage(scene, chatUnitRouteId, tree)
,将构建好的ChatInfoTree
以及先前记录的chatUnitRouteId
传入 MapSession。Map→Chat 路由
MapSession 通过内网协议Other2Chat_ChatMessage
将消息安全地送达 ChatSession。此协议是基于 ChatUnit 的运行时 ID,所以 ChatSession 自动路由到正确的ChatUnit
。Chat 服分发
反序列化与准备:ChatSession 使用
SerializerComponent
将二进制树结构还原为ChatInfoTree
对象(可选)。分发调用:调用
ChatSceneHelper.Distribution(chatUnit, tree)
,根据tree.ChatChannelType
分别走广播或定向逻辑:- 全服广播:通过
ChatChannelCenter.Apply(WorldChannelId)
获取世界频道实例,再执行Send(tree)
,由频道将消息发给所有在线ChatUnit
;最终生成Chat2G_ChatMessage
回传 Gate。 - 私聊/定向频道:直接调用
ChatChannel.Private(tree)
或ChatChannel.Channel(tree)
,生成Chat2C_ChatMessage
,并携带目标列表TargetList
回传 Gate。
- 全服广播:通过
Chat→Gate 回传
ChatSession 向 GateSession 发送Chat2G_ChatMessage
(广播场景)或Chat2C_ChatMessage
(定向场景),包含原始的ChatInfoTree
及可选的目标路由 ID 列表。Gate→客户端下发
GateSession 根据收到的消息类型:- 对于
Chat2G_ChatMessage
,遍历所有在线ClientSession
并发送。 - 对于
Chat2C_ChatMessage
,只向指定的ClientSession
(通过 RunTimeId)发送。
最终,客户端收到Chat2C_Message
,解析出ChatInfoTree
并在 UI 上渲染展示。
- 对于
核心伪代码示例
GateLoginHelper
/// <summary>
/// 处理用户在Map服务器的上线流程
/// </summary>
/// <param name="scene">当前场景</param>
/// <param name="gateUnit">网关单元</param>
/// <param name="gateRouteId">网关路由ID</param>
/// <returns>包含错误码、游戏服务器路由ID和路由类型的元组</returns>
private static async FTask<(uint errorCode, long routeId, int routeType)> OnlineMap(Scene scene, GateUnit gateUnit,
long gateRouteId)
{
// 获取游戏服务器配置
var mapConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Map)[0];
// 向游戏服务器发送登录请求,携带已获取的聊天服务器路由ID
var response = (M2G_LoginResponse)await scene.NetworkMessagingComponent.CallInnerRoute(mapConfig.RouteId,
new G2M_LoginRequest()
{
// gateUnit.Routes[RouteType.ChatRoute]记录了ChatUnit的RuntimeId 就是ChatUnitRouteId
// 发送ChatUnitRouteId Map服的MapUnit要记录ChatUnitRouteId
// 这样以后发消息才有目标 能找到对应的ChatUnit
ChatUnitRouteId = gateUnit.Routes[RouteType.ChatRoute]
});
// 返回登录结果 包括 错误码 MapUnit的RuntimeId 服务器类型是地图服
return (response.ErrorCode, response.MapRouteId, RouteType.GMapRoute);
}
- OnlineMap:GateUnity上线时,通知Mao服Unit(其他服也是如此)上线时要执行的委托。
ChatHelper
public static class ChatHelper
{
/// <summary>
/// 向 Chat 服务器发送聊天消息(任何非 Chat 场景调用)
/// </summary>
/// <param name="scene">调用方场景</param>
/// <param name="chatUnitRouteId">目标 ChatUnit 的路由 ID(Gate 注册时返回)</param>
/// <param name="tree">构建好的 ChatInfoTree</param>
public static void SendChatMessage(Scene scene, long chatUnitRouteId, ChatInfoTree tree)
{
if (scene.SceneType == SceneType.Chat)
{
Log.Warning("ChatHelper.SendChatMessage: 不可在 Chat 场景中调用");
return;
}
var other2ChatChatMessage = new Other2Chat_ChatMessage()
{
ChatInfoTree = tree
};
scene.NetworkMessagingComponent.SendInnerRoute(chatUnitRouteId, other2ChatChatMessage);
}
}
- SendChatMessage:其他服想发聊天消息调用的接口。
- scene.SceneType 检查:避免 Chat 服内部循环调用自身。
- Other2Chat_ChatMessage:内网路由协议,由 Chat 服的
Other2Chat_ChatMessageHandler
统一接收。
Other2Chat_ChatMessageHandler
public sealed class Other2Chat_ChatMessageHandler : Route<ChatUnit, Other2Chat_ChatMessage>
{
/// <summary>
/// 处理来自其他服(如 Map、Game)的聊天消息
/// </summary>
/// <param name="chatUnit">目标 ChatUnit 实例(由路由自动查找)</param>
/// <param name="message">包含 ChatInfoTree 的消息载体</param>
protected override async FTask Run(ChatUnit chatUnit, Other2Chat_ChatMessage message)
{
// 调用聊天分发逻辑,根据频道类型执行广播/私聊/群聊
var result = ChatSceneHelper.Distribution(chatUnit, message.ChatInfoTree, false);
// 如果分发返回非 0,说明出现错误(如频道不存在、发送过于频繁等)
if (result != 0)
{
Log.Warning($"Other2Chat_ChatMessageHandler: 分发失败,结果码 = {result}");
}
// 分发完成后立即返回已完成的任务
await FTask.CompletedTask;
}
}
- **Route<ChatUnit,…>**:接受这个消息的接受方是
ChatUnit
,因为自动根据ChatUnit
的 RunTimeId 找到正确ChatUnit
实例。 - Distribution:核心分发逻辑,基于不同的消息树类型发送Chat2G或Chat2C的消息,参见第四篇 Chat 服设计。
其他服调用示例
// Map 服:击杀事件触发世界广播
var tree = ChatTreeFactory.World(scene)
.AddText("玩家")
.AddText(killerName)
.AddText("击败了")
.AddText(victimName);
ChatHelper.SendChatMessage(scene, chatUnitRouteId, tree);
// Chat 服侧已在 Other2Chat_ChatMessageHandler 中统一处理,
// 不需要 Map 服关心分发细节
// Guild 服:公会频道消息
var guildChannelId = GetGuildChannelId(guildId);
var tree = ChatTreeFactory.Team(scene)
.AddText($"公会{guildName}已开启活动!");
ChatHelper.SendChatMessage(scene, chatUnitRouteId, tree);
总结
- 统一协议:所有后端服仅需构建
ChatInfoTree
并调用ChatHelper
,无需重复实现分发逻辑。 - 低耦合:业务服专注业务事件,Chat 服负责分发、Gate 服负责路由,职责清晰。
- 高可用:利用内网路由与多网关并行传递,保证跨服消息高可靠、低延迟。
- 灵活扩展:新增频道类型、定向推送或富文本节点,业务服调用方式不变,持续兼容。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com