5.聊天系统跨服调用

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 跨服调用聊天核心流程说明

  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 上渲染展示。

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

×

喜欢就点赞,疼爱就打赏