fix(framework): decodeFrame 改单层(服务器协议非双层)

真机联调发现:服务器实发单层 {app,route,rpc,data};旧客户端的'外层 data'是浏览器
MessageEvent(00_minhttp.js:266 ws.onmessage=config.onmessage),非协议层。框架传输层
已取 ev.data,decodeFrame 不应再剥一层。修正后 kick_server 等业务包能正确解析。
- decodeFrame 单层化 + 测试改单层帧
- 更正 docs/protocol/01 §3.2(双层→单层,附 MessageEvent 依据)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 20:25:36 +08:00
co-authored by Claude Opus 4.8
parent 5132fddc4e
commit 96c6c7c914
5 changed files with 65 additions and 63 deletions
+13 -11
View File
@@ -74,39 +74,41 @@ var ws = new WebSocket("ws://" + config.ipport); // 00_minhttp.js:258,例: w
- netType==0:`Net.ws_tcp.send(JSON.stringify(_msg))`(`09_Net.js:20`)。**单层**,直接发信封。
- netType==1:走 `Func.AjaxHttp2(GameData.Server, _msg, ...)`(`09_Net.js:24`),POST JSON。
### 3.2 服务器 → 客户端(接收)—— WebSocket 路径:双层包装
### 3.2 服务器 → 客户端(接收)—— WebSocket 路径:**单层**(「外层 data」是浏览器 MessageEvent,非协议层)
WebSocket 收包处理在 `12_Logic.js` 的 `this.onmessage`(定义于 **`12_Logic.js:119`**,路由分发在 **`12_Logic.js:258`**)。**收到的是双层结构**:
> **⚠️ 重要更正(2026-06-28 真机联调确认)**:早期本节误记为「双层 `{data:<inner>}`」。实测+源码核对确认:**服务器实发的协议帧是单层** `{route, rpc, data}`(实测还带 `app` 字段)。所谓"外层 `data`"是**浏览器 WebSocket `MessageEvent` 对象**,不是服务器的协议层。
依据 `00_minhttp.js:266` 的 `min_tcp()`:`ws.onmessage = config.onmessage;`——把**浏览器原生 `MessageEvent`** 直接交给 `12_Logic.js:119` 的 `this.onmessage(_msg)`。所以 `_msg` 是 `MessageEvent`,`_msg.data`(= `MessageEvent.data`)才是服务器实发帧(源码注释原文:「`msg.data` 才是服务器发过来的业务数据」`00_minhttp.js:265`)。`12_Logic.js` 里 `data=_msg.data`(137) 与 `_msg=_msg.data`(224) 读的是**同一个 `MessageEvent.data`**,只剥掉这一层浏览器事件壳,没有第二层。
```
原始帧(字符串) = JSON.stringify({
"data": <内层> // 外层只有 data 字段
})
<内层> 可能是字符串或对象,再解析后 = {
MessageEvent.data(= 服务器实发帧,单层)= {
"app": "youle", // 实测服务器回包带 app(与客户端发包同形)
"route": "...",
"rpc": "...",
"data": { ... } // 真正的业务数据
}
// 握手包:MessageEvent.data 为原始串 "@toconcon...";心跳包:{ "com": "@serverheartbeat" }
```
**新前端落地(YouleNexus)**:传输层(`CocosWebSocketTransport` / 联调用 `WsTransport`)已取 `ev.data`(= MessageEvent.data = 服务器单层帧)交给 `decodeFrame`,故 **`decodeFrame` 不得再剥一层 `.data`**——直接把 frame 当单层 `{route,rpc,data}` 解。(曾因沿用"双层"误解多剥一层,导致 `kick_server` 等业务包被误丢弃,真机联调暴露并已修正。)
完整解包顺序(含三道前置过滤,务必照此实现,否则会误处理旧连接包/错误回包/握手心跳):
1. **旧连接去重**:`if(GameData.TcpID != this.id) return;`(`12_Logic.js:122`)——每次重连 `GameData.TcpID++`,每个 WebSocket 实例闭包持有自己的 `this.id`,只有最新连接的包才被处理,旧连接残留包直接丢弃。
2. 若网络状态关闭 `if(!GameData.netWorkSate) return;`(`12_Logic.js:127`)。
3. `_msg = JSON.parse(原始帧)`(仅当是字符串,`12_Logic.js:133-136`)→ 取外层 `data = _msg.data`(`12_Logic.js:137`)。
3. `_msg`(= MessageEvent;若传输直接给字符串则 `JSON.parse`,`12_Logic.js:133-136`)→ 取 `data = _msg.data`(= 服务器实发帧,`12_Logic.js:137`)。
4. **特殊错误包**:若 `data === "webserve-服务器未工作"` → 进入重发登录分支后 `return`(详见 4 节)。
5. 若 `data` 是字符串:
- 若 `data.substr(0,9) === "@toconcon"` → **握手包,直接 `return` 忽略**(`12_Logic.js:176-179`)。
- 否则 `data = JSON.parse(data)`(`12_Logic.js:180`)。
- 否则 `data = JSON.parse(data)`(`12_Logic.js:180`)→ 得到单层 `{route,rpc,data}`。
6. **重置收包超时定时器**(仅 `!ConstVal.isGameHall` 时创建,见第 4 节)(`12_Logic.js:182-217`)。
7. 若 `data.com === "@serverheartbeat"` → **心跳包,直接 `return` 忽略,不回包**(`12_Logic.js:218-223`)。
8. 令 `_msg = _msg.data`(取内层),必要时再 `JSON.parse`(`12_Logic.js:224-229`)→ 得到 `{route, rpc, data}`。
8. 令 `_msg = _msg.data`(仍是同一个 `MessageEvent.data`),必要时再 `JSON.parse`(`12_Logic.js:224-229`)→ 得到 `{route, rpc, data}`。
9. **错误回包忽略**:`if(_msg.rpc == "submit_error") return;`(`12_Logic.js:235`)。
10. **登录态门控**(`isSendLoginState`):若 `GameData.isSendLoginState==true`(已发登录、等待 `player_login` 响应期间),则除 `player_login`(清门控)与 `kick_server` 外,**其它收包一律 `return` 丢弃**(`12_Logic.js:238-257`)。
11. 按 `_msg.route` 分发(见第 5 节,`12_Logic.js:258`)。
> 注:源码外层与内层都做了 `typeof == "string"` 判断后再 `JSON.parse`,是为兼容服务器有时发对象、有时发字符串。新前端实现时对每层都「是字符串就 parse」最稳妥。
> 注:源码对 `MessageEvent.data` 做了 `typeof == "string"` 判断后再 `JSON.parse`,是为兼容服务器有时发对象、有时发字符串(**同一层**的两种编码,非两层)。新前端「是字符串就 parse 一次」即可。
### 3.3 服务器 → 客户端 —— HTTP(netType==1) 路径:单层、无信封过滤