5.聊天系统跨服调用

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 }

跨服调用聊天核心流程说明

  1. Gate→Map 登录登记
    在用户通过 Gate 登录的流程中,GateSession 向 MapSession 发送 G2M_LoginRequest,其中包含该用户在 Chat 服的路由 ID(ChatUnitRouteId)。MapSession 接收到后,记录这个 ChatUnitRouteId,并以 M2G_LoginResponse 返回自己的 MapRouteId作为路由。此步骤确保 Map 服拥有发送聊天的目标路由。

  2. 业务触发调用
    当 MapScene 中发生需要推送聊天的业务事件(如玩家击杀、系统公告等)时,调用 ChatHelper.SendChatMessage(scene, chatUnitRouteId, tree),将构建好的 ChatInfoTree 以及先前记录的 chatUnitRouteId 传入 MapSession。

  3. Map→Chat 路由
    MapSession 通过内网协议 Other2Chat_ChatMessage 将消息安全地送达 ChatSession。此协议是基于 ChatUnit 的运行时 ID,所以 ChatSession 自动路由到正确的 ChatUnit

  4. 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。
  5. Chat→Gate 回传
    ChatSession 向 GateSession 发送 Chat2G_ChatMessage(广播场景)或 Chat2C_ChatMessage(定向场景),包含原始的 ChatInfoTree 及可选的目标路由 ID 列表。

  6. 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

×

喜欢就点赞,疼爱就打赏