Files
youle_framework/server/docs/development-guide/03-数据收发与通信协议.md
T

7.7 KiB
Raw Blame History

03 · 数据收发与通信协议

本篇是日常手册:数据包长什么样、收包要做哪些固定步骤、三种发包方式怎么选、为什么"主动推送"是唯一可靠的下发通道,以及本项目最重要的一条——成败标志只认 data.success。


1. 数据包结构

所有前后端数据包统一四字段:

{
    app:   "youle",        // 应用名(游戏固定 "youle")
    route: "<你的游戏>",   // 路由名 = 模块 routename
    rpc:   "playCard",     // 方法名,决定调到 mod 的哪个函数 / 前端走哪个解析分支
    data:  { /* 业务数据 */ }
}
  • app/route/rpc 三字段驱动三层路由(见 01)。
  • data 里必含平台参数:agentid、playerid、gameid、roomcode、seat 等;数值参数收包时要 parseInt。
  • 一包多信息:一个响应包应携带本次状态变更的完整信息(出了什么牌、轮到谁、各家剩余、倒计时、比分……),减少往返。

2. 收包处理的固定步骤

每个 RPC handler 开头都是同一套"安检流程",不可省略、不可简化:

mod_<游戏>.playCard = function(pack) {
    // 1) 提取参数(数值 parseInt)
    var agentid  = pack.data.agentid;
    var playerid = parseInt(pack.data.playerid);
    var gameid   = pack.data.gameid;
    var roomcode = parseInt(pack.data.roomcode);
    var seat     = parseInt(pack.data.seat);

    // 2) 校验玩家与房间(必做)——失败静默 return
    var o_room = mod_<游戏>.import.check_player(
        agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
    if (!o_room) return;

    // 3) 取桌对象与对局状态
    var o_desk = o_room.o_desk;
    if (!o_desk) return;

    // 4) 业务校验(游戏阶段?轮到该座?操作合法?)——任一不过即 return
    // 5) 执行业务(委托权威模块)
    // 6) 构建并【主动推送】结果(见下文)
};
  • check_player 是强制安检:它校验座位、连接、身份,返回 o_room 或 null。返回 null 一律直接 return。
  • 校验失败静默:不要回 "你作弊了" 之类提示,直接 return,避免给作弊者反馈。
  • 调试记录(若框架提供 o_desk.debug.save_receivepack 等)在关键节点调用,便于复盘。

3. 三种发包方式

方式 接口 用于
点对点 o_room.method.sendpack_toseat(msg, seat) 只发给某个座位(个人状态、定向数据)
广播其他人 o_room.method.sendpack_toother(msg, seat) 发给除 seat 外所有人(seat=-1 即全发)
差异化广播 逐座位组 msg 后各自 sendpack_toseat 每个玩家看到的内容不同(如手牌只发本人)

平台发包内部会从 seatlist[seat] 上取 conmode/fromid 填入包,再交给底层 SendPack 按 TCP/HTTP 下发——这两个连接字段不需要你手工设置,只要座位上有在线玩家即可。

差异化广播:棋牌最常用

牌类游戏里"同一动作、各家可见不同",所以广播时为每个座位深拷贝一份基础数据再定制:

for (var seat = 0; seat < o_room.seatlist.length; seat++) {
    if (!o_room.seatlist[seat]) continue;            // 空座跳过
    var msg = {
        app: "youle", route: "<游戏>", rpc: "playCard",
        data: deepCopy(baseData)                     // 公共信息
    };
    if (seat === actionSeat) {
        msg.data.handCards = hands[seat];            // 仅本人可见手牌
    } else {
        msg.data.handCards = [];                     // 他人看不到
    }
    o_room.method.sendpack_toseat(msg, seat);
}

敏感信息(手牌、暗牌、未公开的判定)只发给该看到的人,这是服务器权威的一部分。


4. 主动推送是唯一可靠的下发通道

前端收包的唯一通道是服务端主动推送(sendpack_toseat / toother → 底层 SendPack)。

而 RPC 方法的 return 值不可靠下发前端:框架虽会对 DoPack 的返回值做一次回发,但它只回当前请求连接、不按座位定向,浏览器/友乐链路下不能当作状态推送通道(机制见 01 §4)。

推论:凡需要让前端看到的结果(包括操作的成功/失败),都必须放进主动推送的 data 里。


5. 成败标志协议(本项目最重要的一条)

这条覆盖并修正了早期文档里"用 HTTP 风格 status 码判成败"的写法。以本协议为准。

规则

  1. 成败唯一权威字段是 data.success(boolean)。前端一律 if (!data.success) 判断操作成败。
  2. 禁止用 data.status(如 status === 200)或 data.code 判成败。status/code 只能作展示/日志用的细分信息,不参与成败裁定。
  3. 主动推送必须自带 success:因为 return 不下发前端,凡有成败语义的推送包,其 data 必须显式带 success: true/false,不能只放 status。
  4. 禁止双轨/兼容兜底:前端不得写 status !== 200 && !success 之类的 status 兜底;新增/改动的推送一律补齐 success,前端一律只认 success。

正反例

// ❌ 错误:推送只带 status,前端读 data.success 恒 undefined → 误判失败
o_room.method.sendpack_toseat({
    app:"youle", route:"<游戏>", rpc:"setHostingState",
    data: { status: 200, hosting: true }      // 少了 success
}, seat);

// ✅ 正确:成败语义放 success,status 仅作细分
o_room.method.sendpack_toseat({
    app:"youle", route:"<游戏>", rpc:"setHostingState",
    data: { success: true, status: 200, hosting: true }
}, seat);
// 前端:只认 success
if (!data.success) { /* 失败处理 */ return; }
// data.status / data.code 仅用于展示或日志细分

典型事故:托管状态推送 { status: 200, ... } 不带 success,前端读 data.success 恒为 undefined,于是"已进入托管却报失败"。根因就是违反了本协议第 3 条。


6. 前后端对接的固定接缝

下发结构变了,对应的前端解析接口必须同步检查(前端平台代码与受限接口文件不可随意改,见 04):

场景 服务端产出 前端接收接口
开战 export.makewar 的返回包 Game_Modify.StartWar(makewar 返回值)
重连/中途加入 export.get_deskinfo 的返回 Game_Modify.Reconnect(get_deskinfo 返回值)
对局操作 RPC handler 的主动推送(按 rpc 区分) 前端对应 rpc 的解析分支

改服务端下发 = 同步核对前端解析,否则前端按旧结构解析必然出错。


7. 服务端代替玩家操作时的透明性

服务端自动替玩家执行的操作(如 AI 托管出牌/碰杠胡),必须复用真人操作的同一套数据包与广播链路:产生的包结构、rpc、data 与真人操作完全一致,前端无需也禁止为它单开一套接收/解析分支。唯一区别只在触发源(前端请求 → 服务端决策),其后的数据组织、协议、广播路径不变。(详见 04 的对应红线。)


8. 小结

  • 包结构恒为 {app, route, rpc, data};收包先 check_player,失败静默 return。
  • 三种发包方式按"谁该看到什么"选;差异化广播逐座位定制,敏感信息只发本人。
  • 主动推送是唯一可靠下发通道,return 不算。
  • 成败只认 data.success,推送必自带 success,禁止 status/code 判成败与兼容兜底。
  • 改下发结构必同步核对前端 StartWar/Reconnect/对应 rpc 解析。

下一篇 04-开发规范与红线 汇总所有必须遵守的工程纪律。