Battle.net 大厅前缺少 1.13c SID_NULL 保活,闲置后连接可能被静默关闭 #541

Closed
opened 2026-10-01 12:41:39 +00:00 by troytt · 0 comments
Owner

现象 / 风险

客户端登录 Battle.net 后,如果停在大厅前的界面(选人、建人、游戏列表、频道聊天)一段时间不操作,BNCS 连接(以及可能的 MCP realm 连接)没有任何上行流量。1.13c 原版客户端会定期发送 SID_NULL(BNCS 0x00)保活,我们的客户端从来不发。

一旦服务器端或 WSS 桥(nginx → bnetd/d2cs)有空闲超时,连接就会被静默关闭。用户下一次操作(比如点创建角色)才会发现已经断线。之前遇到的建人失败("MCP realm session is not connected")可能就有这个原因。

自 309738c 起,掉线已经会统一回到登录页并显示原因(fix/lobby-disconnect),但还是会被踢回去,需要从根上补保活。

代码现状(main 309738c)

  • src/netproto/bncs/packets.ts:118 已经有 encodeBncsNull()(0x00,空 payload),但除了测试 tests/e2e-netproto/tier5-adversarial-coverage.test.ts:561 以外,没有任何地方调用它。
  • src/netproto/bncs/session.ts:140-145 只会被动回应服务器发来的 SID_PING(0x25),没有任何定时器主动发保活包。
  • 收到服务器发来的 SID_NULL 时只做了解码(packets.ts:294),没有其他处理。
  • MCP(src/netproto/mcp/session.ts)同样没有空闲保活。
  • D2GS 游戏内有 0x6D ping,不在本 issue 范围内。

待确认(不要猜常数)

  1. 1.13c 的发送间隔和条件:从 /usr/local/google/home/taodao/d2-data/Bnclient.dll 找到 SID_NULL 的发送点(构造 FF 00 04 00 的位置)和驱动它的定时器常数。BNETDocs 记载的是每 8 分钟一次左右,但以 DLL 为准。同时确认在 MCP 连接建立后(选人界面)BNCS 是否还继续发。
  2. MCP 是否有对应的保活:检查 D2MCPClient.dll / D2Client.dll 有没有 MCP 层的周期包。没有就不要发明。
  3. 服务器端空闲超时(不改服务器配置,只读):
    • bnetd / d2cs 的空闲断开设置;
    • WSS 桥 nginx 的 proxy_read_timeout(默认 60s)。如果没调过,空闲 60 秒就会被关,这比 bnetd 的超时更可能先触发。这时只靠 1.13c 的 8 分钟间隔是不够的,需要另行评估(例如 WebSocket ping 帧,或在桥上调长超时),并单独记录这是 web 桥带来的偏差,不属于 1.13c 行为。
  4. 实测:用 bot(不要用 d2webbot1 和 troytt)登录后停在选人界面,分别闲置 2、5、10 分钟,记录哪条连接在什么时候被关、关闭码和原因,抓包存成 .d2cap。

期望修复

  • 按第 1 条确认的常数,在 BNCS 会话进入已登录状态后定时发送 SID_NULL;会话关闭时清理定时器,不能泄漏。
  • 如果第 2 条确认 MCP 也有保活,同样实现。
  • 如果第 3 条确认桥的超时比 1.13c 间隔短,按讨论结果处理,不要静默加大发包频率。
  • 测试:用 fake clock 断言发送间隔和停止条件;用 fake server 断言闲置不会触发 session lost;再做一次实网闲置测试(≥ 10 分钟不掉线)。

相关

  • fix/lobby-disconnect(309738c):掉线统一回到登录页
  • 建人失败截图那次排查时,SID_LOGONRESPONSE2 在服务器端曾出现短暂超时(与本 issue 无关,另行观察)
## 现象 / 风险 客户端登录 Battle.net 后,如果停在大厅前的界面(选人、建人、游戏列表、频道聊天)一段时间不操作,BNCS 连接(以及可能的 MCP realm 连接)没有任何上行流量。1.13c 原版客户端会定期发送 `SID_NULL`(BNCS `0x00`)保活,我们的客户端从来不发。 一旦服务器端或 WSS 桥(nginx → bnetd/d2cs)有空闲超时,连接就会被静默关闭。用户下一次操作(比如点创建角色)才会发现已经断线。之前遇到的建人失败("MCP realm session is not connected")可能就有这个原因。 > 自 `309738c` 起,掉线已经会统一回到登录页并显示原因(`fix/lobby-disconnect`),但还是会被踢回去,需要从根上补保活。 ## 代码现状(main `309738c`) - `src/netproto/bncs/packets.ts:118` 已经有 `encodeBncsNull()`(`0x00`,空 payload),但除了测试 `tests/e2e-netproto/tier5-adversarial-coverage.test.ts:561` 以外,**没有任何地方调用它**。 - `src/netproto/bncs/session.ts:140-145` 只会被动回应服务器发来的 `SID_PING`(`0x25`),没有任何定时器主动发保活包。 - 收到服务器发来的 `SID_NULL` 时只做了解码(`packets.ts:294`),没有其他处理。 - MCP(`src/netproto/mcp/session.ts`)同样没有空闲保活。 - D2GS 游戏内有 `0x6D` ping,不在本 issue 范围内。 ## 待确认(不要猜常数) 1. **1.13c 的发送间隔和条件**:从 `/usr/local/google/home/taodao/d2-data/Bnclient.dll` 找到 `SID_NULL` 的发送点(构造 `FF 00 04 00` 的位置)和驱动它的定时器常数。BNETDocs 记载的是每 8 分钟一次左右,但以 DLL 为准。同时确认在 MCP 连接建立后(选人界面)BNCS 是否还继续发。 2. **MCP 是否有对应的保活**:检查 `D2MCPClient.dll` / `D2Client.dll` 有没有 MCP 层的周期包。没有就不要发明。 3. **服务器端空闲超时**(不改服务器配置,只读): - bnetd / d2cs 的空闲断开设置; - WSS 桥 nginx 的 `proxy_read_timeout`(默认 60s)。如果没调过,空闲 60 秒就会被关,这比 bnetd 的超时更可能先触发。这时只靠 1.13c 的 8 分钟间隔是不够的,需要另行评估(例如 WebSocket ping 帧,或在桥上调长超时),并单独记录这是 web 桥带来的偏差,不属于 1.13c 行为。 4. **实测**:用 bot(不要用 d2webbot1 和 troytt)登录后停在选人界面,分别闲置 2、5、10 分钟,记录哪条连接在什么时候被关、关闭码和原因,抓包存成 `.d2cap`。 ## 期望修复 - 按第 1 条确认的常数,在 BNCS 会话进入已登录状态后定时发送 `SID_NULL`;会话关闭时清理定时器,不能泄漏。 - 如果第 2 条确认 MCP 也有保活,同样实现。 - 如果第 3 条确认桥的超时比 1.13c 间隔短,按讨论结果处理,不要静默加大发包频率。 - 测试:用 fake clock 断言发送间隔和停止条件;用 fake server 断言闲置不会触发 session lost;再做一次实网闲置测试(≥ 10 分钟不掉线)。 ## 相关 - `fix/lobby-disconnect`(`309738c`):掉线统一回到登录页 - 建人失败截图那次排查时,`SID_LOGONRESPONSE2` 在服务器端曾出现短暂超时(与本 issue 无关,另行观察)
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#541
No description provided.