c++游戏后端开源框架学习——wukong(六、大厅)

📅 2026/8/14 2:43:56
c++游戏后端开源框架学习——wukong(六、大厅)
Lobby大厅服对应代码目录lobby/一、Lobby 是什么Lobby 是玩家业务逻辑的主载体。每个在线玩家在 Lobby 中有一个LobbyObject它负责处理客户端发来的游戏消息维护玩家数据并定期同步给 Record 服通过心跳维持与 Gateway 和 Record 的连接管理战斗状态打个比方如果 Gateway 是大楼前台那 Lobby 就是玩家的办公室——玩家的大部分业务都在这里处理数据存在这里的文件柜里定期送到档案室Record存档。二、核心设计2.1 核心类类文件职责LobbyServerlobby_server.h单例初始化RPC/Agent/Redis/事件/热更新LobbyObjectlobby_object.h每个在线玩家一个玩家业务核心LobbyObjectManagerlobby_object_manager.h管理所有LobbyObjectLobbyServiceImpllobby_service.hRPC服务实现MessageHandlermessage_handler.h游戏消息处理器2.2 LobbyObject 核心属性classLobbyObject:publicMessageTarget{UserId userId_;RoleId roleId_;ServerId serverId_;// 逻辑服IDServerId gatewayId_;// 关联的网关服0离线ServerId recordId_;// 关联的记录服std::string lToken_;// 游戏对象唯一标识std::string sceneId_;// 当前场景ID空在大厅std::unique_ptrLobbyObjectDatadata_;// 玩家业务数据BattleLobbyPhase battleLobbyPhase_;// 战斗状态Cond cond_;// 协程条件变量EventEmitter emiter_;// 本地事件intenterTimes_;// 重登计数器防误判intheartbeatFailNum_;// 心跳失败计数Timer*leaveGameTimer_;// 离线定时器};2.3 三个核心协程LobbyObject 启动后运行三个协程各司其职LobbyObject.start() ├── 心跳协程 heartbeatRoutine() 每20秒 ├── 存盘协程 syncRoutine() 每1秒 └── 更新协程 updateRoutine() 每 updatePeriod 毫秒三、角色加载流程当 Gateway 认证通过后会调用 Lobby 的loadRoleRPC 加载角色Gateway Lobby Record Redis │ │ │ │ │── loadRole RPC ─────────────→│ │ │ │ (roleId, userId, lToken, │ │ │ │ serverId) │ │ │ │ │ │ │ │ 检查角色是否已存在 │ │ │ 防止重复加载 │ │ │ │ │ │ │ GetRecordAddress ──────────────────────────────────────────→│ │ 查询Record位置 │ │ │ │←── {recordAddr or } ────────────────────────────────│ │ │ │ │ │ 若Record地址不存在 │ │ │ ├── randomServer随机选Record │ │ │ └── 生成rToken │ │ │ │ │ │ │ SetLobbyAddress ────────────────────────────────────────────→│ │ 注册Lobby位置hsetnx原子操作 │ │ │ TTL 60秒 │ │ │ │ │ │ │ │── loadRoleData RPC ─────────→│ │ │ │ (roleId, userId, lToken, │ │ │ │ serverId) │ │ │ │ │── LoadRole ──────────→│ │ │ │ (从Redis或MySQL加载) │ │ │ │←── roleData ──────────│ │ │←── {roleData} ───────────────│ │ │ │ │ │ │ createLobbyObject() │ │ │ ├── 用DemoLobbyObjectData初始化数据 │ │ │ ├── 设置gatewayId, recordId │ │ │ ├── 注册到map │ │ │ └── start() 启动三个协程 │ │ │ │ │ │ │←── loadRole 响应 ────────────│ │ │ │ │ │ │ │── enterGame RPC ────────────→│ │ │ │ (通知Lobby玩家已进入游戏) │ │ │ │ │ │ │ │ onEnterGame() │ │ │ ├── buildAllDatas 构建全量数据 │ │ │ ├── 发送 S2C_ENTERGAME 给客户端 │ │ │ └── 发送 S2C_ENTERLOBBY 给客户端 │ │关键设计防重复加载先检查角色是否已存在existRole避免同一角色加载两次Record地址查询优先从Redis查询Record位置不存在则随机分配原子注册SetLobbyAddress用hsetnx保证原子性防止重复注册lToken唯一标识整个游戏期间用于校验消息归属四、心跳机制4.1 心跳协程// lobby_object.cpp:252-360voidLobbyObject::heartbeatRoutine(){while(running_){cond_.wait(TOKEN_HEARTBEAT_PERIOD);// 等20秒// 1. 刷新Lobby地址TTL保持Location不过期RedisUtils::SetLobbyAddressTTL(cache,roleId_,lToken_);// 2. 向Gateway心跳intretheartbeatToGateway();switch(ret){case1:// 成功heartbeatFailNum_0;break;case0:// 网关对象不存在客户端断了gatewayId_0;onOffline();break;case-1:// Gateway连不上case-2:// RPC超时heartbeatFailNum_;if(heartbeatFailNum_3){gatewayId_0;onOffline();}break;}// 3. 向Record心跳retheartbeatToRecord();if(失败){leaveGame();// Record心跳失败直接销毁对象}}}4.2 心跳失败处理策略失败对象失败次数处理Gateway1-2次累计计数继续等待Gateway3次gatewayId_0触发onOfflineGateway返回0客户端已断线立即onOfflineRecord1次直接leaveGame销毁对象4.3 防误判机制enterTimes_计数器玩家重登过程中旧对象还在、新对象在创建心跳可能返回异常结果。通过enterTimes_标记重登过程避免误判离线。五、离线与销毁5.1 离线流程心跳检测到Gateway不可达 │ ▼ onOffline() │ ├── 不立即销毁启动5秒定时器 │ leaveGameTimer_ Timer::create(5000, leaveGame) │ ├── 5秒内重连 │ └── 是 → 取消定时器恢复正常 │ └── 5秒超时 → leaveGame() ├── 通知战斗服移除玩家如果在战斗中 ├── LobbyObjectManager.leaveGame() │ ├── stop() → 各协程退出 │ ├── 清理事件和定时器 │ ├── onDestory() │ ├── RemoveLobbyAddress从Redis移除Lobby位置 │ └── 从map中删除 └── 对象销毁5.2 为什么等5秒网络抖动可能导致短暂断线5秒缓冲期让玩家有机会断线重连避免频繁创建/销毁对象带来的开销和数据同步问题。六、数据存盘6.1 存盘协程// lobby_object.cpp:362-386voidLobbyObject::syncRoutine(){while(running_){cond_.wait(SYNC_PERIOD);// 等1秒// 收集脏数据std::vectorDataFragmentdatas;data_-buildSyncDatas(datas);// 只收集有变更的数据if(!datas.empty()){// 通过RPC同步给Record服recordAgent-sync(recordId_,roleId_,lToken_,datas);}}}6.2 脏数据机制玩家业务操作如花金币 │ ▼ LobbyObjectData.setCurrency(newValue) │ ├── 更新内存值 └── 标记 dirty_map_[currency] true 存盘协程每1秒 ├── buildSyncDatas()遍历dirty_map_收集标记为dirty的数据 ├── RPC发送给Record └── Record返回成功后清除dirty标记关键点只同步有变更的数据增量同步减少网络传输和Record处理压力。七、战斗状态管理7.1 状态机setWaitingEnterBattle() InHall ──────────────────────────────→ WaitingBattleKcp ↑ │ │ │ notifyPlayerBattleState │ │ 战斗服通知鉴权成功 │ ▼ └──────────────────────────────────── InBattle clearBattleStateFromBattleServer7.2 战斗流程1. 客户端发 C2S_StartBattle 到 Lobby 2. Lobby 调用 BattleService.requestBattleAssignment RPC → Battle服分配房间返回 roomId、KCP地址、sessionToken 3. Lobby 设置 battleLobbyPhase_ WaitingBattleKcp 4. Lobby 发送 S2C_BattleEnter 给客户端含KCP地址和sessionToken 5. 客户端用KCP连接Battle服发送鉴权 6. Battle服鉴权成功后调用 Lobby.notifyPlayerBattleState RPC → Lobby 设置 battleLobbyPhase_ InBattle 7. 战斗结束Battle服通知Lobby → Lobby 设置 battleLobbyPhase_ InHall7.3 离线处理如果玩家在战斗中离线onOffline() └── notifyBattleLeaveIfNeeded() → 通知Battle服移除玩家八、消息处理8.1 消息接收流程客户端消息 │ ▼ Gateway.forwardIn → LobbyService.forwardIn RPC │ ▼ LobbyObjectManager 根据 roleId 找到 LobbyObject │ ▼ LobbyObject.handleMessage(msgType, tag, rawMsg) │ ├── MessageHandleManager 查找消息注册信息 ├── Protobuf反序列化 ├── need_wait_?正在处理中 │ ├── 是 → 入等待队列保证消息串行处理 │ └── 否 → 继续 ├── needCoroutine? │ ├── 是 → 启动新协程处理 │ └── 否 → 直接调用处理函数 └── 处理完成后检查等待队列8.2 Demo 消息处理器// lobby/src/message_handler.cppvoidregisterMessages(){REGISTER_MESSAGE(C2S_MESSAGE_ID_ECHO,EchoHandle,true);// 回声测试REGISTER_MESSAGE(C2S_MESSAGE_ID_STARTBATTLE,StartBattleHandle,true);// 开始战斗REGISTER_MESSAGE(C2S_MESSAGE_ID_LEAVEGAME,LeaveGameHandle,true);// 离开游戏}九、LobbyObjectData9.1 数据抽象classLobbyObjectData{virtualvoidinitData(conststd::vectorDataFragmentdatas)0;virtualvoidbuildSyncDatas(std::vectorDataFragmentdatas)0;virtualvoidbuildAllDatas(std::vectorDataFragmentdatas)0;protected:std::mapstd::string,booldirty_map_;// 脏数据标记};9.2 代码生成DemoLobbyObjectData由tools/gameDataCodeGen自动生成根据gameData.json配置生成基本类型字段string/uint32/int32 → 直接值 dirty标记Protobuf消息字段指针 dirty标记列表字段map getAllKeys/has/get/add/remove dirty标记// demo/gameData.json{members:[{attr:name,type:string,need_profile:true},{attr:exp,type:uint32},{attr:lv,type:uint32,need_profile:true},{attr:currency,type:demo.pb.Currency},{attr:card,type:demo.pb.Cards,inner_list:cards,inner_list_item_type:demo.pb.Card,inner_list_item_id_key:cardid}]}十、解决的问题问题Lobby 的解决方案玩家业务逻辑承载LobbyObject 三个协程心跳/存盘/更新数据一致性脏数据标记 增量同步每秒断线感知心跳3次失败判定离线 5秒缓冲期消息并发安全同一玩家消息串行处理等待队列重登误判enterTimes_ 计数器战斗状态管理状态机InHall/WaitingBattleKcp/InBattle数据结构重复编写Go代码生成器自动生成LobbyObjectData离线数据清理5秒定时器 leaveGame流程