麻将节点间路由转发链:失败回退与错误传播语义(ROUTE_SEMANTICS)
第0条(基线)本文件语义以产品源码为权威源;引用只允许以文件与符号(函数、协议名)给出;行号引用前必须复核现码。
第一章 总则
第1条(拓扑与消息链)拓扑为 SCENE(场景服)到 MC(匹配中心)到 GC(游戏中心)到 GS(游戏服)与 PS(玩法服);CLIENT 每玩家一份。消息链:PS 经 CallCenter 到 MC,经转发到 GC,经转发到 GS,经 CallClient 到 CLIENT。
第2条(记法)A --函数--> B 表示 A 调用转发函数将消息送往 B;同进程直调标注 [直调]。
第二章 转发链
第3条(状态同步,全量快照)
PS Game_BroadcastGameState
├─[直调] GameCenter2GameServer_SyncGameState
└─ module:CallCenter("PlayServer2MatchCenter_SyncGameState", 房间, dwID, GCID, 状态)
└─ MC 处理器 → Forward_ToGameCenter
├─ 同中心:[直调] GC 函数
└─ 跨中心:CallCenter 到目标中心 → GC 处理器
└─ Forward_ToGameServer → GS 处理器 → SendToClient → S2C_MAHJONG_SyncGameState
回执:无,单向广播;客户端必须以全量快照为权威状态。
第4条(行动通知)
PS 各广播点
├─[直调] GameCenter2GameServer_NotifyAction / GameAction
└─ module:CallCenter("PlayServer2MatchCenter_NotifyAction" / "GameAction", 房间, dwID, GCID, 数据)
└─ MC 处理器 → Forward_ToGameCenter → GC 处理器 → Forward_ToGameServer → GS → S2C 下发
回执:无显式回执;本家 GameAction 到达时,客户端必须取消对应 C2S 请求的 5 秒超时定时器(BUG#30)。
第5条(回合等待)
PS Game_BroadcastRoundWaitState
├─[直调] GameCenter2GameServer_RoundWaitState
└─ module:CallCenter("PlayServer2MatchCenter_RoundWaitState")
└─ MC 处理器 → GC 处理器 → Forward_ToGameServer → GS 下发
客户端将状态写入并启动定时器;后续全量快照权威覆盖,属预期行为。
第6条(结算:主通道加双兜底)
主通道(非外游戏):
PS 结算发布与 Game_End
└─ module:CallCenter("PlayServer2MatchCenter_EndGame", 房间, 匹配类型, 结算数据)
└─ MC 处理器 → module:CallSceneServer("MatchCenter2SceneServer_EndGame") ← 只去场景服,不去 GC
└─ SCENE 逐玩家 SceneServer2GameCenter_EndGame
└─ GC Forward_ToGameServer("GameCenter2GameServer_EndGame")
└─ GS 处理 → SendToClient("S2C_MAHJONG_EndGame")
兜底通道 A(资金):PS 逐玩家 module_guardians.AutoTakeOut;失败进 5 秒重试队列。
兜底通道 B(送达):场景服结算 pending 重投;转发失败触发。
失败回环:GC 到 GS 转发失败必须回 ForwardFailed 到场景服,由场景服存 pending 或跨中心重路由。EndGame 的客户端通知必须经场景服中转;MC 到场景服一跳无失败检测,任何一跳断裂客户端即收不到 EndGame,只能靠重连拉取。
第7条(崩溃补偿)
触发源 A:MC 健康检查或玩家操作发现 PS 断开 → CallSceneServer("MatchCenter2SceneServer_Crash") 并取消房
触发源 B:场景服自发现(验证超时、中心断开处理)→ Center_Call("SceneServer2MatchCenter_Crash")
SCENE MatchCenter2SceneServer_Crash
└─ 逐玩家 module:CallCenter("SceneServer2GameCenter_Crash", 类型, 参数)
└─ GC 处理器 → Forward_ToGameServer("GameCenter2GameServer_Crash")
└─ GS 处理 → 线性流水线回退 → SendToClient("S2C_MAHJONG_Crash")
SCENE 收尾:玩家解绑 → 分享码移除 → 反向索引清理 → 房间移除 → 反向通知 MC 清理
第8条(退款,随崩溃通道)
GS 崩溃处理 key 等于期望分支
└─ RollbackMahjongPendingEntry → RefundFrozenOnAbnormal
├─ 状态守卫:无房间且无冻结 → 幂等完成
├─ 房间 ID 或冻结 token 校验不符 → 拒绝,不退款
├─ token 先推进并持久化
└─ 发放失败 → token 回退,可重试
另有 Guardians 通道:Mahjong_GuardiansRefundAbnormal。
第9条(C2S 请求失败回包级联)
PS 处理失败 → module:CallCenter("PlayServer2MatchCenter_RequestFailed")
└─ MC 处理器 → Forward_ToGameCenter → GC 处理器 → Forward_ToGameServer
└─ GS 处理器:协议映射表 → SendToClient(对应 S2C, 错误码);入场失败回滚;退房失败清陈旧房号
MC 前置错误(转发前):GetPlayableRoomOrReply
├─ 房间不存在 → 回 3300001
└─ PS 断开 → 回 3300017 并触发断开处理
第10条(跨中心重路由)
SCENE ForwardFailed 处理
├─ 成功匹配 → 回应答,避免开局等待
├─ EndGame → 存 pending
├─ 玩家在当前中心 → 跨中心转发(带元数据)
├─ 否则转原始中心
└─ 无可用中心 → 回 RequestFailed 3300017
GC 跨中心转发失败 → 回补 RequestFailed 到源 GC;本地兜底失败仅日志
第三章 失败模式
第11条(失败模式清单)
| # | 失败点 | 检测 | 回退 | 客户端感知 |
|---|---|---|---|---|
| F1 | PS 到 MC 转发(异步无失败回调) | 无检测;MC 注释确认无可靠回调 | 健康检查每 16 帧回收僵尸房间 | 无消息;请求 5 秒超时;推送静默丢失 |
| F2 | MC 到 GC 转发失败 | MC 侧转发返回失败 | 仅记错误日志;调用方忽略返回值 | 消息静默丢失 |
| F3 | GC 到 GS 转发失败 | GC 侧转发返回失败 | 回 ForwardFailed;跨中心重试或回错误码 | 回补成功时收到错误码,否则静默 |
| F4 | PS 断线且玩家操作途中 | MC 连接校验 | 回 3300017 并通知场景服崩溃、取消房 | 先收错误码,后收崩溃通知 |
| F5 | PS 静默断线 | MC 健康检查 | 逐房通知场景服崩溃并取消房 | 崩溃通知 |
| F6 | 场景服到 GC 崩溃通知失败 | 可用性过滤;调用返回失败 | 空房通知重试一次;崩溃通知不重试 | 不可达时冻结悬挂,等重连或重投 |
| F7 | GS 退款失败 | 退款返回失败 | 不确认;场景服持续重投至上限暂停,重连再触发,总上限外永久放弃 | 收不到回退确认;pending 保留供拉取 |
| F8 | 结算送达失败 | 场景服转发失败回调 | 存 pending 定时重投;资金另有自动结算兜底 | 正常收 EndGame;失败时无感知,重连恢复 |
| F9 | 崩溃结算 key 乱序 | GS 线性流水线比较 | 小于期望跳过并确认;大于期望只确认等待;等于执行回退 | 均收崩溃通知;大于期望不回退 |
| F10 | 客户端请求超时 | 请求定时器 | 回调 3300101 并发布错误事件;房间变更释放锁;断线退出全量取消 | 提示操作失败 |
| F11 | 跨中心 GC 不可达 | 连接检查与转发 | 带元数据时回补 RequestFailed;本地兜底失败仅日志 | 回补成功收错误码,否则静默 |
| F12 | 场景服自发现 PS 断开 | 验证定时器 | 升级为类型 6 崩溃 | 崩溃通知 |
| F13 | 无可用中心建房 | 选服返回空 | 建房回 3300004 | 场景服映射崩溃类型并补偿 |
| F14 | EndGame 房间索引缺失 | 房间查询为空 | 仅记日志返回,不回退不补偿 | 资金靠自动结算;无 S2C |
| F15 | 退房无场景服 | 场景服 ID 不大于 0 | 回 3300001 | 退房错误码 |
第四章 重复与乱序防护
第12条(防护清单)
| 机制 | 位置 | 说明 |
|---|---|---|
| 结算去重(客户端) | mahjong_client_core.lua |
相同房间已有结算结果且待显示时跳过重复 EndGame |
| 陈旧房间守卫 | 同上 | EndGame 先查房间归属;崩溃通知无此守卫 SUSPECT-2 |
| 座位号回归拒绝 | 同上 | 过期快照座位号倒退必须恢复旧值 |
| 倒计时单调递减 | 同上 | 换三张与定缺阶段拒绝倒计时回升 |
| 定时器锚定策略 | 同上 | 进行中与等待响应不重启定时器;换三张与定缺每次重启收敛 |
| 全局倒计时锚点 | 同上 | 仅广播非零值建立 |
| 请求超时取消 | 同上 | 本家行动到达取消对应请求超时定时器(BUG#30) |
| PK 审计 | 同上 | 仅无选项的他人通知才清本家 pending |
| 定缺幂等 | mahjong_play_server.lua |
已提交直接返回 |
| 建房幂等 | mahjong_match_center.lua |
已有活跃玩法服则忽略;等待装载则复用转发 |
| 崩溃验证去重 | mahjong_scene.lua |
验证中标志加冷却,防重复验证 |
| 崩溃 key 乱序 | mahjong_game_server.lua |
线性流水线:小于跳过、大于等待、等于执行 |
| 结算重投去重 | mahjong_scene.lua |
确认后删除 pending;重投更新下次重试时间 |
| 请求回调单槽 | mahjong_client_core.lua |
同协议新请求先关旧定时器与旧回调 |
第五章 模拟注入点
第13条(注入点)
| 注入场景 | mock 位置 | 注入方式 |
|---|---|---|
| PS 到客户端直达转发 | node_ps.lua 转发覆盖 |
拦截各 PS 侧协议直接投递客户端;延迟参数即乱序注入点 |
| PS 节点接收 MC 协议 | node_ps.lua 协议注册 |
解包后调真实全局函数;注入失败即替换为丢弃桩或抛错 |
| S2C 路由表 | node_ps.lua 初始化 |
路由项含延迟字段,即乱序注入点 |
| 玩家过滤 | node_ps.lua 投递覆盖 |
按 dwID 过滤;改条件可模拟目标玩家不在本机 |
| 通用网络层 | framework.lua 转发 stub |
全部经路由表与消息总线;无路由条目即静默丢弃 |
| 客户端节点 | node_client.lua |
客户端处理器自动收集,可断言收到与未收到 |
| 转发失败 | framework.lua |
清空路由条目或包装投递为丢弃 |
| 中心断开 | mahjong_match_center.lua |
stub 连接查询返回空,使转发返回失败 |
| 结算重投 | mahjong_scene.lua 重试节流 |
直接调用并推进下次重试时间;断言投递计数 |
| GS 不可达 | mahjong_game_center.lua 转发 |
玩家查询返回空且非远程,使转发返回失败 |
模拟局限:node_ps.lua 只仿真 PS 到客户端直达,未仿真 MC、GC、GS 中间节点;其余 PS 侧协议落入仅日志分支。场景服、GC、GS 链必须按 loader.lua 的导出方式另行装载模块后手工接线。
第六章 缺陷与缺口
第14条(SUSPECT 清单)
- SUSPECT-1(mock 缺陷)
node_ps.luaEndGame 广播取第 4 个参数(匹配类型)而非第 5 个(结算数据),客户端收到非表数据即丢弃,mock 下结算显示永不触发;测试套件必须先修此点或直接断言该缺陷。 - SUSPECT-2 崩溃通知无陈旧房间守卫、无去重:玩家进新房后旧房崩溃会误触发事件。
- SUSPECT-3 MC 四个 S2C 转发处理器忽略转发返回值:GC 断开时消息静默丢失且无补偿(F2)。
- SUSPECT-4 EndGame 主通道依赖 MC 到场景服一跳且无失败检测:客户端通知无兜底,只能重连拉取(F8)。
- SUSPECT-5 GS 崩溃未来 key 分支确认后不回退且无重试:退款可能被永久跳过。
- SUSPECT-6 结算投递目标中心无效即永久放弃:资金只能靠自动结算通道,若 PS 已死则悬挂。
- SUSPECT-7 空房关闭通知只重试一次:二次失败后冻结悬挂,无主动补偿。
- SUSPECT-8 客户端 EndGame 去重窗口仅覆盖待显示期间:显示消费后重投的 EndGame 会重复展示结算。
- SUSPECT-9 MC 建房先写索引后调用:PS 未收到请求时房间成为僵尸,回收依赖健康检查,窗口期玩家已分配无游戏。
- SUSPECT-10 请求回调每协议单槽:同协议并发请求后发覆盖先发,覆盖场景语义由业务层承担。
第七章 测试点
第15条(测试点清单,共 32 项)
- 正向 状态广播后客户端收到全量快照且含私有手牌。
- 正向 行动通知逐玩家送达:动作玩家带完整手牌,旁观带手牌数。
- 正向 本家行动到达后请求超时定时器被取消,无超时事件。
- F2 注入中心断开:转发返回失败、状态同步静默丢弃、无错误回包。
- F1 清空 PS 到 MC 路由:客户端无消息;请求 5 秒后回调超时并发布错误事件。
- F4 PS 断开且玩家操作:回 3300017 且触发崩溃通知与取消房。
- F4 错误码到达 GS:下发带错误码的 S2C。
- F5 健康检查发现断开:该服所有房间触发场景服崩溃与索引清理。
- F13 无可用玩法服建房:回 3300004。
- 幂等 取消房对不存在房间二次调用安全返回。
- 幂等 建房同房重复且游戏进行中:忽略。
- F12 崩溃处理器收到不匹配 PS ID:发起验证,超时后升级类型 6。
- F6 崩溃逐玩家通知且场景服 ID 被补全。
- F7 退款失败:不确认,pending 计数递增并重投。
- F9 key 小于期望:只确认并通知,不退款。
- F9 key 大于期望:只确认,不回退(SUSPECT-5)。
- 退款 房号不匹配:拒绝且零发放。
- 退款 冻结 token 不匹配:拒绝。
- 退款 发放失败:token 回退可重试。
- F8 转发失败:pending 建立,到期重发结算。
- F8 目标中心不可达:重试计数递增,不得每帧无限重试。
- F8 重试达上限:暂停;重连重新触发。
- F8 总重试超上限:pending 永久放弃并清理。
- F3 转发失败且成功匹配:回应答,游戏不被卡住。
- F3 转发失败跨中心重路由:带元数据转发。
- F3 无可用中心:回 3300017。
- F11 跨中心转发失败且带元数据:源中心收到回补。
- F10 请求超时且房间已变更:仍回调超时并发布错误事件。
- 去重 结算未显示期间重复到达:跳过;结算失败码触发错误事件。
- 乱序 倒计时增大与座位号倒退均被拒绝。
- SUSPECT-1 mock 结算广播投递匹配类型:断言客户端丢弃;先修后测。
- 重试 准备请求本地状态未就绪:200 毫秒后自动重试一次。