test(net): TC-NET-204 定案 Trap 07——disconnect→close 不卡死,真因是 bit3
Trap 07(2026-07-19 写成"2025-07")断言"disconnect→close 让 Socket 卡
FIN_WAIT_2 数分钟、因为 close 不发 RST"。新增定案用例 TC-NET-204(Phase 2
硬件层,同一 Socket、同一 PC 回显、FUN_PARA=0x08,三模式 × 5 轮 = 45 项判定
全过)做受控对照,实测推翻其机制说法、保留其做法:
A close → 回 CLOSED 0/0/150/0/0 ms,重开 5/5 OK
B disconnect → close → 全部 +0 ms,重开 5/5 OK("卡死"未出现)
C disconnect(不 close)→ 10 s 内从未回 CLOSED,byte0 恒 0x05、byte1 0x00;
直接 open → 0x20 FAIL(5/5),补 close 后 +0 ms
三条推翻:① FIN_WAIT_2(06H) 全程未出现(15 轮 × 9 采样点),实测过渡态只有
TIME_WAIT(0AH);② 手册无 RST 语义(§5.35 只说 close 前自动断链),且主动发
FIN 先进 FIN_WAIT_1 才进 FIN_WAIT_2,原说法跳拍;③ 观测越过了 byte1 的语义
边界——§5.22 byte0 只有 00H/05H,官方 SDK 注释明确 byte1 仅在 byte0=OPEN 时
有意义,而当年代码判据用的是正确的 byte0,break 后照样打印 byte1,于是"已
CLOSED 下的残留读数"被读成"卡在某个 TCP 态"。
真因:FUN_PARA bit3 SOCKET_CLOSE=1(§5.52 ②)下产生 DISCONNECT 后芯片不关
Socket、不清缓冲,Socket 停在 0x05(OPEN)/0x00(TCP_CLOSED),此时 open_socket
必返 0x20 FAIL——即历史那条 Phase 2 open_socket: 0x20 / socket 未完全关闭。
拉回 CLOSED 的动作是 close,与 RST 无关。
git 佐证:ch395f_tcp_disconnect() 自首个提交起只有定义、零调用点(含全部分支
与 11 个悬空提交),"disconnect→close" 从未进入提交代码;当时驱动已轮询
GET_CMD_STATUS(4 s 上限),也不是"命令没等完成"。
做法保留,理由换成三条有支撑的:§5.35 close 已自动断链故 disconnect 多余;
disconnect 制造的 DISCONNECT 事件在 bit3=1 下须我方消费/预清(Trap 14/17/22);
它把芯片推进 closing 过渡态、重开需沉降(Trap 09)。本轮 15 轮重开均未观察到
残留 DISCONNECT(0x10)——close 顺带清掉锁存,这才是"直接 close"安全的原因。
另消解 Trap 17 与 Trap 07 的旧矛盾:实测支持 Trap 17(close 走 FIN teardown、
0~150 ms 可见 TIME_WAIT),不支持 RST 说法。
文档:Trap_Records.md 加 Trap 07「定案更正」整节 + 改写「驱动关键点 → TCP 关闭
重连」速查清单(删 RST 说法、补 byte0/byte1 口径与 0x20 陷阱);Test_Guide.md
新增 TC-NET-204 用例表(前置含 tcp_server --timeout 900 这条坑:首连后连接
间隔 >60 s 服务器会自行退出)。
验证:测试构建 0 错 0 警、Phase 2 74/75(唯一 FAIL 是 TC-203 环境守卫,PC 服务
未及时关闭);宏已还原,产品构建 Code=196788 与 HEAD 一致(用例全在测试宏内、
产品零足迹),@test 闭环 PASS(戳匹配 + 回读无差异 + 锚点 x1 + 590 ms)。
pre-commit 全绿(test-cppcheck / doc-links / no-mojibake 均 Passed)。
This commit is contained in:
@@ -154,11 +154,35 @@ Phase 2 测试,2025-07
|
||||
`disconnect` 发送 FIN 将 Socket 推入 `FIN_WAIT_2`,此后 `close` 不再发 RST,必须等远端发 FIN 才能关闭。
|
||||
|
||||
### 解决方案
|
||||
直接调用 `ch395f_close_socket()`,在 ESTABLISHED 或 CLOSE_WAIT 下会发 RST 立即终止。
|
||||
关闭后轮询 `ch395f_get_socket_status()` 等待 `sock=0x00` 再 `open_socket`。
|
||||
直接调用 `ch395f_close_socket()`(**做法保留**,原"发 RST"的说法见下方定案更正)。
|
||||
关闭后轮询 `ch395f_get_socket_status()` 的**第 1 字节**等待 `sock=0x00` 再 `open_socket`。
|
||||
|
||||
### 定案更正(2026-10-11):机制说法作废,实测 `disconnect → close` 不卡死
|
||||
|
||||
新增定案用例 **TC-NET-204**(`test/board/ch395f_test_task.c`,用例表见 `docs/测试说明/CH395F_Test_Guide.md`):同一 Socket、同一 PC 回显服务、`FUN_PARA=0x08`、硬件层独占,三模式各 5 轮(45 项判定全过):
|
||||
|
||||
| 模式 | 关闭序列 | 回到 `SOCKET_CLOSED` 耗时(5 轮) | 重开+重连+回显 |
|
||||
|---|---|---|---|
|
||||
| A | `close` | 0 / 0 / 150 / 0 / 0 ms | 5/5 OK |
|
||||
| B | `disconnect → close` | **0 / 0 / 0 / 0 / 0 ms** | 5/5 OK |
|
||||
| C | `disconnect`(不 close) | **10 s 内从未回到**,byte0 恒 `0x05`、byte1 `0x00` | 直接 `open` → **`0x20` FAIL**(5/5);补 `close` 后 +0 ms 回 CLOSED,重开 5/5 OK |
|
||||
|
||||
**三条推翻原根因**:
|
||||
|
||||
1. **`FIN_WAIT_2`(0x06) 全程未出现**(15 轮 × 9 个采样点)。实测过渡态只有 `TIME_WAIT`(0x0A)(A 模式一轮在 +0/+50/+100 ms 可见,+150 ms 回 CLOSED)与 `TCP_CLOSED`(0x00)。
|
||||
2. **手册无 RST 语义**:§5.35 只说"关闭 Socket 前 CH395 会自动断开 TCP 连接",全文 RST 仅见于错误码 `CH395_ERR_RST` 与 RST/RSTI 引脚;且主动发 FIN 先进 `FIN_WAIT_1`(05H)、收到 ACK 才进 `FIN_WAIT_2`(06H)——"disconnect 推入 FIN_WAIT_2"本身就跳了一拍。
|
||||
3. **观测口径错在把 byte1 当独立事实**:§5.22 第 1 字节只有 `00H/05H` 两个取值,第 2 字节才是 TCP 态;官方 SDK `CH395CMDGetSocketStatus()` 注释明确"Second byte — TCP state, **meaningful only if** TCP mode **and the first byte is open**"。当年 Phase 2 收尾代码的判据用的是**正确的 byte0**(`st[0]==CH395F_SOCKET_CLOSED`),break 之后照样打印 byte1 → 一个"已 CLOSED 状态下的残留 TCP 态读数"被读成了"卡在某个 TCP 态"。
|
||||
|
||||
**真正的原因**(C 模式 5/5 复现):`FUN_PARA` bit3 `SOCKET_CLOSE=1`(§5.52 ②,`0x08`/`0x09` 都置了)下,产生 DISCONNECT 后**芯片不关 Socket、不清缓冲**,Socket 长期停在 `byte0=0x05(OPEN) / byte1=0x00(TCP_CLOSED)`;此时直接 `open_socket` 必返 **`0x20` FAIL**——正是 2026-07-19 测试指南问题表里那条 `Phase 2 open_socket: 0x20 (FAIL) / socket 未完全关闭`。**把 Socket 拉回 CLOSED 的动作是 `close`,与 RST 无关。**
|
||||
|
||||
**git 侧佐证**:`ch395f_tcp_disconnect()` 自首个提交起**只有定义、零调用点**(含全部分支与 11 个悬空提交),"disconnect→close" 序列从未进入任何提交代码;本条"发现时间 2025-07"也与仓库不符(仓库首个提交 `b1d05091` = 2026-06-05,Phase 2 对应 2026-07-19 的 `17109d9b`→`7a8a97f4`→`a08d26bc`)。当时驱动已在轮询 `GET_CMD_STATUS`(4 s 上限),故也不是"命令没等完成"。
|
||||
|
||||
**做法为何仍然对**(换成三条有支撑的理由):① §5.35 `close` 已自动断链,`disconnect` 纯属多余;② `disconnect` 会**制造**一个 DISCONNECT 事件,bit3=1 下芯片不自动收尾,该事件必须被消费/预清,否则污染下一条连接(Trap 14 / 17 / 22 实证);③ 它把芯片推进 closing 过渡态,重开需要沉降(Trap 09)。本轮 15 轮重开均未观察到残留 `DISCONNECT`(0x10)——`close` 顺带清掉了锁存,这正是"直接 close"安全的原因。
|
||||
|
||||
**与 Trap 17 的张力已消解**:Trap 17 写"close 后芯片要等对端 FIN/ACK 才把 Socket 置 CLOSED(耗时数毫秒)",与本条原"close 发 RST 瞬间回 CLOSED"矛盾;实测支持 Trap 17 一侧(A 模式可见 `TIME_WAIT` 过渡、0~150 ms),不支持 RST 说法。
|
||||
|
||||
### 发现时间
|
||||
Phase 2 TCP Client 测试,2025-07
|
||||
Phase 2 TCP Client 测试,2025-07(**日期有误**,实为 2026-07-19;定案更正与 TC-NET-204 实测 2026-10-11)
|
||||
|
||||
---
|
||||
|
||||
@@ -205,10 +229,11 @@ CH395F 的 RECV 中断是电平触发的——只要接收缓冲区有数据就
|
||||
- 不分配会导致发送数据为固定垃圾内容(`0x0028` 填充)、接收缓冲区无法存储数据
|
||||
|
||||
### TCP 关闭重连
|
||||
- **直接调用 `ch395f_close_socket()`,不要先调 `ch395f_tcp_disconnect()`**
|
||||
- ❌ `disconnect → close`:disconnect 发 FIN 推入 FIN_WAIT_2,close 不再发 RST,Socket 卡住数分钟
|
||||
- ✅ `close` 直接:在 ESTABLISHED 或 CLOSE_WAIT 下 close 发 RST 立即终止,瞬间回到 CLOSED
|
||||
- 关闭后需轮询 `ch395f_get_socket_status()` 等待 `sock=0x00` 再 `open_socket`
|
||||
- **直接调用 `ch395f_close_socket()`,不要先调 `ch395f_tcp_disconnect()`**(做法不变;机理口径已于 2026-10-11 由 TC-NET-204 实测更正,见 Trap 07「定案更正」)
|
||||
- ✅ `close` 直接:§5.35 关闭前芯片**自动断链**,无需先 disconnect;实测 0~150 ms 回到 `SOCKET_CLOSED`,过渡态可见 `TIME_WAIT`(0x0A)——**不是 RST**(手册无 RST 语义)
|
||||
- ❌ 只 `disconnect` 不 `close`:`FUN_PARA` bit3=1(§5.52 ②)下芯片**不自动关 Socket**,Socket 长期停在 `0x05(OPEN)/0x00(TCP_CLOSED)`,再 `open_socket` 必返 **`0x20` FAIL**
|
||||
- ❌ `disconnect → close` 本身**不卡死**(实测 5/5 全部 +0 ms 回 CLOSED),只是多余:它制造一个 DISCONNECT 事件,而 bit3=1 下该事件须由我方消费/预清(Trap 14 / 17 / 22)
|
||||
- 关闭后需轮询 `ch395f_get_socket_status()` 的**第 1 字节**等待 `sock=0x00` 再 `open_socket`;**第 2 字节(TCP 态)仅在 byte0=OPEN 时有意义**,别拿它当"卡住"的判据
|
||||
|
||||
### SOCK_TIMEOUT 处理
|
||||
- 长时间无数据时可能触发 `SINT_STAT_SOCK_TIMEOUT`,**不应视为致命错误**——记录日志后继续操作即可,不要因此关闭 Socket
|
||||
|
||||
Reference in New Issue
Block a user