4.聊天系统Chat服设计
4.1 知识点
设计思路
Chat服务器作为聊天中枢,专注消息分发、频道管理与序列化,解耦网络路由与业务逻辑。
- 单元管理:通过
ChatUnitManageComponent
缓存并维护在线聊天单元(ChatUnit
)。 - 频道中心:由
ChatChannelCenterComponent
负责申请、解散、查找频道,并协调成员加入、退出。 - 消息树解析:接收并反序列化
ChatInfoTree
,按节点类型分发:广播(世界、地图、广播等)、频道(队伍、公会等)或私聊。 - 双端协议:与客户端共享
ChatInfoTree
、ChatInfoNode
、ChatNodeFactory
和序列化组件,保证协议一致性。 - 回程路由:分发结果包装为
Chat2G_ChatMessage
(全服广播)或Chat2C_Message
(局部/私聊),通过 Gate 自动转发至客户端。
此设计使 Chat 专注业务逻辑,依赖 Gate 提供的网络转发,支持动态热更与横向扩展。
Chat服主要组件
classDiagram class ChatScene { +Start() +OnInnerRoute() } class ChatUnitManageComponent { -DictionaryUnits +Add(unitId, userName, gateRouteId): ChatUnit +Remove(unitId) +TryGet(unitId, out ChatUnit): bool } class ChatUnit { +long Id +string UserName +long GateRouteId +Dictionary Channels +Dictionary SendTime +Dispose() } class ChatChannelCenterComponent { -Dictionary Channels +Apply(channelId): ChatChannel +Disband(channelId) +TryGet(channelId, out ChatChannel): bool } class ChatChannel { +long Id -HashSet Units +Send(tree) +JoinChannel(chatUnitId): bool +ExitChannel(chatUnitId) } class SerializerComponent { +ISerialize Serialize +MemoryStreamBufferPool BufferPool +Initialize() } ChatScene --> ChatUnitManageComponent : 挂载 ChatScene --> ChatChannelCenterComponent : 挂载 ChatScene --> SerializerComponent : 挂载 ChatScene --> NetworkMessagingComponent : 引用 ChatChannelCenterComponent --> ChatChannel : 管理 ChatUnitManageComponent --> ChatUnit : 管理 ChatChannel --> ChatUnit : 通知/广播
ChatScene
方法
Start()
:初始化组件并注册内网路由处理器(Other2Chat_ChatMessageHandler
、G2Chat_LoginRequestHandler
等)。OnInnerRoute()
:接收来自 Gate 或 Map 的聊天相关路由消息。
SerializerComponent
字段/方法
Serialize
:选用 ProtoBuf 等序列化器。BufferPool
:内存流缓冲池,复用MemoryStream
提升性能。Initialize()
:在 Scene 启动时注册序列化器。
ChatUnitManageComponent
方法
Add(unitId, userName, gateRouteId)
:创建或更新ChatUnit
,并返回用于后续路由。Remove(unitId)
:移除ChatUnit
,释放资源。TryGet(unitId, out ChatUnit)
:查询是否在线。
ChatUnit
字段
Id
,UserName
,GateRouteId
:对应 GateSession 的运行时 ID。Channels
:此用户所加入的频道引用。SendTime
:各频道上次发言时间,防止刷屏。
方法
Dispose()
:退出所有频道并清理自身引用。
ChatChannelCenterComponent
方法
Apply(channelId)
:获取或创建频道。Disband(channelId)
:销毁频道,释放资源。TryGet(channelId, out ChatChannel)
:查询指定频道。
ChatChannel
字段/方法
Units
:频道成员 ID 列表。Send(tree)
:广播消息给所有成员,内部调用NetworkMessagingComponent.SendInnerRoute
。JoinChannel(chatUnitId)
:成员加入。ExitChannel(chatUnitId)
:成员退出,空频道时自动Dispose()
。
Chat 服上下线及频道管理流程梳理
ChatUnit 管理中心:上线/下线流程
“ChatUnit 管理中心”维护了所有在线聊天单元的生命周期:
上线
- 检查
ChatUnitManageComponent.Units
中是否已有该UnitId
; - 存在:更新已有
ChatUnit
的GateRouteId
(支持断线重连或重复登录),并刷新UserName
等信息; - 不存在:调用
Add(unitId, userName, gateRouteId)
创建新的ChatUnit
实体,加入到Units
。
- 检查
下线
- 检查缓存中是否有该
ChatUnit
; - 存在:调用
Remove(unitId)
,先执行ChatUnit.Dispose()
(会退出所有频道),再从Units
中移除; - 不存在:无操作。
- 检查缓存中是否有该
此流程确保 ChatUnit 的“在线”与“离线”状态始终与 Gate 层及客户端连接状态保持一致,并自动清理无用实体。
多网关消息回传:ChatUnit → Gate → 客户端
当 ChatSession 分发完成后,可能需要通过多个 Gate 节点将消息发送给所有客户端:
- Chat 调用
NetworkMessagingComponent.SendInnerRoute(gateRouteId, message)
,将消息发给 Gate1 与 Gate2 等。 - 各个
GateSession
收到时,依旧通过其各自的RouteComponent
或直接session.Send()
转发给对应的 ClientSession。 - 如果是广播消息或者世界消息,可能调用的是
networkMessagingComponent.SendInnerRoute(gateSceneConfig.RouteId, Chat2GMessage)
,拿的是配置里Gate服的RouteId,这样GateScene再遍历自己GateUnitManageComponent中的所有GateSession发送消息给各个客户端。 - 这种多网关并行转发的机制,保证了跨机房、多网段环境下的高可用与低延迟,且不丢失任一在线客户端。
聊天频道中心:创建/加入/解散
ChatChannelCenterComponent
提供了频道的“三大基石操作”:
申请/创建频道
接收
channelId
,若不存在则:Entity.Create<ChatChannel>(scene, channelId, ...)
新建频道实体;- 加入
Channels
字典并返回。
加入频道
通过
Apply(channelId).JoinChannel(chatUnitId)
:- 在
ChatChannel.Units
中添加chatUnitId
; - 在对应的
ChatUnit.Channels
字典中保存频道引用。
- 在
解散频道
调用
Disband(channelId)
:- 从
Channels
字典移除该ChatChannel
; - 自动触发
Dispose()
,清理所有成员引用。
- 从
这些方法确保频道的生命周期(申请→使用→解散)流畅、无残留。
聊天频道消息与成员增减
ChatChannel
实现了广播与动态成员管理:
发送消息
Send(tree)
遍历Units
中的每个chatUnitId
:- 取出对应
ChatUnit
,获取GateRouteId
; - 调用
NetworkMessagingComponent.SendInnerRoute(gateRouteId, chat2cMessage)
。
- 取出对应
加入频道
JoinChannel(chatUnitId)
:将chatUnitId
加入Units
,并在ChatUnit.Channels
中记录该频道。
退出频道
ExitChannel(chatUnitId)
:- 从
Units
中移除chatUnitId
; - 从
ChatUnit.Channels
中移除频道引用; - 若
Units.Count == 0
,则调用Dispose()
自动解散频道。
- 从
通过清晰的增删流程与自动销毁机制,频道始终保持正确的成员列表与生命周期管理。
Chat服核心流程图
sequenceDiagram autonumber participant GateSession participant ChatSession GateSession->>ChatSession: 1. G2Chat_LoginRequest { UserName, UnitId, GateRouteId } ChatSession->>SerializerComponent: 2. Initialize if needed ChatSession->>ChatUnitManageComponent: 3. Add(UnitId, UserName, GateRouteId) ChatSession-->>GateSession: 4. Chat2G_LoginResponse { ChatRouteId } GateSession->>ChatSession: 5. Other2Chat_ChatMessage { ChatInfoTree } ChatSession->>SerializerComponent: 6. Deserialize ChatInfoTree ChatSession->>ChatUnitManageComponent: 7. Get chatUnit by UnitId ChatSession->>ChatSceneHelper: 8. Distribution(chatUnit, tree) alt Broadcast 全服 ChatSceneHelper ->>ChatChannelCenterComponent: Apply(WorldChannelId) ChatChannelCenterComponent ->>ChatChannel: Send(tree) else 私聊 / 频道 ChatSceneHelper ->>ChatChannel: Join/Channel/Private(tree) end ChatChannel-->>ChatSession: 9. Chat2G_ChatMessage or Chat2C_ChatMessage ChatSession-->>GateSession: 10. 回传消息 { ChatInfoTree, TargetList? }
Chat服核心流程图说明
G2Chat 登陆请求
GateSession 收到客户端登录后的内部路由,从 Gate 转发过来的G2Chat_LoginRequest
,包含三个关键信息:UserName
:玩家的用户名,用于在 Chat服创建或查找对应的 ChatUnit。UnitId
:GateUnit 在 Gate 服内存中的唯一运行时 ID,用作 ChatUnit 的标识。GateRouteId
:GateSession 的 RunTimeId,ChatUnit 用它回传消息时,Gate 能准确地转回到原始客户端。
SerializerComponent 初始化
在ChatSession
的上下文中,首先检查并初始化SerializerComponent
:- 如果尚未初始化,调用
Initialize()
绑定 ProtoBuf 等序列化器。 - 确保后续对
ChatInfoTree
等复杂对象的序列化和反序列化都能借助缓冲池和高效的序列化组件完成。
- 如果尚未初始化,调用
ChatUnit 注册/更新
调用ChatUnitManageComponent.Add(UnitId, UserName, GateRouteId)
:- 如果
Units
字典中已存在该UnitId
,则更新其GateRouteId
(应对断线重连场景); - 否则,创建新
ChatUnit
,赋值Id
、UserName
、GateRouteId
,并加入到Units
; - 返回该
ChatUnit
,后续分发逻辑中会使用其Channels
和SendTime
等状态。
- 如果
ChatRouteId 回传 Gate
ChatSession 构造Chat2G_LoginResponse { ChatRouteId = chatUnit.RunTimeId }
,并回传给 GateSession:- GateSession 收到后将该
ChatRouteId
保存到自身的RouteComponent.addressMap
中,键为SceneType.Chat
; - GateRouteId 与 ChatRouteId 的映射确保后续所有从 Gate 发往 Chat 的请求都正确路由到对应的 ChatSession。
- GateSession 收到后将该
Other2Chat 聊天消息接收
GateSession 接收到客户端发来的C2Chat_SendMessageRequest
后,通过RouteComponent.Send(SceneType.Chat, message)
转发为Other2Chat_ChatMessage { ChatInfoTree }
给 ChatSession:- 这里 Gate 不干预消息内容,只做透明转发;
- ChatSession 的路由处理器
Other2Chat_ChatMessageHandler
被触发。
反序列化 ChatInfoTree
在Other2Chat_ChatMessageHandler
中,调用SerializerComponent.Deserialize<ChatInfoTree>(bytes)
:- 使用缓冲池内的
MemoryStream
读取二进制数据; - 重建
ChatInfoTree
对象,包括其Nodes
列表,每个ChatInfoNode
的Content
、Data
、NodeType
、EventType
都被还原。
- 使用缓冲池内的
获取 ChatUnit 实例
根据请求携带的chatUnitId
(即 GateRouteId 对应的 ChatUnit.RunTimeId),通过ChatUnitManageComponent.TryGet(chatUnitId, out chatUnit)
:- 验证该用户是否仍在线;
- 获取用户所在的所有频道信息和上次发送时间等状态,便于后续分发与频率限制。
消息分发决策
调用ChatSceneHelper.Distribution(chatUnit, tree)
进行核心逻辑分发:检查发送频率与内容合法性:使用
chatUnit.SendTime
确保各频道间隔不低于配置;计算文本长度和道具数量,防止刷屏或滥发;根据
ChatChannelType
决定分发方式:- Broadcast:调用
Broadcast(scene, tree)
; - Channel(队伍/公会/地图):执行
Channel(chatUnit, tree)
; - Private:走
Private(chatUnit, tree)
私聊分支;
- Broadcast:调用
返回分发结果码,决定后续组装哪种回传消息。
生成并回传消息
分发函数根据分发类型生成两种可能的消息对象:- Chat2G_ChatMessage:当需要向所有客户端广播时(世界聊天、跑马灯等),消息发送目标是 GateScene(SceneType.Gate), 调用
networkMessagingComponent.SendInnerRoute(gateSceneConfig.RouteId, Chat2GMessage)
,拿的是配置里Gate服的RouteId; - Chat2C_ChatMessage:当是私聊或局部频道时,Chat服直接指定目标 ChatUnit 的
GateRouteId
列表,调用NetworkMessagingComponent.SendInnerRoute(gateRouteId, chat2cMessage)
,gateRouteId就是登录时知道的GateSessionRunTimeId,这样在框架层被包装为发送到对应 GateSession。
- Chat2G_ChatMessage:当需要向所有客户端广播时(世界聊天、跑马灯等),消息发送目标是 GateScene(SceneType.Gate), 调用
Gate 转发至客户端
GateSession 收到Chat2G_ChatMessage
或Chat2C_ChatMessage
后:- 对于 Chat2G_ChatMessage,收到消息的是GateScene,执行
GateUnitManageComponent.ForEachUnitSession()
,遍历所有在线GateSession
并调用session.Send(chat2cMessage)
,发送消息给所有客户端; - 对于 Chat2C_ChatMessage,收到消息的是GateSession,无需特殊处理,框架层自动回把这个消息给客户端;
- 客户端最终收到
Chat2C_Message { ChatInfoTree }
,立刻解析并在 UI 渲染。
- 对于 Chat2G_ChatMessage,收到消息的是GateScene,执行
这样,Chat服在整条消息流中只聚焦“反序列化 → 分发决策 → 重序列化回传”,所有网络转发、分发目标的维护均由 Gate 的 RouteComponent
与各 Flag
、UnitManageComponent
协作完成,使得 Chat 逻辑干净、职责单一、易于测试与扩展。
总结
- 解耦路由:Chat 仅处理业务分发,依赖 Gate 提供统一路由。
- 组件化管理:单元、频道、序列化各司其职,易于维护与扩展。
- 灵活分发:同一套
ChatInfoTree
能覆盖世界、频道、私聊等所有聊天场景。 - 性能优化:内存缓冲池、发送冷却(
SendTime
)与及时销毁策略,确保高并发下稳定性。 - 双端一致:客户端与服务器共享协议定义、序列化组件与节点工厂,保证格式一致、无缝升级。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com