5.聊天系统跨服调用
5.1 设计思路
在大型游戏架构中,除了 Gate 与 Chat 服务器外,诸如 Map、战斗、经济等后端服也需要在相关业务逻辑触发时推送聊天消息。为了保持耦合度低、调用简单、统一协议,我们需要:
- 统一入口:所有非客户端发起的聊天均通过 Chat 服提供的路由接口;
- 其他服上线时:要使用聊天服其他服上线时时,Gate要发包含chatUnitRouteId消息,让其他服记录chatUnitRouteId,这样其他服调用聊天发消息接口时才有目标;
- 轻量调用:业务服无需了解聊天细节,只需构造
ChatInfoTree
并调用ChatHelper.SendChatMessage()
; - 路由透明:Chat 服负责反序列化、分发、回传 Gate,无需业务服关注;
- 可扩展性:新增频道或消息格式时,业务服调用方式不变;
这种设计确保各业务服只需在必要时触发一次调用,就能借助 Chat 服完成全服或定向聊天推送。
5.2 跨服调用聊天核心流程图
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 }
5.3 跨服调用聊天核心流程说明
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 上渲染展示。
- 对于
5.4 核心伪代码示例
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);
5.5 总结
- 统一协议:所有后端服仅需构建
ChatInfoTree
并调用ChatHelper
,无需重复实现分发逻辑。 - 低耦合:业务服专注业务事件,Chat 服负责分发、Gate 服负责路由,职责清晰。
- 高可用:利用内网路由与多网关并行传递,保证跨服消息高可靠、低延迟。
- 灵活扩展:新增频道类型、定向推送或富文本节点,业务服调用方式不变,持续兼容。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com