docs(N34): 阶段3 文档同步——reconciler 合并口径+net_drop_conn+Trap 补充

- BSD_Socket_API_使用指南:五层自愈→四层(合一 reconciler 形态①~⑤+U11,
  分级去抖②1/③④⑤2/①4轮+1s冷却);新增 net_drop_conn 小节(拆会话保
  监听/幂等/EOF 不得调);变更记录追加 2026-10-13 行
- BSD_Socket_API_函数调用关系:pump 节点删除、net_drop_conn 链路新增、
  驱动接口表补 sock_reset/cmd_done/缓存行、常量清单更正
- 网络Socket监听生命周期设计说明:新增 §10.10 合并说明,历史文本保留+注记
- Net层阻塞模型架构设计:主循环 3 tick 口径 + 附录 B 追加
- RAM使用分析与优化方案:netTask 链尾改 retry_tick→relisten(1944 B 不变)
- CH395F_Trap_Records:Trap 07 补充(close 无 RST:对端内核存活不 close
  → FIN_WAIT_2 长尾,兜底 E13);Trap 09 补充(LISTEN 态二段关闭)
- 实施方案 §5:项1/2/3/5 ✅、项4 受 E6 暂缓、§5.1 E-02 收口、§5.2 延后项
- 实验方案:E-02 收口台架小节(FIN_WAIT_2/TIME_WAIT ~150ms/双 PASS)
- N34 README 裁决行更正;Test_Guide TC-NET-1101 pump→reconciler 形态③④

历史变更记录与历史事故描述一律原文保留、只追加;check_doc_links
--sections-strict 对改动文件 0 问题;乱码 0、无 BOM 变化。
This commit is contained in:
2026-10-13 15:10:54 +08:00
parent 6394853fef
commit f51c0833ae
9 changed files with 246 additions and 89 deletions
@@ -112,7 +112,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
| 契约要求 | 对应缺口 | 收口手段 |
|---|---|---|
| 「会话结束」必须被**谁都能看见**,不能只在边沿那一刻 | EOF 边沿化:抢先 relisten → recv 永久 `WOULDBLOCK`(§2.3) | 不变量 I:`net_sock_t.session_dead` 粘滞电平,`net_recv_locked` DONTWAIT 判据改 `state==CLOSED \|\| session_dead`(P1) |
| 关闭 Socket 必须**一定有人做**、且只做一次 | 重建所有者 12 个 + U12 的 LISTEN 失败不回 close | 不变量 II:`net_listener_pump` 单点重建 + 补 U12 的 close 对称(P2) |
| 关闭 Socket 必须**一定有人做**、且只做一次 | 重建所有者 12 个 + U12 的 LISTEN 失败不回 close | 不变量 II:`net_listener_pump` 单点重建 + 补 U12 的 close 对称(P2)(N34 阶段 3:pump 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick——不变量 II 不变,见 §10.10) |
| 关闭姿势必须正确 | Trap 14/17:陈旧 DISCONNECT 锁存秒断下一条连接 | `net_close_locked` 既有「close → 等 `CLOSED` → 排空本 Socket 中断」,是 bit3=1 下唯一正确的关闭序列 |
| 异常释放须先排空残渣再关 | Trap 23:~2KB RX 残渣被下条连接当自己的数据读走 | `net_relisten_locked` 隔离期(400 ms)持续排空 RX + 主动 close + 等 `CLOSED/TCP_CLOSED` |
@@ -149,7 +149,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
- **不变量 I(电平化)**:「这条会话已结束」必须是**可重复查询、幂等**的状态,
而不是"恰好在那一刻观察到才算"。谁先看到不重要,**谁都饿不着**。
- **不变量 II(单一所有者)**:监听器的重建**只有一个决定者**(net 层的 listener pump)。
其它所有位置只**置事实**,不**做动作**。
其它所有位置只**置事实**,不**做动作**。(N34 阶段 3 起该决定者 = `net_reconcile_tick` 形态③④,pump 已并入,见 §10.10)
两条不变量成立后,"谁重建""会不会错过"这两类问题在结构上消失,
对账④从"唯一救命稻草"降级为"事件丢失时的补漏"(Trap 22 实证事件会丢,故对账**必须保留**)。
@@ -185,7 +185,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
### P2 单一所有者(中改,消灭多所有者)—— P2a ✅ 2026-10-12 / P2b 建议暂缓
1. 新增 `net_listener_pump()`(100 ms 周期),接管**周期性**重建,是唯一做周期性
`net_relisten_try` 的地方。✅ 已落地
`net_relisten_try` 的地方。✅ 已落地(N34 阶段 3 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick,见 §10.10)
2. ⚠ **原第 2 条已被实测推翻**:原计划「删除 `net_accept_locked` / `net_recv_locked` /
`net_pending_tick` 三处即时重建」。实测删掉会把 CLOSED 窗口从 ~0 拉长到 ~100 ms,
调用方高频空转 `net_accept`(RPC 无上界)→ uiTask 最坏单轮 172 ms → 1034 ms + `[WD] task stall`。
@@ -333,6 +333,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
**P2a(✅ 2026-10-12 落地,提交 `7cb5af0`)**
- [x] 新增 `net_listener_pump()`(100 ms 周期 + 去抖 + 冷却),接管**周期性**重建,
覆盖:① LISTENING 失位 ② CLOSED 不在 LISTEN ③ CLOSED 但芯片**正在** LISTEN(U11)
(N34 阶段 3 已并入 `net_reconcile_tick` 形态③④/U11,单一 100ms tick,见 §10.10)
- [x] 删对账③④ 及其孤儿态认领门槛(对账只剩①②;认领收窄为 ESTABLISHED/LISTENING)
- [x] 补 U11:本层 CLOSED 而芯片正在 LISTEN → **只做状态重同步**(含 `last_peer_port`
转登 + `lost_conn_seen` 清零,同 `net_resync_listen`),不发任何芯片命令
@@ -346,6 +347,9 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
同样存在(P1 基线同条件 53 条),忙等的危害是**理论性**的;而 `net_close` 是每次会话
结束都走的热路径。按 Trap 25「不留未证明的复杂度」,为一个未发生的故障去动热路径,
性价比存疑 —— 建议等有 `task stall` 与忙等的**因果证据**后再做。
(**N34 阶段 3 已实施 2026-10-13**:不是"为忙等而改",而是驱动 `sock_reset` 原语落地后
顺路收口——`net_sock_reset_sync` 确定性排渣替代盲等(GRACEFUL 100ms 底 / 600ms 封顶),
并删除 `net_close_locked` / `net_connect_locked` 的全局中断读清(F-NET-7),见 §10.10)
> ⚠ **P2a 与设计文档原清单的关键差异(实测推翻原计划,务必据实执行)**:
> 原清单第 2 条要求把 `net_recv_locked` / `net_pending_tick` / `net_accept_locked`
@@ -370,7 +374,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
> **动机**:真机 net 层 `net_socket.c` 4126 行,而模拟器做同样的事只有 807 行(5 倍);
> `relisten` 一词在文件里出现 110 次。用户诉求 = **高内聚低耦合**。
> P2a 把「周期性重建」收敛到 `net_listener_pump`(所有者 7→5)之后,搬移边界才**第一次清晰**——
> 这正是 P2a 相对"直接抽模块"的价值:先收敛,再搬移。
> 这正是 P2a 相对"直接抽模块"的价值:先收敛,再搬移。(N34 阶段 3 起 pump 已并入 `net_reconcile_tick` 形态③④,见 §10.10)
>
> 本节只定**边界与接口**,不含任何代码;执行须过 §10.6 的验证门。
@@ -397,6 +401,8 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
| 常量 | **8** | `NET_RELISTEN_{QUARANTINE,GRACEFUL,SETTLE}_MS` / `NET_RELISTEN_SETTLE_{QUARANTINE,GRACEFUL}` / `NET_RELISTEN_{RETRY,DEADLINE}_MS` / `NET_LISTENER_PUMP_PERIOD_MS` |
| **搬移量小计** | **≈470 行** | 函数块 414 + 常量/状态注释块 55 |
> ⚠ **N34 阶段 3(2026-10-13)追注**:上表是 2026-10-12 搬移时的历史清点,原文保留。此后 `net_listener_pump` 已**删除**(重建决策并入 `net_socket.c` 的 `net_reconcile_tick` 形态③④/U11,单一 100ms tick),`NET_LISTENER_PUMP_PERIOD_MS` 等 pump 常量同删、新增 `NET_RECONCILE_PERIOD_MS` / `NET_RECON_STABLE_*`——见 §10.10。
**跨边界引用点**(这个数字决定接口面大小,是本设计的核心依据):
| 引用 | 处数 | 位置 |
@@ -416,7 +422,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
| 搬入 | 理由 |
|---|---|
| `net_relisten_locked` / `net_relisten_try` / `net_relisten_retry_tick` / `net_relisten_clear` / `net_relisten_done` | 「维持 fd 处于 LISTEN 态」的全部机制 |
| `net_listener_pump` | 唯一的周期性重建决策者(P2a 成果) |
| `net_listener_pump` | 唯一的周期性重建决策者(P2a 成果)(N34 阶段 3 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick,见 §10.10) |
| `net_drain_chip_rx` | 重建前的排空(只被 relisten 调用) |
| 7 个 `s_relisten_*` + 8 个 `NET_RELISTEN_*` / `PUMP_PERIOD` 常量 | 状态与策略私有化 |
@@ -429,7 +435,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
| `net_lost_connect_probe` | 做的是"补 accept"(连接建立),不是重建监听 |
| `net_listener_armed` | **双端公开 API**(sim 也实现);纯 RAM 查询,很薄,留在原处避免 sim 缺符号 |
| ~~`net_resync_listen`~~ | **N34 阶段1 已删除**:其"只改本层状态"语义并入对账⑤(静默回收形态自动认领,纪律"不发芯片命令"随动作沉淀进 `net_reconcile_tick`),手动 API 面退役 |
| `net_reconcile_tick` | ①②⑤ 是**连接**失同步自愈(③④ 已在 P2a 移交 pump;⑤ 为 N34 阶段1 新增,吸收原 `net_resync_listen` 语义) |
| `net_reconcile_tick` | ①②⑤ 是**连接**失同步自愈(③④ 已在 P2a 移交 pump;⑤ 为 N34 阶段1 新增,吸收原 `net_resync_listen` 语义)(N34 阶段 3:③④/U11 已**回到本 tick**,单一 100ms tick 覆盖①②③④⑤/U11,见 §10.10) |
| `net_sock_t` 全部字段 | 数据归 `net_types.h`(双端共享核心),**不随模块走** |
> ⚠ `net_sock_t` 的 `last_peer_port` / `lost_conn_seen` 是**跨层字段**:新模块的
@@ -553,3 +559,29 @@ void net_listener_tick(void);
正常开销误报;
- 改完文件**必须重新 `git add`** —— pre-commit 校验的是**暂存区快照**,漏 add 会让
「本地已修好」与「提交时仍报错」自相矛盾(本次踩到,白跑两轮)。
### 10.10 N34 阶段 3:pump 并入 reconciler(**已执行 2026-10-13**)
审计(`docs/网络/N34-自愈审计/实施方案.md` §5 项 1)指出:`net_reconcile_tick`(200ms,①②⑤)与
`net_listener_pump`(100ms,③④/U11)是**同一件事的两个半片**——都在"读芯片状态 → 比对镜像 → 分级处置",
拆两个 tick 只是历史演化(P2a 从对账里拆出去),既多一份去抖/冷却状态机,又让"对账① 置 CLOSED 后
要等下一个 pump 轮才重建"多一跳延迟。阶段 3 合并为**单一 100ms `net_reconcile_tick`**:
- **`net_listener_pump()` 删除**;三种情形作为**形态③④ + U11** 回到 `net_reconcile_tick`,
认领门槛按原 pump 口径以 `listener_fd` 门(`auto_relisten` + TCP + 已绑端口)恢复——
**不变量 II(单一所有者)不变**,重建决策仍只此一处;
- **判据改读驱动状态缓存** `ch395f_sock_state_cached`(唯一刷新点 = `net_poll` 每轮全量刷新,
E2 实测单次回读 18.1µs)——reconciler 不再自发状态 SPI,形态② 命中时的对端端口回读是
本 tick 唯一按需 SPI;
- **分级去抖**(100ms/轮):②=1 轮(`NET_RECON_STABLE_ACCEPT`)、③④⑤=2 轮
(`NET_RECON_STABLE_RELISTEN`)、①=4 轮(`NET_RECON_STABLE_DEATH`,M3 让位窗 400ms 不变);
冷却 1s(`NET_RECONCILE_COOLDOWN_MS`);U11 被动重同步不去抖不占冷却;
- 常量 `NET_LISTENER_PUMP_PERIOD_MS` / `NET_LISTENER_STABLE_N` / `NET_LISTENER_COOLDOWN_MS` 删除;
主循环自愈 tick 由 4 个回到 **3 个**(retry / pending / reconcile);
- 同阶段顺路收口 P2b(§9 曾"建议暂缓"):`net_listen_locked` / `net_connect_locked` 的 open 失败
重试与 `net_close_locked` 的排空循环改走驱动 `sock_reset` 原语(`net_sock_reset_sync`,
GRACEFUL 100ms 底 / 600ms 封顶),并删除 close/connect 路径的全局中断读清(F-NET-7)。
**台架回归(2026-10-13,最终构建 Code=211348)**:`:7003` 50/50、`:21` 100ms/0ms/600ms 各 50/50、
Trap 23 RST 10/10 + FIN 5/5、Trap 25 n30 11/11、iec104 18/18、`ftp_regression` 11/11、E-02 双 PASS;
netTask 静态最坏链 1944 B 不变(链尾改经 `net_listener_retry_tick`→`net_relisten_locked`)。
+1 -1
View File
@@ -1094,7 +1094,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON
| **类型** | 功能测试 |
| **标题** | TCP 连接空闲超过 KeepAlive 阈值后自动断开 |
| **前置条件** | 1. `NET_KEEPALIVE_IDLE_MS=60000` / `INTVL=5000` / `CNT=3`(`net_config.h`)<br>2. 对端须为**死对端**:PC 连接 8080 后**禁用网卡**制造"链路在、对端静默"(存活对端会 ACK 探测 → 永不 TIMEOUT;Windows 防火墙规则对已建立连接无效,E11 实证)<br>3. 勿与 Phase 6/9/10/13 混选(recv 互抢数据) |
| **测试步骤** | 1. PC 连接 8080,不发数据<br>2. 立即禁用 PC 网卡(中间有交换机 → 设备侧 PHY 链路保持)<br>3. 等待芯片报 TIM_OUT(实测判死窗:连接起 **18~31s**,芯片定时语义与手册公式 75s 不符,见实验方案 E11/E13)<br>4. 本层 QUARANTINE → pump 重建 → s0 回 LISTEN<br>5. 恢复网卡,重连 8080 验证端口已恢复 |
| **测试步骤** | 1. PC 连接 8080,不发数据<br>2. 立即禁用 PC 网卡(中间有交换机 → 设备侧 PHY 链路保持)<br>3. 等待芯片报 TIM_OUT(实测判死窗:连接起 **18~31s**,芯片定时语义与手册公式 75s 不符,见实验方案 E11/E13)<br>4. 本层 QUARANTINE → reconciler 形态③④ 重建 → s0 回 LISTEN<br>5. 恢复网卡,重连 8080 验证端口已恢复 |
| **通过标准** | recv 返回 0 且耗时落在 **10s~120s 有界窗**(下限防"存活空闲会话被误杀"——T2 实测存活对端静默 90s 不触发;上限防"判死缺位");s0 回 LISTEN 且可接受新连接。报告后测试自动释放 socket 等新连接再测(一次性,防对已死连接 10ms 节奏刷假 FAIL) |
---
+37 -19
View File
@@ -62,8 +62,8 @@
| `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) |
| `net_types.h` | 类型定义(地址结构、Socket 控制块等) |
| `net_socket.h` | BSD Socket API 头文件,含 `net_msg_t` 消息类型定义 |
| `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含 L3 pending 化 recv、Trap 22 周期对账(200ms,去抖+冷却,①②⑤) |
| `net_listener.c`(**真机专属**) | **监听生命周期**(N-32 §10 模块化):`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick`(D10 重试 30ms/2s)/ `net_listener_pump`(唯一重建入口,100ms)/ `net_listener_settle`(沉降/隔离 100ms/400ms);内部头,模拟器不编译 |
| `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含 L3 pending 化 recv、Trap 22 周期对账 + 监听重建**合一** reconciler(`net_reconcile_tick`,100ms,分级去抖+冷却,形态①②③④⑤/U11,N34 阶段 3) |
| `net_listener.c`(**真机专属**) | **监听生命周期**(N-32 §10 模块化):`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick`(D10 重试 30ms/2s)/ `net_listener_settle`(沉降/隔离 100ms/400ms);原 `net_listener_pump` 的重建决策已并入 `net_reconcile_tick` 形态③④(N34 阶段 3,单一 100ms tick);内部头,模拟器不编译 |
| `net_select.h` | select/poll API 头文件 |
| `net_select.c` | select/poll 实现(仅 MCU 端编译) |
| `net_socket_sim.c`(`simulator/sim/`) | Winsock2 后端(模拟器端,与 `net_socket.c` 互斥编译),详见 §2.3 |
@@ -155,10 +155,10 @@ sequenceDiagram
| 规则 | 说明 |
|------|------|
| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()`——请求入队列,netTask 串行执行,调用方阻塞等回执。**自愈无显式 API**(N34 阶段1:`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 pump 自动认领,见 §3.6) |
| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()` / `net_drop_conn()`——请求入队列,netTask 串行执行,调用方阻塞等回执。**自愈无显式 API**(N34 阶段1:`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 reconciler 形态③④自动认领,见 §3.6) |
| **`netTask` 内** | 内部 API `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` / `net_listen_locked()` |
| **`net_socket()` / `net_bind()`** | 直接调用,无需队列;实际在就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)中创建监听 |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **4 个非阻塞 tick**(D10 重监听重试 → L3 挂起 recv 完成 → Trap 22 对账 200ms → N-32 监听器 pump 100ms),再处理队列 |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **3 个非阻塞 tick**(D10 重监听重试 → L3 挂起 recv 完成 → Trap 22 对账+监听重建合一 reconciler 100ms),再处理队列 |
### 3.4 内部 API 说明
@@ -183,20 +183,19 @@ int net_close_sock(net_sock_t *p_sock);
### 3.6 芯片/本层状态对账与重监听自愈(Trap 09 / 22 / 23)
单连接模型下"监听 Socket = 数据通道",多种芯片行为会造成**芯片侧与本层状态失同步**,纯事件(中断)驱动无法自愈。当前版本有**五层自愈**,全在 netTask 内、全非阻塞、不冻结主循环:
单连接模型下"监听 Socket = 数据通道",多种芯片行为会造成**芯片侧与本层状态失同步**,纯事件(中断)驱动无法自愈。当前版本有**四层自愈**(N34 阶段 3:原 200ms 周期对账与 100ms 监听器 pump 合并为**单一 100ms `net_reconcile_tick`**),全在 netTask 内、全非阻塞、不冻结主循环:
| 机制 | 触发 | 动作 | 关键参数 |
|---|---|---|---|
| **`auto_relisten`** | 数据通道正常 EOF / DISCONNECT | 重开 + 重新 LISTEN,回 LISTENING;应用层无需 close/重 listen | `net_listen()` 置位;`net_close()` 清 |
| **D10 非阻塞重监听重试** | 重开失败(Trap 09:芯片 close 后需沉降) | 单次尝试失败即登记待重试,每轮到点重试;超时限放弃并记 `relisten give up` | 重试间隔 30ms / 时限 2s(自首次尝试);同 fd 有在途挂起 recv 时跳过(L3 保险,防毁在途连接) |
| **Trap 22 周期对账** | 芯片丢 CONNECT/DISCONNECT 中断(快速复连);或静默回收 Socket(peer FIN,无 DISCONNECT 中断) | 每 200ms 对在用 socket 回读芯片状态,修正**三类**失同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理;② 本层 LISTENING 而芯片已握手 → 补 accept;⑤ 本层 ESTABLISHED 而芯片已静默回收回 LISTEN(0x05/0x01)→ RESYNC 语义只修本层、不发芯片命令(N34 阶段1 新增,原手动 API `net_resync_listen` 的语义并入;③④ 已移交 pump) | 周期 200ms;**连续 2 次失配**才动手(去抖 ≈400ms)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴) |
| **N-32 监听器 pump**(**唯一重建入口**) | 监听型 fd 的芯片状态与本层不一致(会话结束待重建 / 本层状态滞后) | 每 100ms 检查监听型 fd:芯片失位即重建(`net_listener_try`);本层 CLOSED 而芯片**正在** LISTEN 时**只做状态重同步**、不发芯片命令 | 周期 100ms;连续 2 次失配(≈200ms)+ 动手后 1s 冷却;其它位置只置事实、不做重建动作 |
| **Trap 22 周期对账 + 监听重建(合一 `net_reconcile_tick`,N34 阶段 3;对账①②⑤ + 原 pump③④/U11)** | 芯片丢 CONNECT/DISCONNECT 中断(快速复连);静默回收 Socket(peer FIN,无 DISCONNECT 中断);监听型 fd 芯片监听失位 / 本层状态滞后 | 每 100ms 对在用 socket 读**驱动状态缓存**(`ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs),修正**五类**失同步 + 一类被动重同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理(只置事实);② 本层 LISTENING 而芯片已握手 → 补 accept(N-30 幽灵甄别);③ 本层 LISTENING 而芯片监听失位 → 重建(`net_listener_try`);④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建;⑤ 本层 ESTABLISHED 而芯片已静默回收回 LISTEN(0x05/0x01)→ RESYNC 语义只修本层、不发芯片命令(N34 阶段1 新增,原手动 API `net_resync_listen` 的语义并入);U11 本层 CLOSED 而芯片**正在** LISTEN → 只做状态重同步、不发芯片命令 | 周期 100ms;**分级去抖**:②=1 轮、③④⑤=2 轮、①=4 轮(M3 让位窗 400ms,**勿改小**)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴;U11 被动重同步不去抖、不占冷却);其它位置只置事实、不做重建动作 |
| **Trap 23 排渣隔离** | 异常释放(芯片 TIMEOUT,如客户端中止上传):在途数据落进 RX 缓冲,直接复监听 → 下条连接把 ~2048B 残渣当自己的数据读走(上传静默截断);正常 FIN 释放亦有残留 DISCONNECT 收尾问题 | 复监听前沉降/隔离:持续排空 RX 残渣 + 主动 close + 排空中断,等芯片回 CLOSED/TCP_CLOSED 才 open+listen;到期强制放行 | 沉降等级(`net_listener_settle`):异常释放 QUARANTINE **400ms** / 正常释放 GRACEFUL **100ms**(2026-09-24 起正常 FIN 亦需沉降);上限 **800ms** 强制放行(**必须 < 2s 时限**,否则监听被放弃) |
**行为结论**:监听器恢复从"一次性事件"变为"登记 → 到点重试"过程:
- 正常断连:通常一次重开即成;失败时 ~30ms 量级恢复,最迟 ~1s(2s 后放弃,错误日志可见)。恢复窗内该端口拒绝新连接 → 调用方重连逻辑须容忍 ~1s 中断。
- **快速复连不再"监听死亡"(Trap 22)**:对账保证失同步 ≤200ms 内修正(2026-09-23 生产路径:待机后 UI `:7003` 快速复连 10/10;FTP `:21` 100ms/0ms/600ms 间隔各 50/50;零重建风暴)。
- **快速复连不再"监听死亡"(Trap 22)**:对账保证失同步按分级去抖在 100~400ms 内修正(②1 轮 / ③④⑤2 轮 / ①4 轮,100ms/轮;2026-09-23 生产路径:待机后 UI `:7003` 快速复连 10/10;FTP `:21` 100ms/0ms/600ms 间隔各 50/50;零重建风暴)。
- **中止复连首上传不再被静默截断(Trap 23)**:RST/FIN 中止后首笔复连上传完整通过(RST 10/10、FIN 5/5,SHA-256 全匹配)。详见 `docs/问题记录/CH395F_Trap_Records.md` Trap 22/23。
---
@@ -248,7 +247,7 @@ int main(void)
- 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT)
- 触发事件回调
- 处理数据接收
- `net_process_messages()` 每轮先跑 3 个**非阻塞**检查再取队列:D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 唤醒,E-02 修复)→ Trap 22 芯片/本层对账(200ms 周期,丢中断/静默回收自愈)
- `net_process_messages()` 每轮先跑 3 个**非阻塞**检查再取队列:D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 唤醒,E-02 修复)→ Trap 22 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,丢中断/静默回收/监听失位自愈,§3.6)
- **监听 Socket 建立**:由就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)经队列 API 建立(`net_socket` → `net_bind` → `net_listen`);断连后 net 层自动重监听(§3.6),服务层只需循环 `net_accept`,无需维护重监听逻辑
```c
@@ -876,7 +875,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
| `addrlen` | 输入输出:地址长度 |
| 返回值 | 新连接描述符(单连接模式下 == 监听 `sockfd`),无连接时返回 -1(可循环重试) |
> 单连接模式:监听 Socket 在 CONNECT 后**自身即数据通道**(§5.1)。`net_accept` 失败时若状态为 CLOSED 且 `auto_relisten` 开启,内部触发**非阻塞**重监听(失败交重试轮询,§3.6)。中断丢失也不会让监听死亡——对账在 200ms 内修正"本层 LISTENING 而芯片监听消失"等失同步。
> 单连接模式:监听 Socket 在 CONNECT 后**自身即数据通道**(§5.1)。`net_accept` 失败时若状态为 CLOSED 且 `auto_relisten` 开启,内部触发**非阻塞**重监听(失败交重试轮询,§3.6)。中断丢失也不会让监听死亡——合一对账(100ms 周期,§3.6)在 ~200ms 内修正"本层 LISTENING 而芯片监听消失"等失同步。
#### net_listener_armed / net_can_read - 就绪门控(**纯 RAM 读,不走消息队列**)
@@ -1003,6 +1002,23 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
int net_close(int sockfd);
```
释放 Socket 控制块,TCP 先断开连接。⚠ **单连接模型(MCU 端 CH395F)不得对监听 fd 调本 API**——连接与监听同一 fd,close 会连监听一起毁掉(§12.4);EOF 时 net 层已 `auto_relisten`,应用层**不得**在 EOF 时 close 该 fd。"放弃会话但保监听"用下面的 `net_drop_conn()`。
#### net_drop_conn - 主动拆会话、保留监听(N34 阶段 3,E-02 收口)
```c
int net_drop_conn(int sockfd);
```
应用**放弃会话**(对时 30s 预算耗尽、协议超时)时主动拆掉当前 TCP 会话并**保留监听**——单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(PC 内核自动 ACK keepalive,芯片僵尸判不死,定案见 `docs/网络/N34-自愈审计/实验方案.md` E13),而应用又不得 `net_close` 监听 fd(毁监听);本 API 补上"拆连接不拆监听"的应用侧形态。
- **netTask 侧**:先了结本 fd 挂起 recv(排空/取消 + 必通知),再 `sock_reset` QUARANTINE(FIN + 排渣 + 排空事件 + 等 byte0=00H,Trap 23 异常释放语义,400ms 底);本层收口 CLOSED;`auto_relisten` fd 随即 `net_listener_try` 即时重监听(失败交 D10 重试 + reconciler 形态④)
- **幂等**:无活动会话(非 ESTABLISHED/CONNECTING)直接返 0,不动监听
- **EOF 路径不要调**:EOF 时 net 层 `auto_relisten` 已接管(§12.4),无需也不应再拆
- 经消息队列委托 netTask(同其它 `net_*` API,非 netTask 上下文调用);等待期 netTask 有界占用 ~400-600ms
- 模拟器(`net_socket_sim.c`,数据 fd ≠ 监听 fd)实现为关数据连接、监听常驻
- 台架定案(2026-10-13,E-02):CH395F close **无 RST 语义**——对端内核存活但不 close 时芯片停 FIN_WAIT_2 长尾,监听恢复受限于对端 close;对端配合则 TIME_WAIT 过渡 ~150ms → reconciler 形态④ relisten → 新连接接入。不配合场景兜底 = 芯片 TIMEOUT/keepalive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` Trap 07
#### 自愈与查询 API
**net_sock_state — 查询 Socket 状态(只读快照)**
@@ -1018,7 +1034,7 @@ int net_sock_state(int sockfd);
两者曾是"应用层手动自愈"入口:`net_resync_listen()` 只修 MCU 侧状态(D14 静默回收),`net_relisten()` 无条件芯片侧 open+listen。现状:
- **静默回收**由对账⑤自动认领(§3.6:本层 ESTABLISHED 而芯片 0x05/0x01 → RESYNC 语义,≤400ms 去抖 + 1s 冷却,纪律"不发芯片命令"原样保留在认领动作内)——应用无需判定"连接是否已死",也就消除了"误判活连接调 resync 把活连接标成监听态"的 footgun。
- **监听重建**由 pump(100ms 周期,唯一重建入口)/ D10(30ms 重试)单一所有者负责——`net_relisten()` 对健康监听器反而是 footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。
- **监听重建**由 reconciler 形态③④(100ms 周期,唯一重建入口;N34 阶段 3 起原 pump 已并入 `net_reconcile_tick`)/ D10(30ms 重试)单一所有者负责——`net_relisten()` 对健康监听器反而是 footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。
生产调用者本就为零(仅板级测试 Phase 13,已改由自动机制接管)。历史实现见 `net_socket.c` git 历史。
@@ -1038,14 +1054,13 @@ Socket 已创建或已绑定时可用;port 为 0(动态分配)时返回已
void net_process_messages(void);
```
处理其他任务投递的请求(send/recv/connect/accept/close)。每次调用先跑 4 个**非阻塞** tick,再取队列消息(0 超时出队):
处理其他任务投递的请求(send/recv/connect/accept/close/drop_conn)。每次调用先跑 3 个**非阻塞** tick,再取队列消息(0 超时出队):
1. **D10 重监听重试**(`net_listener_retry_tick`):断连后监听器恢复的到点重试(30ms 间隔 / 2s 时限,§3.6)
2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点(仅显式预算) ③断连 检查每个挂起请求并通知调用方;④ tripwire:挂起超 60s 只 `[ERR]` 留痕一次,不强制了结(N34 阶段 1.5)
3. **Trap 22 芯片/本层对账**(`net_reconcile_tick`):200ms 周期修正**三类**失同步(①②⑤,§3.6)
4. **N-32 监听器 pump**(`net_listener_pump`):100ms 周期,**唯一重建入口**(③④;本层 CLOSED 而芯片在听时只重同步,不发芯片命令,§3.6)
3. **Trap 22 芯片/本层对账 + 监听重建(合一 `net_reconcile_tick`)**:100ms 周期,判据读驱动状态缓存(`ch395f_sock_state_cached`),修正**五类**失同步(①②③④⑤)+ U11 被动重同步——原 200ms 对账与 100ms 监听器 pump 已合并为单一 tick,**唯一重建入口**=形态③④(§3.6)
> `net_msg_t.timeout_ms`:L3 显式预算(`net_recv_to()`),0=**无限等待**(N34 阶段 1.5 删除 `NET_RECV_TIMEOUT_MS` 30s 默认预算——超时归调用方)。消息类型仅 6 种(send/recv/close/connect/listen/accept)——自愈类消息 `NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 已随 API 退役删除(N34 阶段1)。
> `net_msg_t.timeout_ms`:L3 显式预算(`net_recv_to()`),0=**无限等待**(N34 阶段 1.5 删除 `NET_RECV_TIMEOUT_MS` 30s 默认预算——超时归调用方)。消息类型 7 种(send/recv/close/connect/listen/accept/drop_conn)——自愈类消息 `NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 已随 API 退役删除(N34 阶段1)。
#### net_send_sock - 发送数据(内部版本)
@@ -1260,7 +1275,7 @@ static int read_time_frame(int conn_sock, uint8_t buf[FRAME_LEN]) {
**是的**,且 `net_poll()` 与 `net_process_messages()` 缺一不可:
- `net_poll()`:GPIO 轮询 INT#,**每轮批处理一批**中断,更新各 Socket 状态、触发事件回调(RECV 为电平触发,每轮只处理一批,避免饿死主循环)。
- `net_process_messages()`:除串行处理队列请求外,每轮先跑 3 个**非阻塞**检查——D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 的唤醒)→ Trap 22 芯片/本层对账(200ms 周期,丢中断/静默回收自愈)。
- `net_process_messages()`:除串行处理队列请求外,每轮先跑 3 个**非阻塞**检查——D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 的唤醒)→ Trap 22 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,丢中断/静默回收/监听失位自愈,§3.6)。
建议主循环周期 ≤ 10ms(本项目 1ms)。若 netTask 主循环停摆(断点、任务被挂起),监听器不会恢复、阻塞 recv 不会完成——这是软件侧"监听死机"的最常见原因。
@@ -1316,9 +1331,11 @@ if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
(2026-09-21 台架实证:`ui_net_sink` 曾在 EOF 时无条件 `net_close`,表现为 7003 答完一帧即断连、
端口随后"连接被拒"。)
**应用主动放弃会话(非 EOF)**:若会话是**应用侧放弃**的(对时 30s 预算耗尽、协议超时)而对端 TCP 仍活,EOF 路径不会发生、挂起客户端会永久占住单连接监听——此时用 `net_drop_conn(fd)`(§10.1)拆会话保监听,**不要** `net_close` 监听 fd。EOF 路径**不要**调它(`auto_relisten` 已接管)。
**芯片静默回收(D14,2026-09-22 台架实证)**:对端发 FIN 后,芯片可能**自行把 Socket 回收回 LISTEN 且不发 DISCONNECT 中断** → 本层滞留 ESTABLISHED,应用继续往死连接上读写,新连接的字节被旧会话吞掉。
**无需应用处理(N34 阶段1 起)**:对账⑤自动认领此形态(§3.6:≤400ms 去抖 + 1s 冷却)→ RESYNC 语义只修本层状态 + 取消本 fd 挂起 recv,**不发任何芯片命令**(发 close/重监听会摧毁芯片侧健康监听器 → 连接永拒,run8 实证 170s 拒绝)。等待期间该端口实际可接新连接(芯片侧已 LISTEN),新连接经对账补 accept 被本层感知。原显式 API `net_resync_listen(fd)` 已退役——"应用判定连接已死"这一步本身就是误判源(对活连接调用会把活连接标成监听态)。
**无需应用处理(N34 阶段1 起)**:对账⑤自动认领此形态(§3.6:2 轮去抖 ≈200ms + 1s 冷却)→ RESYNC 语义只修本层状态 + 取消本 fd 挂起 recv,**不发任何芯片命令**(发 close/重监听会摧毁芯片侧健康监听器 → 连接永拒,run8 实证 170s 拒绝)。等待期间该端口实际可接新连接(芯片侧已 LISTEN),新连接经对账补 accept 被本层感知。原显式 API `net_resync_listen(fd)` 已退役——"应用判定连接已死"这一步本身就是误判源(对活连接调用会把活连接标成监听态)。
⚠ 切勿用**单次**芯片状态读决定重建:单次读可能落在"刚 open 未生效"瞬态,曾致监听反复重建直至 `relisten give up`(2026-09-23 实测风暴,三服务口全死)——任何重建动作必须去抖(连续确认)+ 冷却(§3.6)。
@@ -1425,11 +1442,11 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
### 12.10 监听器自愈行为与沉降/隔离(D10 / Trap 09、Trap 22、Trap 23 / N-32)
**默认行为**(无需应用代码):断连 → `auto_relisten` 重开监听;重开失败 → 30ms 节奏重试(2s 时限);芯片丢中断/静默回收 → 200ms 对账 + 100ms 监听器 pump 自愈;中止复连首上传 → 沉降/隔离。全在 netTask 内,应用只需保证 `net_poll()` + `net_process_messages()` 主循环在跑。
**默认行为**(无需应用代码):断连 → `auto_relisten` 重开监听;重开失败 → 30ms 节奏重试(2s 时限);芯片丢中断/静默回收/监听失位 → 单一 reconciler 100ms 自愈(分级去抖,N34 阶段 3 起对账与 pump 合一);中止复连首上传 → 沉降/隔离。全在 netTask 内,应用只需保证 `net_poll()` + `net_process_messages()` 主循环在跑。
| 场景 | 修复前症状 | 机制 | 生产验证(2026-09-23) |
|---|---|---|---|
| **Trap 22** 快速复连丢中断 → 监听静默死亡 | 待机后 UI `:7003` 快速复连 2/10 且**永久**拒连;FTP `:21` 100ms 间隔自第 29 轮死亡;设备日志**无报错**(状态静默滞留) | 周期对账 `net_reconcile_tick()`(200ms,①②⑤)+ 监听器 pump `net_listener_pump()`(100ms,**唯一重建入口**,N-32)+ 去抖(连续 2 次)+ 冷却(1s);`handle_disconnect_event` 只置沉降事实、不再自行重建 | `:7003` 10/10;`:21` 100ms/0ms/600ms 各 50/50;UI 20/20;FTP 正常 10/10(SHA-256);中止 RST/FIN 5/5 + 5/5;零重建风暴 |
| **Trap 22** 快速复连丢中断 → 监听静默死亡 | 待机后 UI `:7003` 快速复连 2/10 且**永久**拒连;FTP `:21` 100ms 间隔自第 29 轮死亡;设备日志**无报错**(状态静默滞留) | 单一 reconciler `net_reconcile_tick()`(100ms,对账①②⑤ + 重建③④/U11 合一,**唯一重建入口**=形态③④,N34 阶段 3;判据读驱动状态缓存)+ 分级去抖(②1 轮/③④⑤2 轮/①4 轮)+ 冷却(1s);`handle_disconnect_event` 只置沉降事实、不再自行重建 | `:7003` 10/10;`:21` 100ms/0ms/600ms 各 50/50;UI 20/20;FTP 正常 10/10(SHA-256);中止 RST/FIN 5/5 + 5/5;零重建风暴(2026-10-13 合并后复测 `:7003` 50/50、`:21` 三档各 50/50 全绿) |
| **Trap 23** 中止后首笔复连上传被静默截断 ≤2048B 并误报 226 成功 | RST/FIN 中止上传 → 紧接着的首笔上传"收"到 ~2KB 残留后判结束回 226 → 客户端以为成功 = **静默数据损坏**(第二笔起正常) | 异常释放(TIMEOUT)预重监听隔离:`net_drain_chip_rx()` 分块排空 RX 残渣 + 主动 close + 排空中断 + 等 CLOSED/TCP_CLOSED(400ms 起 / 800ms 上限 < 2s 时限) | RST 10/10、FIN 5/5 全通过;正常路径 30/30 + 20/20,SHA-256 全匹配 |
| **Trap 21**(TC-NET-604)"接收冻结"虚警 | 旧判读脚本把"对端填满 2KB RX 窗口"误判为 STALL/WEDGE | **结论:非缺陷**(芯片 RX 缓冲即窗口,对端填满是常态);判据改为脚本层 STEP + 客户端内容校验(RET SHA-256),虚警工具归档(`MDK-ARM/retracted_bench_20260922/`,README 禁重跑) | 真实 FTP 路径 227 轮全通过(8KB×10 … 1MB×2);并发 UI 流 600/600 CRC;设备零 FAIL/ERROR/WARN |
@@ -1441,6 +1458,7 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
| 日期 | 变更 |
|------|------|
| 2026-10-13 | **N34 阶段 3 自愈合并 + E-02 收口**(实施规格 `docs/网络/N34-自愈审计/实施方案.md` §5):`net_listener_pump` **删除**,与 `net_reconcile_tick` 合并为**单一 100ms tick**(覆盖原对账①②⑤ + 原 pump③④/U11;判据读**驱动状态缓存** `ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs;**分级去抖** ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(M3 让位窗 400ms)+ 1s 冷却;§2.2 / §3.3 / §3.6(五层 → **四层**)/ §10.2 / §12.1 / §12.10 同步,主循环自愈 tick 由 4 个回到 **3 个**;常量 `NET_LISTENER_PUMP_PERIOD_MS` / `NET_LISTENER_STABLE_N` / `NET_LISTENER_COOLDOWN_MS` 删除);**忙等统一**——`net_listen_locked` / `net_connect_locked` open 失败重试"close+盲等 500ms"与 `net_close_locked` 500ms 排空循环改走驱动 `sock_reset` 原语(`net_sock_reset_sync`,GRACEFUL 100ms 底),`net_close_locked` 不再读清全局中断寄存器(F-NET-7:全局寄存器唯一读者 = `net_poll`);新增 **`net_drop_conn(sockfd)`**(§10.1,E-02 收口:拆会话**保监听**、幂等、EOF 路径不得调;新消息类型 `NET_MSG_DROP_CONN`;timeSync 30s 预算耗尽路径接入;模拟器实现为关数据连接);`net_poll` 每轮对在用 Socket 步进 `ch395f_sock_reset_poll`(同步包装 600ms 放弃后复位机不搁浅);`net_sock_state_t` 注释声明为"本层会话投影"(芯片事实源 = 驱动缓存,`net_types.h`);台架回归全绿(最终构建 Code=211348,E-02 双 PASS,见 `docs/网络/N34-自愈审计/实验方案.md` E-02 收口台架) |
| 2026-10-13 | **N34 阶段 1.5 recv 语义主流化**(规格 `docs/网络/N34-自愈审计/实施方案.md` §3.5):`net_recv()` 默认**无限阻塞**(删 `NET_RECV_TIMEOUT_MS` 30s 默认预算,唤醒源=数据/EOF/close/网络重建 CONNRESET);新增 `net_recv_nb()`(`NET_MSG_DONTWAIT` 路径转正)与 `net_setblocking(fd, on)`(模式挂 socket 控制块属性,`net_sock_t.blocking`,默认阻塞);`net_recv_to()` 保留为**显式预算**便捷入口(`timeout_ms=0` 语义从"默认 30s"改为"无限"故仍拒 INVAL);内部 recv 看门狗降级为 **tripwire**(挂起超 60s 只 `[ERR]` 留痕一次,不再"到点 +1s 强制了结"伪造 TIMEDOUT);⚠ 新增**文档契约**:IWDG `required_mask` 内任务禁止无限阻塞 recv(必须 `net_select` 超时或 `net_recv_to` 显式预算);`ui_net_sink` / `iec104_serv` 迁移 `net_recv_nb`,lftpd / timeSync 维持显式预算;模拟器 `net_socket_sim.c` 镜像三 API(blocking 属性包装层模拟) |
| 2026-10-13 | **N34 阶段1 API 面收敛**(审计见 `docs/网络/N34-自愈审计/README.md`):删除手动自愈 API `net_relisten()` / `net_resync_listen()`(生产零调用;后者语义并入对账**⑤**——本层 ESTABLISHED 而芯片 0x05/0x01 静默回收 → RESYNC 只修本层,§3.6 对账由两类扩为三类①②⑤;前者与 pump/D10 重叠且对健康监听器是 footgun)与 `net_can_accept()`(只认 ESTABLISHED 的旧门控挡死丢 CONNECT 探针,监听门控统一 `net_listener_armed()`,§10.1 门控节改写);`NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 消息类型删除;timeSync 会话收尾不再 `net_close`(同 fd 续 accept,监听跨会话常驻);lftpd 应用层阻塞重试退役(就绪屏障已消除 Trap 09 启动形态) |
| 2026-10-12 | **N-32 监听生命周期模块化 + 单一所有者**:监听生命周期抽为 `net_listener.c`(§2.2 文件表新增该行,真机专属);主循环自愈 tick 由 3 个增至 **4 个**(新增 `net_listener_pump`,100ms,**唯一重建入口**);`net_reconcile_tick` 对账收窄为 ①②,③④ 移交 pump;`handle_disconnect_event` / `handle_timeout_event` 改为 `net_listener_settle(GRACEFUL 100ms / QUARANTINE 400ms)` **只置事实**、不再自行重建;§3.3 / §3.6(四层自愈 → **五层**)/ §12.10 同步。权威设计见 `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10 |
@@ -14,7 +14,7 @@
## 1. 一句话架构
**一切芯片(CH395F)操作收敛到唯一执行者 netTask 串行完成**;其他任务通过"入队消息 + 任务通知"表达请求,结果只经**任务通知值**回传;阻塞等待**不独占 netTask**(挂起登记 + 主循环节拍完成);监听器**常驻**(`auto_relisten` + D10 非阻塞重试 + Trap 22 周期对账 + Trap 23 排渣隔离 + **N-32 监听器 pump(唯一重建入口)**,五层自愈)。
**一切芯片(CH395F)操作收敛到唯一执行者 netTask 串行完成**;其他任务通过"入队消息 + 任务通知"表达请求,结果只经**任务通知值**回传;阻塞等待**不独占 netTask**(挂起登记 + 主循环节拍完成);监听器**常驻**(`auto_relisten` + D10 非阻塞重试 + **Trap 22 周期对账 + 监听重建合一 `net_reconcile_tick`(唯一重建入口=形态③④,100ms)** + Trap 23 排渣隔离,四层自愈;N34 阶段 3 起原 N-32 监听器 pump 已并入对账)。
---
@@ -22,7 +22,7 @@
| 执行域 | 运行位置 / 节奏 | 允许的操作 | 典型调用者 |
|---|---|---|---|
| **netTask 主循环** | 独立 FreeRTOS 任务,1ms/tick | **唯一**可碰芯片(SPI2)的地方;4 个非阻塞 tick(D10 重试 / L3 挂起 / Trap 22 对账 / N-32 pump);队列分派;事件处理器;用户回调 | `net_poll` / `net_process_messages` 及其下游全部 `_locked` / `_tick` / `handle_*` |
| **netTask 主循环** | 独立 FreeRTOS 任务,1ms/tick | **唯一**可碰芯片(SPI2)的地方;3 个非阻塞 tick(D10 重试 / L3 挂起 / Trap 22 对账+监听重建合一 reconciler);队列分派;事件处理器;用户回调 | `net_poll` / `net_process_messages` 及其下游全部 `_locked` / `_tick` / `handle_*` |
| **应用任务侧** | ftpTask / uiTask / timeSyncTask / testTask 等 | 经队列的公共 API(入队 + `xTaskNotifyWait` 等回执);纯 RAM 直调 API;**禁止直碰芯片** | 各服务任务 |
| **直调快路径** | 同步调用,不经过队列 | `net_socket` / 查询族 / 回调注册(纯 RAM);**直碰 SPI 的三例外**:`net_bind`(UDP) / `net_sendto` / `net_recvfrom` | 初始化阶段、netTask 上下文、测试套件(硬件层测试模式下 netTask 自挂起,testTask 独占芯片) |
@@ -35,7 +35,7 @@
```mermaid
flowchart TB
subgraph APP["应用任务(非 netTask)"]
PUB["经队列公共 API<br/>netListen · netAccept · netConnect · netSend<br/>netRecv · netRecvTo · netClose<br/>netRelisten · netResyncListen"]
PUB["经队列公共 API<br/>netListen · netAccept · netConnect · netSend<br/>netRecv · netRecvTo · netClose · netDropConn"]
RAM["直调 · 纯 RAM<br/>netSocket · netSockState · netGetSock<br/>netGetErrno · netSetEventCb · netGetSockname<br/>字节序(netHtons / netInetAddr …)"]
SPI["直调 · 直碰 SPI(须芯片唯一上下文)<br/>netBind(UDP) · netSendto · netRecvFrom"]
CB["用户事件回调<br/>(在 netTask 上下文执行!)"]
@@ -45,20 +45,20 @@ flowchart TB
XN["xTaskNotify / xTaskNotifyWait<br/>(结果回传的唯一通道)"]
subgraph NT["netTask 主循环 — 1ms/tick;芯片操作唯一执行者"]
NPM["netProcessMessages<br/>先跑 4 个 tick,再排空队列分派"]
NP["netPoll<br/>INT# GPIO 轮询 → 全局中断 → 各 Socket SINT → 每轮一批"]
NPM["netProcessMessages<br/>先跑 3 个 tick,再排空队列分派"]
NP["netPoll<br/>INT# GPIO 轮询 → 全局中断 → 各 Socket SINT → 状态缓存全量刷新 + sock_reset 步进 → 每轮一批"]
T1["① netListenerRetryTick<br/>D10 重监听重试:30ms / 2s 时限"]
T2["② netPendingTick<br/>L3 挂起 recv:①数据 ②到点(仅显式预算) ③断连 ④tripwire 60s 只留痕"]
T3["③ netReconcileTick<br/>Trap 22 对账:200ms / 去抖 / 冷却(①②⑤)"]
T4["④ netListenerPump<br/>N-32 唯一重建入口:100ms / 去抖 / 冷却"]
DISP["队列分派 switch<br/>SEND · RECV · CLOSE<br/>CONNECT · LISTEN · ACCEPT"]
T3["③ netReconcileTick<br/>Trap 22 对账 + 监听重建合一(N34 阶段 3):100ms / 分级去抖 / 冷却<br/>读驱动状态缓存(①②③④⑤ + U11)"]
DISP["队列分派 switch<br/>SEND · RECV · CLOSE<br/>CONNECT · LISTEN · ACCEPT · DROP_CONN"]
subgraph LK["_locked / 内部分派目标(仅 netTask 上下文)"]
LL["netListenLocked<br/>初次监听(3×500ms 阻塞重试)"]
LN["netConnectLocked<br/>3×500ms 重试 + tcpConnect"]
LL["netListenLocked<br/>初次监听(open 重试 3× + sock_reset 排渣)"]
LN["netConnectLocked<br/>open 重试 3×(sock_reset 排渣)+ tcpConnect"]
LS["netSendLocked<br/>分块 ≤1KB + sendReady 门控"]
LR["netRecvLocked<br/>快路径 / DONTWAIT / 登记挂起"]
LA["netAcceptLocked<br/>先 netPoll + 状态判定"]
LC["netCloseLocked<br/>了结挂起 + close + 等 CLOSED + 排空中断"]
LC["netCloseLocked<br/>了结挂起 + cmd_done 快路径 / sock_reset(GRACEFUL)"]
DC["netDropConnLocked<br/>拆会话保监听:sock_reset(QUARANTINE) + 即时重监听"]
RLS["netListenerTry → netRelistenLocked<br/>单次尝试 + Trap 23 排渣隔离"]
end
EV["事件处理器<br/>handleConnect · handleDisconnect<br/>handleRecv · handleSend · handleTimeout"]
@@ -70,24 +70,25 @@ flowchart TB
PUB -->|入队| MQ
MQ --> DISP
DISP --> LL & LN & LS & LR & LA & LC & RLS
NPM --> T1 & T2 & T3 & T4 & NP
DISP --> LL & LN & LS & LR & LA & LC & DC & RLS
NPM --> T1 & T2 & T3 & NP
T1 --> RLS
T3 -->|"① 已断"| EV
T3 -->|"② 补 accept"| EV
T3 -->|"⑤ 静默回收:RESYNC 只修本层 + 取消挂起(不发芯片命令,N34 阶段1 吸收原 net_resync_listen 语义)"| RSY["reconcile⑤ RESYNC 动作"]
T4 -->|"①② 芯片失位即重建"| RLS
T3 -->|"③④ 监听失位 / 孤儿监听即重建(原 pump,N34 阶段 3)"| RLS
DC -->|"拆会话后即时重监听"| RLS
NP --> EV
EV --> FE
FE -.-> CB
EV -->|"断连/超时:只置事实"| ST
ST -.->|"事实 → 由 pump 统一重建"| T4
ST -.->|"事实 → 由 reconciler 形态③④ 统一重建"| T3
LR -->|无数据+阻塞| PR
PR --> T2
T2 -->|xTaskNotify| XN
XN -->|唤醒| PUB
NP --> CH
LL & LN & LS & LR & LA & LC & RLS --> CH
LL & LN & LS & LR & LA & LC & DC & RLS --> CH
SPI -->|直碰 SPI| CH
CH -->|INT# 低电平| NP
```
@@ -95,10 +96,10 @@ flowchart TB
**读图要点**:
1. 应用侧只有两种"等":① 入队后 `xTaskNotifyWait` 等回执(同步语义);② 挂起 recv 的等待(同一通道,只是"完成"由 `netPendingTick` 判定,不由 netTask 立即处理)。
2. `net_process_messages()` 开头的 **4 个 tick 是网络层自愈主干**:没有任何应用消息时,重试 / 挂起完成 / 状态对账 / 监听重建照常运转——"断连后即使没人发消息,监听器也能自己活过来"靠的就是它们。
3. 事件处理器**只由 `net_poll` 驱动**,例外是 `net_reconcile_tick`(带 2 次去抖 + 1s 冷却)可再入 `handle_disconnect_event` / `handle_connect_event`——对账是丢中断的自愈通道(Trap 22)。**注意职责边界(N-32 P2)**:对账与事件处理器**只置事实**(置 CLOSED / 登记沉降),**重建监听的动作统一由 `net_listener_pump` 做**(本层唯一重建入口)——"谁先看到 DISCONNECT 就谁重建"的竞争在结构上消失。
2. `net_process_messages()` 开头的 **3 个 tick 是网络层自愈主干**:没有任何应用消息时,重试 / 挂起完成 / 状态对账 + 监听重建照常运转——"断连后即使没人发消息,监听器也能自己活过来"靠的就是它们。
3. 事件处理器**只由 `net_poll` 驱动**,例外是 `net_reconcile_tick`(分级去抖 + 1s 冷却)可再入 `handle_disconnect_event` / `handle_connect_event`——对账是丢中断的自愈通道(Trap 22)。**注意职责边界(N-32 P2,N34 阶段 3 合并后)**:对账与事件处理器**只置事实**(置 CLOSED / 登记沉降),**重建监听的动作统一由 `net_reconcile_tick` 形态③④ 做**(本层唯一重建入口)——"谁先看到 DISCONNECT 就谁重建"的竞争在结构上消失。
4. 回调 `event_cb` 在 **netTask 上下文**执行:回调内不得阻塞、不得发网络操作。
5. **模块归属**:`T1`(D10 重试)/ `T4`(pump)/ `RLS` 的 `net_listener_try` / `ST`(settle)在 `net_listener.c`(真机专属、模拟器不编译);其余 tick 与 `_locked` 在 `net_socket.c`。
5. **模块归属**:`T1`(D10 重试)/ `RLS` 的 `net_listener_try` / `ST`(settle)在 `net_listener.c`(真机专属、模拟器不编译);其余 tick(含合并后的 `T3` reconciler)与 `_locked` 在 `net_socket.c`。
---
@@ -116,8 +117,8 @@ flowchart TB
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `net_poll()` | 事件泵:GPIO 轮询 INT#(低=有待处理)→ `GET_GLOB_INT_STATUS_ALL`(读=清)→ 按位图逐 Socket 读 SINT → 分派 5 类事件 | `HAL_GPIO_ReadPin` → `ch395f_get_glob_int_status_all` → `ch395f_get_sock_int_status` ×n → `handle_connect/disconnect/recv/send/timeout_event` | **RECV 中断是电平触发**:每次调用**只处理一批**(do-while(0)),处理完立即返回,否则主循环(echo/应用逻辑)饿死(Trap 08);SENBUF_FREE 只置 `send_ready` 不记事件 |
| `net_process_messages()` | 队列入口 + 自愈 tick 宿主:每轮**先**跑 4 个非阻塞 tick,再排空队列逐条分派,结果 `xTaskNotify` 回传 | `net_listener_retry_tick` → `net_pending_tick` → `net_reconcile_tick` → `net_listener_pump` → `osMessageQueueGet`(0 超时) ×n → switch 分派 → `xTaskNotify` | ① tick 放在队列**之前**:断连后即使没有任何应用消息,监听器也能自主恢复、挂起 recv 也能完成;② RECV 若返回 `NET_PENDING_REGISTERED`(哨兵 0x70000000)则**不发即时回执**(`do_notify=0`),完成时才通知;③ 失败结果把 `s_net_errno`(具体负错误码)压入通知值——调用方恢复 errno 后对外统一 -1(BSD 语义);④ 通知值**整个 32 位**都是结果(不压位域标签,否则 32768 这类大字节数会溢出为负);⑤ **pump 置于对账之后**:对账①可能刚把会话置 CLOSED,pump 紧接着就能看到并重建 |
| `net_poll()` | 事件泵:GPIO 轮询 INT#(低=有待处理)→ `GET_GLOB_INT_STATUS_ALL`(读=清)→ 按位图逐 Socket 读 SINT → 分派 5 类事件;**每轮末尾**对在用 Socket 全量刷新**驱动状态缓存** + 步进 `ch395f_sock_reset_poll`(N34 阶段 3) | `HAL_GPIO_ReadPin` → `ch395f_get_glob_int_status_all` → `ch395f_get_sock_int_status` ×n → `handle_connect/disconnect/recv/send/timeout_event` → `ch395f_get_sock_state` ×n(刷新缓存)+ `ch395f_sock_reset_poll` ×n | **RECV 中断是电平触发**:每次调用**只处理一批**(do-while(0)),处理完立即返回,否则主循环(echo/应用逻辑)饿死(Trap 08);SENBUF_FREE 只置 `send_ready` 不记事件;状态缓存**唯一刷新点**在此(reconciler 只读缓存,E2 实测单次回读 18.1µs);`sock_reset_poll` 步进使同步包装 600ms 放弃后的复位机不搁浅(IDLE 槽纯 RAM 判零 SPI) |
| `net_process_messages()` | 队列入口 + 自愈 tick 宿主:每轮**先**跑 3 个非阻塞 tick,再排空队列逐条分派,结果 `xTaskNotify` 回传 | `net_listener_retry_tick` → `net_pending_tick` → `net_reconcile_tick`(对账+重建合一)→ `osMessageQueueGet`(0 超时) ×n → switch 分派 → `xTaskNotify` | ① tick 放在队列**之前**:断连后即使没有任何应用消息,监听器也能自主恢复、挂起 recv 也能完成;② RECV 若返回 `NET_PENDING_REGISTERED`(哨兵 0x70000000)则**不发即时回执**(`do_notify=0`),完成时才通知;③ 失败结果把 `s_net_errno`(具体负错误码)压入通知值——调用方恢复 errno 后对外统一 -1(BSD 语义);④ 通知值**整个 32 位**都是结果(不压位域标签,否则 32768 这类大字节数会溢出为负);⑤ **对账①置 CLOSED 后形态③④同轮即可重建**(N34 阶段 3 合并,原"pump 置于对账之后"的顺序依赖在单 tick 内自然成立) |
### 4.3 经队列的公共 API 及其 netTask 侧实现
@@ -125,14 +126,15 @@ flowchart TB
| 公共 API | 消息 | netTask 侧实现 | 关键芯片命令 | 返回语义 | 设计意图 / 注意 |
|---|---|---|---|---|---|
| `net_listen` | `NET_MSG_LISTEN` | `net_listen_locked` | `set_proto_type` + `set_sour_port` + `open_socket`(**3×500ms 阻塞重试**)+ `tcp_listen` + 清全部中断状态 | 0 / -1 | **初次**监听允许阻塞重试(启动期无竞争);成功后置 `auto_relisten=1`——监听常驻的源头;启动前清空各 Socket/全局中断状态,防历史残留误触发事件 |
| `net_accept` | `NET_MSG_ACCEPT` | `net_accept_locked` | 无(先 `net_poll` 泵一次 + 状态判定) | 监听 fd 自身(单连接)/ -1(WOULDBLOCK) | 单连接:监听 Socket 在 CONNECT 后**自身即数据通道**,返回的就是它;CLOSED + auto_relisten 时触发**非阻塞** `net_listener_try`;**accept 路径不做自愈**——单次状态读会误判"刚 listen 未生效"致重建风暴(2026-09-23 实测),统一交给 `net_listener_pump`(N-32 单一重建入口) |
| `net_connect` | `NET_MSG_CONNECT` | `net_connect_locked` | `set_proto_type` + `open_socket`(3×500ms 重试)+ 清残留中断 + `set_sour_port` / `set_des_ip`(大端数组)/ `set_des_port` + `tcp_connect` | 0 = 请求已发出(**异步**)/ -1 | open **先于**寄存器写入(open 会复位寄存器);open 后清残留 DISCONNECT,防新连接秒断(Trap 14/17);目的 IP 字节序写反 = CONNECT TIMEOUT(Trap 16);返回 0 ≠ 已建立——调用方以 `net_sock_state` 轮询等 ESTABLISHED |
| `net_listen` | `NET_MSG_LISTEN` | `net_listen_locked` | `set_proto_type` + `set_sour_port` + `open_socket`(**open 重试 3×,失败走 `net_sock_reset_sync` 确定性排渣**,N34 阶段 3)+ `tcp_listen` + 清**本 Socket** 中断状态 | 0 / -1 | **初次**监听允许有界重试(启动期无竞争);成功后置 `auto_relisten=1`——监听常驻的源头;启动前只清本 Socket SINT(B3/F-NET-7:全局寄存器唯一读者 = `net_poll`,全量读清会焊死其它 Socket 事件位);open 失败重试原"close+盲等 500ms"已换 sock_reset(GRACEFUL 100ms 底,E4 定案复位后 open 必成) |
| `net_accept` | `NET_MSG_ACCEPT` | `net_accept_locked` | 无(先 `net_poll` 泵一次 + 状态判定) | 监听 fd 自身(单连接)/ -1(WOULDBLOCK) | 单连接:监听 Socket 在 CONNECT 后**自身即数据通道**,返回的就是它;CLOSED + auto_relisten 时触发**非阻塞** `net_listener_try`;**accept 路径不做自愈**——单次状态读会误判"刚 listen 未生效"致重建风暴(2026-09-23 实测),统一交给 `net_reconcile_tick` 形态③④(N34 阶段 3 合并后的单一重建入口) |
| `net_connect` | `NET_MSG_CONNECT` | `net_connect_locked` | `set_proto_type` + `open_socket`(open 重试 3× + `net_sock_reset_sync` 排渣)+ 清残留中断(只清本 Socket SINT,`ch395f_sock_events`)+ `set_sour_port` / `set_des_ip`(大端数组)/ `set_des_port` + `tcp_connect` | 0 = 请求已发出(**异步**)/ -1 | open **先于**寄存器写入(open 会复位寄存器);open 后清残留 DISCONNECT,防新连接秒断(Trap 14/17);目的 IP 字节序写反 = CONNECT TIMEOUT(Trap 16);返回 0 ≠ 已建立——调用方以 `net_sock_state` 轮询等 ESTABLISHED |
| `net_send` | `NET_MSG_SEND` | `net_send_locked` | `ch395f_write_send_buf`(分块)+ 等 SENBUF_FREE(`net_poll` 轮询) | 实际字节数 / -1 | 分块 ≤ `NET_SEND_CHUNK_MAX` = 1KB(**硬件发送 FIFO 硬上限**,Trap 20:写穿 FIFO = 数据错位);`send_ready` 门控用 `net_poll` 轮询而非 osDelay;短写**必须**告警——静默截断会让对端永久等待剩余字节(2026-09-21 实证:UI 推流应答截断 → 客户端失联) |
| `net_recv` | `NET_MSG_RECV`(timeout=0→**无限等待**,N34 阶段 1.5 删 30s 默认) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / CONNRESET;阻塞形态无 TIMEDOUT 一档) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞(`blocking` 属性=1 且未 DONTWAIT)→ **登记挂起后立即返回**(L3,netTask 零占用),唤醒源=数据/EOF/close/网络重建;DONTWAIT / `blocking=0` + CLOSED/session_dead → EOF + 触发重监听;⚠ IWDG 受监控任务禁用无限形态(`net_socket.h` 文档契约) |
| `net_recv_nb` | `NET_MSG_RECV`(flags=DONTWAIT) | `net_recv_locked` | 同上 | >0 / 0 / -1(WOULDBLOCK 等) | 原 `NET_MSG_DONTWAIT` 路径转正的具名入口(N34 阶段 1.5);不改变 `blocking` 属性;受 IWDG 监控 / 有周期职责任务的推荐默认(ui_net_sink / iec104_serv 已迁) |
| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套 + **显式预算**入口(N34 阶段 1.5,等价 Python settimeout,无隐藏默认值):调用方把长等待切成有界片(如剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(0 = 无限等待,与本接口"有界"存在理由相反);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) |
| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `close_socket` + 等 CLOSED(5ms 节奏 / ≤500ms)+ 排空 Socket/全局中断 | 0 / -1 | **先**了结本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);关后排空残留 DISCONNECT(Trap 14/17:否则下一连接被"刚建立就断开"秒断);控制块整体清零 |
| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `ch395f_cmd_done(CLOSE)` 快路径(芯片已 CLOSED → 只清本 Socket 残留事件)/ `net_sock_reset_sync(GRACEFUL)` 确定性排渣(N34 阶段 3) | 0 / -1 | **先**了结本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);排空只碰本 Socket SINT,**不再读清全局中断寄存器**(F-NET-7:全局唯一读者 = `net_poll`);原"close + 500ms 盲等排空循环"已换 sock_reset(内置 LISTEN 态二段 CLOSE 补发,E2 补充发现);控制块整体清零(`last_peer_port` 幽灵基准跨 fd 保留,N-30) |
| `net_drop_conn` | `NET_MSG_DROP_CONN`(N34 阶段 3 新增) | `net_drop_conn_locked` | `net_pending_settle_close` + `net_sock_reset_sync(QUARANTINE)` +(auto_relisten)`net_listener_try` | 0(含幂等)/ -1 | **拆会话保监听**(E-02 收口):应用放弃会话(对时 30s 预算耗尽 / 协议超时)而单连接监听被挂起对端永久占住时的应用侧出口;EOF 路径**不得**调(`auto_relisten` 已接管);非 ESTABLISHED/CONNECTING 直接返 0 不动监听;本层收口 CLOSED 不清 fd(保留 local_port/auto_relisten),监听型 fd 即时重监听、失败交 D10 + reconciler 形态④ |
| ~~`net_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **N34 阶段1 已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 |
| ~~`net_resync_listen`~~ | ~~`NET_MSG_RESYNC_LISTEN`~~ | **N34 阶段1 已删除**(语义并入对账⑤) | — | — | D14 静默回收(芯片自行回监听、不发 DISCONNECT)现由 `net_reconcile_tick` 判据⑤自动认领:本层 ESTABLISHED 且芯片 0x05/0x01 → 只修本层(state→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),**不发芯片命令**(纪律原样保留,run8 实证 170s 拒绝);去抖/冷却沿用对账框架 |
@@ -173,7 +175,7 @@ flowchart TB
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `net_listener_settle` | 登记"本 fd 会话已结束,重建前需沉降/隔离"(**只置事实**) | 写 `s_relisten_quarantine[fd]`(等级:GRACEFUL 100ms / QUARANTINE 400ms) | N-32 P1:取代此前外部直接写 `s_relisten_quarantine[]`;由 `handle_disconnect_event`(GRACEFUL)/ `handle_timeout_event`(QUARANTINE)调用。**只置事实,动作交 pump/try**;越界 fd 静默忽略 |
| `net_listener_settle` | 登记"本 fd 会话已结束,重建前需沉降/隔离"(**只置事实**) | 写 `s_relisten_quarantine[fd]`(等级:GRACEFUL 100ms / QUARANTINE 400ms) | N-32 P1:取代此前外部直接写 `s_relisten_quarantine[]`;由 `handle_disconnect_event`(GRACEFUL)/ `handle_timeout_event`(QUARANTINE)调用。**只置事实,动作交 reconciler 形态③④ / `net_listener_try`**;越界 fd 静默忽略 |
| `net_listener_try` | 尝试重建一次;失败登记重试(**不阻塞**) | `net_relisten_locked` → 成功 `net_relisten_done` / 失败置 `s_relisten_wait/next/dead` | 已在重试队列 → 直接返回 -1(**不重置重试时刻**):否则 10ms 一次的 accept 轮询会不断把重试时刻往后推 → 永不重试(饥饿) |
| `net_relisten_locked` | 真正的 open + listen(**单次尝试**) | (沉降期)`net_drain_chip_rx` + `close_socket` + `get_sock_int_status` / `get_glob_int_status_all` + `get_socket_status` → `set_proto_type` + `set_sour_port` + `open_socket` + `tcp_listen` + 清中断 | ① **严禁 osDelay 忙等**:失败只清理并返回,沉降交给重试轮询;② **Trap 23 沉降门**:带沉降标记的 socket(异常释放 TIMEOUT → QUARANTINE 400ms;正常释放 DISCONNECT → GRACEFUL 100ms)→ 排空 RX 残渣 + 主动 close + 排空中断 + 等芯片回 CLOSED/TCP_CLOSED;**上限强制放行**(必须 < 2s 重试时限,否则沉降耗尽重试预算 → `relisten give up` 监听被弃——2026-09-23 实测修正);③ 成功只置 state=LISTENING,**不 memset**(fd 与 `auto_relisten` 保留——BSD 风格监听常驻) |
| `net_listener_retry_tick` | 每轮到点重试(D10) | `net_relisten_locked` → `net_relisten_done` / 顺延 / 放弃 | **同 fd 有在途挂起 recv 时跳过**(重开 Socket 会摧毁在途连接,L3 保险);到 2s 时限 → 清状态 + `relisten give up` 错误日志(应用可见) |
@@ -181,49 +183,54 @@ flowchart TB
| `net_listener_clear` | 取消该 fd 的待重试/沉降 | 清 `wait/next/dead` + `settle_dead/settle_start` + `quarantine` | 触发点:`net_socket`(新 fd 不继承)/ `net_close_locked`(关闭即取消);沉降/隔离状态必须一并清,否则残留标记会污染下一次复监听 |
| `net_drain_chip_rx` | 排空芯片 RX 残留(256B 分块,≤16 次 = 4KB 上限) | `get_recv_len` + `read_recv_buf` | Trap 23:中止/异常断开时芯片 RX 缓冲残留上一条连接的数据(实测 ~2048B),不排空会被下一条连接当自己的数据读走(静默截断);分块读避免新增静态缓冲(内存预算紧张) |
### 4.7 监听器 pump(N-32 P2 单一重建所有者,`net_listener.c`)
### 4.7 监听重建(原 N-32 监听器 pump;N34 阶段 3 已并入 `net_reconcile_tick` 形态③④/U11)
`net_listener_pump()`(`net_process_messages` 每轮调用;周期 **100ms**):
> **N34 阶段 3(2026-10-13)**:`net_listener_pump()` 已**删除**——其全部三种情形作为**形态③④ + U11** 并入 `net_reconcile_tick`(§4.8,单一 100ms tick,判据读驱动状态缓存)。本节保留"单一重建所有者"的设计口径与情形表;实现位置与参数以 §4.8 为准。
**设计意图(设计文档 §4 不变量 II)**:其它所有位置**只置事实**(会话结束 / 状态迁移 / 事件派发),**不做重建动作**——于是"谁先看到 DISCONNECT 就谁重建"这类**多所有者竞争在结构上消失**。本函数是**本层唯一**的周期性重建决策者。
**设计意图(设计文档 §4 不变量 II)**:其它所有位置**只置事实**(会话结束 / 状态迁移 / 事件派发),**不做重建动作**——于是"谁先看到 DISCONNECT 就谁重建"这类**多所有者竞争在结构上消失**。`net_reconcile_tick` 形态③④是**本层唯一**的周期性重建决策者。
**覆盖三种情形**(认领门槛:在用 + `auto_relisten` + TCP + 已绑端口):
**覆盖三种情形**(认领门槛:在用 + `auto_relisten` + TCP + 已绑端口——原 pump 口径,少了它"本层 CLOSED + 已绑端口"的出向连接会被误判为监听器并被 relisten):
| # | 本层状态 / 芯片状态 | 动作 | 来源 |
|---|---|---|---|
| ① | 本层 LISTENING 而芯片监听**已失位** | 重建(`net_listener_try`) | 原对账③ |
| ② | 本层 CLOSED(会话已结束)而芯片**不在** LISTEN | 重建(`net_listener_try`) | 原对账④ |
| ③ | 本层 CLOSED 而芯片**正在** LISTEN | **只做状态重同步**(CLOSED→LISTENING),**不发任何芯片命令** | U11 缺口 |
| ③ | 本层 LISTENING 而芯片监听**已失位** | 重建(`net_listener_try`) | 原对账③ / 原 pump① |
| ④ | 本层 CLOSED(会话已结束)而芯片**不在** LISTEN | 重建(`net_listener_try`) | 原对账④ / 原 pump② |
| U11 | 本层 CLOSED 而芯片**正在** LISTEN | **只做状态重同步**(CLOSED→LISTENING),**不发任何芯片命令** | U11 缺口 |
情形③的理由:芯片监听器是**健康**的,重下发 open+listen 会因 `USE` 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故);只把本层状态收回 LISTENING 即可(与对账⑤ RESYNC 同一纪律——原 `net_resync_listen`,N34 阶段1 已退役)。
情形 U11 的理由:芯片监听器是**健康**的,重下发 open+listen 会因 `USE` 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故);只把本层状态收回 LISTENING 即可(与对账⑤ RESYNC 同一纪律——原 `net_resync_listen`,N34 阶段1 已退役)。
**去抖 + 冷却**(沿用原对账③④ 实测结论,取值不变):同一失配需**连续 2 次**(≈200ms)才动手;同一 Socket 动手后**冷却 1s**(防抖成风暴,2026-09-23 实证)。**周期取 100ms**(对账 200ms 的 1/2):比它快、又不至于像 50ms 那样频繁 close/open 与消费者收发撞车(2026-10-12 台架实测 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms)。
**去抖 + 冷却**(③④ 沿用原实测结论,取值不变):同一失配需**连续 2 轮**(`NET_RECON_STABLE_RELISTEN`,≈200ms)才动手;同一 Socket 动手后**冷却 1s**(防抖成风暴,2026-09-23 实证);U11 被动重同步**不去抖、不占冷却**(零风险)。**tick 周期 100ms**(原 pump 周期;合并后对账也从 200ms 提到 100ms):比 200ms 快、又不至于像 50ms 那样频繁 close/open 与消费者收发撞车(2026-10-12 台架实测 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms)。
> `session_dead` **不在此清零**(与 `net_relisten_locked` 同一契约:重建监听 ≠ 应用已确认会话结束;清零只发生在 `handle_connect_event`)。
### 4.8 Trap 22 周期对账(丢中断自愈,`net_socket.c`)
### 4.8 Trap 22 周期对账 + 监听重建(合一 `net_reconcile_tick`,丢中断自愈,`net_socket.c`)
`net_reconcile_tick()`(`net_process_messages` 每轮调用;周期 200ms):
`net_reconcile_tick()`(`net_process_messages` 每轮调用;周期 **100ms**,`NET_RECONCILE_PERIOD_MS`;N34 阶段 3 起含原 pump 形态③④/U11):
**问题**:芯片在快速复连场景会**丢 CONNECT/DISCONNECT 中断**(或静默回收 Socket 不发中断);丢事件后纯事件驱动无法自愈——典型:对端连接一直占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒,而应用因未收到 DISCONNECT 仍以为有客户端(永不调 accept)。
**机制**:对每个在用 Socket(**仅 ESTABLISHED / LISTENING**)回读 `GET_SOCKET_STATUS`,判定**两类**失同步并修正:
**机制**:对每个在用 Socket 读**驱动状态缓存**(`ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs;**本 tick 不再自发状态 SPI**,唯一按需 SPI = 形态② 命中时回读对端端口做幽灵甄别),判定**五类**失同步 + 一类被动重同步并修正:
| # | 失同步 | 动作 | 复用的实现 |
|---|---|---|---|
| ① | 本层 ESTABLISHED 而芯片已无连接(sock/tcp = CLOSED) | 按断开处理 → CLOSED + 回调(应用读到 EOF 后由 pump 重建监听) | `handle_disconnect_event`(内置陈旧事件甄别) |
| ① | 本层 ESTABLISHED 而芯片已无连接(sock/tcp = CLOSED) | 按断开处理 → CLOSED + 回调(应用读到 EOF 后由形态③④重建监听) | `handle_disconnect_event`(内置陈旧事件甄别) |
| ② | 本层 LISTENING 而芯片已完成握手(tcp = ESTABLISHED) | 补做 standalone accept 簿记 | `handle_connect_event` |
| ③ | 本层 LISTENING(监听型 fd)而芯片监听失位 | 重建 | `net_listener_try`(原 pump①) |
| ④ | 本层 CLOSED(监听型 fd)而芯片不在 LISTEN | 重建 | `net_listener_try`(原 pump②) |
| ⑤ | 本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01,无 DISCONNECT 中断) | RESYNC 只修本层、不发芯片命令 | 原 `net_resync_listen` 语义(N34 阶段1 并入) |
| U11 | 本层 CLOSED(监听型 fd)而芯片**正在** LISTEN | 只重同步 CLOSED→LISTENING,不发芯片命令 | 原 pump③ |
> ⚠ **N-32 P2(2026-10-12)**:原③(本层 LISTENING 丢监听)与④(孤儿监听)已**整体移交 `net_listener_pump`**——本函数内**不再有任何重建动作**(单一所有者),认领门槛相应收窄为 ESTABLISHED/LISTENING(原为驱动④而多认领"CLOSED + auto_relisten + 已绑端口")。
> ⚠ **N-32 P2(2026-10-12)**:原③(本层 LISTENING 丢监听)与④(孤儿监听)曾**整体移交 `net_listener_pump`**——本函数内**不再有任何重建动作**(单一所有者),认领门槛相应收窄为 ESTABLISHED/LISTENING(原为驱动④而多认领"CLOSED + auto_relisten + 已绑端口")。
> ⚠ **N34 阶段 3(2026-10-13)**:pump 已删除,③④/U11 **回到本 tick**(单一 100ms tick 覆盖①②③④⑤/U11);"单一所有者"不变量不变——重建决策仍只此一处,认领门槛按原 pump 口径以 `listener_fd` 门恢复。
**去抖 + 冷却(缺一不可)**:单次状态读可能落在"刚 open/listen 尚未生效"的瞬态——**2026-09-23 实测**:无去抖的 accept 路径自愈把监听反复重建直至 `relisten give up`(三服务口全死、89 行 ERROR)。故:同一失配需**连续 2 次**(≈400ms)才动手;同一 Socket 动手后**冷却 1s**。开销:每 200ms 每在用 Socket 一条状态命令,可忽略。
**去抖 + 冷却(缺一不可)**:单次状态读可能落在"刚 open/listen 尚未生效"的瞬态——**2026-09-23 实测**:无去抖的 accept 路径自愈把监听反复重建直至 `relisten give up`(三服务口全死、89 行 ERROR)。故**分级去抖**(单位=轮,100ms/轮):②=1 轮(`NET_RECON_STABLE_ACCEPT`,幽灵甄别兜底假阳性);③④⑤=2 轮(`NET_RECON_STABLE_RELISTEN`);①=4 轮(`NET_RECON_STABLE_DEATH`,**勿改小**——F-NET-17 M3 可疑电平让位再入的 400ms 租约,E9a 实证);同一 Socket 动手后**冷却 1s**(`NET_RECONCILE_COOLDOWN_MS`;U11 被动重同步不去抖不占冷却)。开销:每 100ms 每在用 Socket 的状态回读已收拢在 `net_poll`(≈100µs/轮),本 tick 本身零额外 SPI。
### 4.9 事件处理器(netTask 上下文;由 `net_poll` 驱动,对账 tick 可再入)
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `handle_connect_event` | LISTENING → standalone accept(回读对端地址 → ESTABLISHED + `send_ready=1` + 清残留 TIMEOUT + 回调);CONNECTING → 客户端连接成功(回读对端 + 使能 KeepAlive + 回调) | `ch395f_get_remot_ipp` + `get_sock_int_status` +(client)`set_keepalive_enable` + `fire_event` | GET_REMOT_IPP 为**小端**输出(与 set_des_ip 的大端相反)——解析别抄反;`session_dead` 的**唯一清零点** |
| `handle_disconnect_event` | 状态机迁移 + 陈旧事件甄别 + **登记沉降事实**(不再自己重建) | `ch395f_get_socket_status` →(LISTENING:芯片已不可监听 → **`net_listener_settle(GRACEFUL)`**,仍在监听 → 判陈旧丢弃)/(ESTABLISHED:芯片仍 ESTABLISHED → 判陈旧丢弃) | ① **陈旧 DISCONNECT 甄别**(D20):同一 fd"关闭→重监听→再连接"循环复用,上一条连接的 FIN/ACK 可能在 fd 已复用时才上报 → 不甄别则新连接秒断(实测:FTP 同会话第二个数据连接必 425);② 正常释放(FIN)→ **GRACEFUL 沉降 100ms**(只置事实,重建由 `net_listener_pump` 做,N-32 P1/P2);③ 置 CLOSED 后**不立即关芯片 Socket**——留给 `net_recv_locked` 排空剩余数据(FIN 可能先于数据到达) |
| `handle_disconnect_event` | 状态机迁移 + 陈旧事件甄别 + **登记沉降事实**(不再自己重建) | `ch395f_get_socket_status`(事件路径**主动 SPI**,不用缓存——缓存刷新在 net_poll 末尾,事件路径上必然滞后)→(LISTENING:只回读留痕,监听失位交 **reconciler 形态③** 重建;仍在监听 → 判陈旧丢弃)/(ESTABLISHED:芯片仍 ESTABLISHED → 判陈旧丢弃;F-NET-17 M3 可疑门 → 置可疑电平让位对账①) | ① **陈旧 DISCONNECT 甄别**(D20):同一 fd"关闭→重监听→再连接"循环复用,上一条连接的 FIN/ACK 可能在 fd 已复用时才上报 → 不甄别则新连接秒断(实测:FTP 同会话第二个数据连接必 425);② 正常释放(FIN)→ **GRACEFUL 沉降 100ms**(只置事实,重建由 `net_reconcile_tick` 形态③④ 做,N-32 P1/P2 + N34 阶段 3);③ 置 CLOSED 后**不立即关芯片 Socket**——留给 `net_recv_locked` 排空剩余数据(FIN 可能先于数据到达) |
| `handle_recv_event` | 置 `readable` + 回调;**不读 recv_len** | `fire_event` | 不读长度是为了避免与 `net_recv_locked` 的 `ch395f_get_recv_len` 竞争(Trap 13:分批读会归零);`readable` 供 `net_select` 无 SPI 访问判读 |
| `handle_send_event` | 发送缓冲已空:触发回调(`send_ready` 由 SENBUF_FREE 前置位) | `fire_event` | — |
| `handle_timeout_event` | CONNECTING → CREATED(连接超时);**ESTABLISHED → 登记隔离 + CLOSED**(Trap 23);LISTENING → 仅记日志 | **`net_listener_settle(fd, NET_LISTENER_SETTLE_QUARANTINE)`** + `fire_event` | TIMEOUT = 异常释放(如客户端中止上传):对端在途数据随后落进 RX 缓冲 → 必须隔离(400ms)后才许复监听;正常 FIN 释放走 disconnect 路径(GRACEFUL 100ms),**不走**隔离 |
@@ -303,7 +310,7 @@ sequenceDiagram
else TIMEOUT(异常,如中止上传)
N->>N: handle_timeout_event:net_listener_settle(QUARANTINE 400ms) + CLOSED
end
Note over N: 只置事实 —— 重建统一由 net_listener_pump(100ms)决定
Note over N: 只置事实 —— 重建统一由 net_reconcile_tick 形态③④(100ms)决定
N->>C: net_listener_try → net_relisten_locked
opt 沉降期内(Trap 23)
N->>C: 排空 RX 残渣(256B 分块)+ close + 排空中断
@@ -319,7 +326,7 @@ sequenceDiagram
Note over N: 2s 超限时 → 清状态 + "relisten give up"(错误日志,应用可感知)
```
### 5.4 丢中断自愈(Trap 22 对账 + N-32 pump)
### 5.4 丢中断自愈(Trap 22 对账 + 监听重建合一 reconciler,N34 阶段 3)
```mermaid
sequenceDiagram
@@ -329,14 +336,14 @@ sequenceDiagram
Note over C: 快速复连:芯片完成握手但 CONNECT 中断丢失
Note over N: 本层滞留 LISTENING → 应用以为无客户端(永不调 accept)
loop 每 200ms
N->>C: net_reconcile_tick:get_socket_status(每个在用 socket)
loop 每 100ms(net_poll 每轮刷新驱动状态缓存)
N->>N: net_reconcile_tick:读 ch395f_sock_state_cached(每个在用 socket)
C-->>N: 失配(本层 LISTENING,芯片 ESTABLISHED)
end
Note over N: 连续 2 次失配(去抖 ≈400ms)且不在冷却期 → 动手
Note over N: 形态② 去抖=1 轮(幽灵甄别挡假阳性;仅命中时回读对端端口,本 tick 唯一按需 SPI)
N->>N: handle_connect_event:补 accept 簿记 → ESTABLISHED
N-->>A: 下一次 net_accept 返回客户端(连接不再"吞掉")
Note over N: 另两类:① 本层 ESTABLISHED 而芯片已断 → handle_disconnect_event<br/>③④ 本层 LISTENING 丢监听 / CLOSED 孤儿 → net_listener_pump(100ms,唯一重建入口)
Note over N: 其余形态:① 本层 ESTABLISHED 而芯片已断 → handle_disconnect_event(去抖 4 轮)<br/>③④ 本层 LISTENING 丢监听 / CLOSED 孤儿 → 本 tick 形态③④ 重建(唯一重建入口,去抖 2 轮)<br/>⑤ 静默回收 → RESYNC 只修本层;U11 芯片在听 → 只重同步
```
### 5.5 网络重建(IP 变更免重启)
@@ -362,15 +369,15 @@ sequenceDiagram
|---|---|---|
| 1 | **一切芯片访问收敛 netTask**(唯一执行者);跨任务调用走队列 | Trap 16:跨任务 SPI 竞争破坏芯片寄存器 → 连接必超时 |
| 2 | **结果只经任务通知值回传**;消息按值入队,调用方看不到 netTask 对消息的写入;勿给消息加"回传用"字段 | Trap 10:6 个 API 永远返回 -1;"加 msg.err 字段"的修法曾致 `ui_net_sink` 每次空轮询误判真错误 → 连上即断 |
| 3 | **netTask 不为任何人自旋等待**:阻塞 recv = 挂起登记(L3);重开失败 = 重试登记(D10);初次 listen/connect 的 3×500ms 是唯一豁免(启动期无竞争) | E-02:一次空闲 30s recv 冻住全网 17.7s;旧重监听 osDelay(500) → 恢复 588ms、期间重连被拒 |
| 3 | **netTask 不为任何人自旋等待**:阻塞 recv = 挂起登记(L3);重开失败 = 重试登记(D10);初次 listen/connect 的 open 重试环(`net_sock_reset_sync` 有界等待,GRACEFUL 100ms 底 / 600ms 封顶)是唯一豁免(启动期无竞争,N34 阶段 3 起原 3×500ms 盲等已换确定性排渣) | E-02:一次空闲 30s recv 冻住全网 17.7s;旧重监听 osDelay(500) → 恢复 588ms、期间重连被拒 |
| 4 | **netTask 不向自身发消息 / 不自我挂起**(挂起登记对执行任务自身发起直接拒绝) | 自等自通知 = 死锁 |
| 5 | **基于单次状态读的重建动作必须去抖**(连续 2 次)+ 冷却(1s) | 2026-09-23:无去抖 → 监听反复重建直至 `relisten give up`,三服务口全死 |
| 5 | **基于状态读的重建动作必须去抖 + 冷却**:分级门槛 ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(100ms/轮)+ 冷却 1s | 2026-09-23:无去抖 → 监听反复重建直至 `relisten give up`,三服务口全死 |
| 6 | **异常释放(TIMEOUT)后复监听前必须隔离**:排空 RX 残渣 + 等 CLOSED/TCP_CLOSED;隔离上限 < 重试放弃时限 | Trap 23:~2KB 残渣被下条连接当自己的数据 → 首笔上传截断 2048B 并误报 226 成功;上限=2s 时隔离耗尽重试预算 → 监听被弃 |
| 7 | **RECV 电平触发中断每轮只处理一批** | Trap 08:连续循环 → 主循环饿死 |
| 8 | **close 前先了结挂起 recv + 排空残留 DISCONNECT** | Trap 14/17/19:锁存的陈旧 DISCONNECT 秒断下一连接 |
| 9 | **两段式等待防丢失通知**:零等待拾取 + 阻塞;清除掩码全 1;以 pdTRUE 判定 | run3~11:丢失通知 → 挂死 / 结果错位;0 结果误判"无通知" → 永久挂死 |
| 10 | **网络重建先取消并通知全部挂起,再复位芯片** | 取消不通知 = 调用方永久挂死 |
| 11 | **监听重建只有一个所有者**(`net_listener_pump`);其它位置**只置事实**(置 CLOSED / 登记沉降),不做重建动作 | N-32 P2 / B10:多所有者导致"谁先看到 DISCONNECT 就谁重建"的竞争与重建风暴 |
| 11 | **监听重建只有一个所有者**(`net_reconcile_tick` 形态③④,N34 阶段 3 合并后;原 `net_listener_pump` 已删);其它位置**只置事实**(置 CLOSED / 登记沉降),不做重建动作 | N-32 P2 / B10:多所有者导致"谁先看到 DISCONNECT 就谁重建"的竞争与重建风暴 |
---
@@ -385,15 +392,20 @@ sequenceDiagram
| `ch395f_set_ip_addr` / `set_gwip_addr` / `set_mask_addr` / `set_dhcp` | `net_init`(**均须在 `ch395f_init` 之前**) | 固定 IP 或 DHCP |
| `ch395f_init` / `ch395f_ping_enable` | `net_init` | 协议栈初始化(INIT_CH395)/ PING(须在 init 之后) |
| `ch395f_set_send_buf` / `ch395f_set_recv_buf` | `net_init`(8 Socket 布局);`net_bind`(UDP)(Socket 4~7 兜底) | 每 Socket 收 4 块 + 发 2 块 |
| `ch395f_get_glob_int_status_all` | `net_poll`;`net_listen_locked` / `net_connect_locked` / `net_close_locked` / `net_relisten_locked`(清残留) | **读 = 清**(中断清除机制) |
| `ch395f_get_glob_int_status_all` | **仅 `net_poll`**(F-NET-7 / B3:全局寄存器唯一读者;`net_listen_locked` / `net_connect_locked` / `net_close_locked` / `net_relisten_locked` 的原全局读清已全部删除) | **读 = 清**(中断清除机制,无按位手段)——其他位置读清会焊死 net_poll 的按 Socket 门控 |
| `ch395f_get_sock_int_status` | `net_poll`(逐 Socket SINT);各 open 路径后清残留;事件处理器清 TIMEOUT | **读 = 清** |
| `ch395f_open_socket` / `ch395f_close_socket` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_close_locked`、`net_relisten_locked`(含沉降期主动 close) | Trap 09:close 后需沉降才能重开 |
| `ch395f_sock_events` | `net_connect_locked`(open 后清残留,F-NET-7 护栏原语);`net_close_locked`(芯片已 CLOSED 快路径只清本 Socket 残留事件) | 只碰**本 Socket** SINT 的护栏原语 |
| `ch395f_open_socket` / `ch395f_close_socket` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`(含沉降期主动 close);`net_close_locked` 的 CLOSE 已改经 `ch395f_cmd_done` / `ch395f_sock_reset_*`(N34 阶段 3) | Trap 09:close 后需沉降才能重开 |
| `ch395f_tcp_listen` / `ch395f_tcp_connect` | `net_listen_locked` / `net_relisten_locked`;`net_connect_locked` | — |
| `ch395f_set_proto_type` / `set_sour_port` / `set_des_ip` / `set_des_port` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`、`net_sendto` | open 之后写端口/IP(open 会复位寄存器);set_des_ip 期望**大端数组** |
| `ch395f_write_send_buf` | `net_send_locked` / `net_sendto` | 单次 ≤1KB(硬件 FIFO 上限,Trap 20) |
| `ch395f_get_recv_len` / `ch395f_read_recv_buf` | `net_recv_locked`、`net_pending_tick`、`net_pending_settle_close`、`net_recvfrom`、`net_drain_chip_rx` | `handle_recv_event` **故意不读**(防竞争,Trap 13) |
| `ch395f_get_socket_status` | `net_listener_pump`、`net_reconcile_tick`、`handle_disconnect_event`(两处甄别)、`net_close_locked`(等 CLOSED)、`net_relisten_locked`(沉降门)、`net_connect_locked`(诊断回读) | 输出 **2 字节**([0]=Socket 状态 [1]=TCP 状态),缓冲必须给 2 字节 |
| `ch395f_get_remot_ipp` | `handle_connect_event`、`net_reconcile_tick`(幽灵甄别)、`net_connect_locked`(诊断) | **小端**输出(与 set_des_ip 相反) |
| `ch395f_get_recv_len` / `ch395f_read_recv_buf` | `net_recv_locked`、`net_pending_tick`、`net_pending_settle_close`、`net_recvfrom`、`net_drain_chip_rx`、`handle_disconnect_event`(M3 可疑门 RX 非空判据) | `handle_recv_event` **故意不读**(防竞争,Trap 13) |
| `ch395f_get_socket_status` | `handle_disconnect_event`(两处甄别——事件路径**主动 SPI**,缓存必然滞后于刚到达的中断)、`net_relisten_locked`(沉降门)、`net_connect_locked`(诊断回读)。⚠ **周期对账不再直接调**——改读缓存(下行) | 输出 **2 字节**([0]=Socket 状态 [1]=TCP 状态),缓冲必须给 2 字节 |
| `ch395f_get_sock_state` / `ch395f_sock_state_cached` | 刷新**仅 `net_poll`** 每轮对在用 Socket 调 `get_sock_state`(唯一刷新点);`sock_state_cached` 读缓存 = `net_reconcile_tick`(形态①②③④⑤/U11 判据) | N34 阶段 3:芯片状态唯一事实源;E2 实测单次回读 18.1µs,缓存新鲜度 ≤1ms |
| `ch395f_sock_reset_begin` / `ch395f_sock_reset_poll` | `net_sock_reset_sync`(`net_listen_locked` / `net_connect_locked` open 重试、`net_close_locked`、`net_drop_conn_locked`);`net_poll` 每轮对在用 Socket **步进**(IDLE 槽纯 RAM 判零 SPI) | 驱动复位状态机:读空 RX → CLOSE → 排空事件(quiescent) → 等 byte0=00H;内置 LISTEN 态二段 CLOSE 补发(E2 补充发现);GRACEFUL 100ms 底 / QUARANTINE 400ms 底 |
| `ch395f_cmd_done` | `net_close_locked`(CLOSED 快路径判据) | 命令完成判定(OPEN/LISTEN/CLOSE 三形态,读缓存不额外发 SPI) |
| `ch395f_link_up_cached` / `ch395f_link_change_count` | `net_reconcile_tick`(链路态订阅留痕,F-DRV-13) | 驱动 link 变化计数,net_poll 喂 |
| `ch395f_get_remot_ipp` | `handle_connect_event`、`net_reconcile_tick`(幽灵甄别,形态② 命中时唯一按需 SPI)、`net_connect_locked`(诊断) | **小端**输出(与 set_des_ip 相反) |
| `ch395f_set_keepalive_enable` | `handle_connect_event`(client 连接成功时) | — |
---
@@ -402,20 +414,20 @@ sequenceDiagram
| 想改什么 | 先看哪些函数 |
|---|---|
| 监听恢复策略(节奏/时限/沉降) | `net_listener_try` / `net_relisten_locked` / `net_listener_retry_tick` / `net_listener_pump`;常量 `NET_RELISTEN_*_MS` / `NET_LISTENER_PUMP_PERIOD_MS` / `NET_LISTENER_STABLE_N` / `NET_LISTENER_COOLDOWN_MS`(`net_listener.c` 内) |
| 监听恢复策略(节奏/时限/沉降) | `net_listener_try` / `net_relisten_locked` / `net_listener_retry_tick`;常量 `NET_RELISTEN_*_MS`(`net_listener.c` 内);重建决策在 `net_reconcile_tick` 形态③④(下行) |
| 阻塞接收等待语义 | `net_pending_register` / `net_pending_tick` / `net_pending_complete`;常量 `NET_PENDING_WATCHDOG_MS` |
| 丢中断自愈策略 | `net_reconcile_tick`(周期/去抖/冷却三常量,①②)+ `net_listener_pump`(③④,唯一重建入口)+ `handle_disconnect_event` / `handle_connect_event` |
| 丢中断自愈 + 监听重建策略(合一 tick,N34 阶段 3) | `net_reconcile_tick`(`NET_RECONCILE_PERIOD_MS` / `NET_RECONCILE_COOLDOWN_MS` / `NET_RECON_STABLE_ACCEPT|RELISTEN|DEATH`,①②③④⑤+U11,唯一重建入口=形态③④)+ `handle_disconnect_event` / `handle_connect_event` |
| 新增一个公共 API | 模式:`net_socket.h` 声明(`NET_NO_OS` 条件编译)→ `net_msg_type_t` 加类型(如需要)→ `net_process_messages` 加 dispatch 分支 → netTask 侧 `_locked` 实现;调用侧复制"参数检查 → 入队 → `net_wait_result` → errno 恢复"骨架 |
| 缓冲布局 / MSS / 重传 / KeepAlive 参数 | `net_init` + `net_config.h` + `ch395f.h`(`CH395F_RECV/SEND_BLOCKS`、`CH395F_TCP_MSS`);改完必须 0 错 0 警构建 + 全测试套件回归 |
| 队列容量 / 消息结构 | CubeMX 生成的 `MX_FREERTOS_Init`(8 槽)+ `net_socket.h` 的 `net_msg_t`(**勿加回传字段**) |
| 超时参数(收发/重试/对账) | `net_config.h`(收发/重传/KeepAlive);`net_listener.c` 内 `NET_RELISTEN_*` / `NET_LISTENER_*`;`net_socket.c` 内 `NET_RECONCILE_*` / `NET_PENDING_WATCHDOG_MS` |
| 超时参数(收发/重试/对账) | `net_config.h`(收发/重传/KeepAlive);`net_listener.c` 内 `NET_RELISTEN_*`;`net_socket.c` 内 `NET_RECONCILE_*` / `NET_RECON_STABLE_*` / `NET_PENDING_WATCHDOG_MS` |
---
## 9. 相关文档
- `docs/网络/BSD_Socket_API_使用指南.md` —— 公开 API 语义、示例、常见问题(本文的上半部)
- `docs/子系统设计/网络Socket监听生命周期设计说明.md` —— **监听生命周期权威设计**(N-32:边沿→电平、单一所有者 pump、`net_listener.c` 模块化边界 §10)
- `docs/子系统设计/网络Socket监听生命周期设计说明.md` —— **监听生命周期权威设计**(N-32:边沿→电平、单一所有者重建(N34 阶段 3 起为 `net_reconcile_tick` 形态③④)、`net_listener.c` 模块化边界 §10)
- `docs/问题记录/CH395F_Trap_Records.md` —— Trap 01~23 机制细节(本文引用处)
- `docs/网络/Net层阻塞模型架构设计.md` —— 阻塞等待模型的现状架构设计(pending 请求模型 / 五路完成语义)
- `docs/问题记录/错误码传播审计.md` —— E-02 头阻塞与错误码回传全审计
+1 -1
View File
@@ -65,7 +65,7 @@ T1(net 层视角)判"机制主体保留、API 可删";T3(主流对照视
| `auto_relisten` | net_types.h:172 | **保留**(模型基石) | 芯片永不自动回 LISTEN,监听常驻是软件承诺(P4) |
| D10 `net_listener_retry_tick` | net_listener.c:394-432 | **短期保留+小修;长期降级** | 小修=补 pump 同型状态门(F-NET-10)+ give-up 文案(F-NET-14);长期=驱动 `cmd_done()` 后内联进事务、删独立定时器(F-DRV-8) |
| Trap 22 `net_reconcile_tick` | net_socket.c:548-641 | **功能保留,形态重构**(阶段 1 已做①扩展=⑤) | 与 pump 判据合并为**单一 reconciler**、读驱动缓存、一套去抖(F-DRV-1);①扩展覆盖"本层 ESTABLISHED+芯片已回 LISTEN"缺口(F-NET-2)✅ 已实施(bad=5,`b8c6a3d`) |
| N-32 `net_listener_pump` | net_listener.c:465-553 | **保留**(内聚的落点,不是补丁) | 唯一周期重建决策者=不变量 II;T3"改内联"与 P2a 台架实测冲突(删消费者驱动→`[WD] task stall`) |
| N-32 `net_listener_pump` | net_listener.c:465-553 | **并入 `net_reconcile_tick`(阶段 3,2026-10-13)** | 唯一周期重建决策者=不变量 II **不变**,只是与对账合一为单一 100ms tick(形态③④/U11,判据读驱动缓存);T3"改内联"与 P2a 台架实测冲突(删消费者驱动→`[WD] task stall`)故消费者三处仍保留 |
| Trap 23 排渣隔离 | net_listener.c:102-121/:149-204 | **下沉驱动** | 排空/等 CLOSED 是纯芯片机械;与 net_close_locked:2326-2345 是同一契约两套变体(F-NET-9/F-DRV-4)→ 收口 `ch395f_sock_reset(sn, mode, wait_ms)`,net 层只选 mode |
| `net_relisten` | net_socket.c:3080 | **删除** ✅ 已实施(`b8c6a3d`) | 生产零调用;对健康监听器无条件 open→USE→close,自毁 ~1s(footgun) |
| `net_resync_listen` | net_socket.c:3026 | **删除**(先并入对账①)✅ 已实施(语义=对账⑤) | 语义正确但载体错位:应是自动对账,不是手动 API(F-NET-2/F-MS-4) |
+45 -1
View File
@@ -140,23 +140,67 @@ Trap 23 RST/FIN 5/5+5/5 + @test PASS + 0 错 0 警。**E8 深查修正**(见
验收:新增原语先**不接线**(只加不删),`@build` + 单元级板测(ch395f_test_task 加 TC 用例:
状态回读一致性、reset 状态机跑通、cmd_done 语义)+ `@test` 冒烟。RAM 增量 <64B,跑 `ram_report.py` 确认。
## 5. 阶段 3 · net 层收敛(门:E1/E6 结果;需完整台架回归)
## 5. 阶段 3 · net 层收敛(门:E1/E6 结果;需完整台架回归)—— **已实施 2026-10-13**(最终构建 Code=211348,回归全绿)
1. **单一 reconciler**:`net_reconcile_tick` + `net_listener_pump` 合并为一个 100ms tick,
判据读**驱动缓存**(不再各自发 SPI),统一一套去抖(连续 2 次)+ 冷却(1s);
覆盖形态:①本层 ESTABLISHED 而芯片 CLOSED/回收 ②本层 LISTENING 而芯片已握手(补 accept)
③监听 fd 芯片失位(重建)④本层 CLOSED 而芯片在听(只重同步)。
消费者即时重建三处保留(P2a 定案)。
✅ **已实施(2026-10-13)**:`net_listener_pump` 删除、并入 `net_reconcile_tick`(`NET_RECONCILE_PERIOD_MS=100`);
判据读 `ch395f_sock_state_cached`(唯一刷新点=`net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs);
去抖**分级落地**(原计划"统一连续 2 次"按 E2/E9a 数据细化):②=1 轮 / ③④⑤=2 轮 / ①=4 轮(M3 让位窗 400ms)+ 冷却 1s;
主循环自愈 tick 由 4 个回到 **3 个**;形态② 命中时的对端端口回读是本 tick 唯一按需 SPI。
2. **探针去抖删除**(读缓存无瞬态噪声,F-MS-9);`net_lost_connect_probe` 判据改读缓存。
✅ **已实施(2026-10-13)**:探针判据读驱动缓存(零 SPI),`NET_LOST_CONN_STABLE_N` 下线;
两类瞬态结构性消除("刚 listen 未生效"显示 TCP=00 永不命中握手门,E2:LISTEN 可见 ≤10ms 无长尾;
拆链窗假 ESTABLISHED 由幽灵甄别拦截),`lost_conn_seen` 退化为 M3 可疑电平标志。
3. **两处忙等统一**:`net_listen_locked` 3×500ms、`net_close_locked` 500ms 改走
`sock_reset_poll`/`cmd_done` 非阻塞路径(P2b 收口)。
✅ **已实施(2026-10-13)**:三处(含 `net_connect_locked` open 重试)统一走 `net_sock_reset_sync`
(驱动 `sock_reset` 原语:读空 RX→CLOSE→排空事件(quiescent)→等 byte0=00H,内置 LISTEN 态**二段 CLOSE** 补发,
E2 补充发现;GRACEFUL 100ms 底 / 600ms 封顶);`net_close_locked` 另加 `ch395f_cmd_done(CLOSE)` 快路径,
并**不再读清全局中断寄存器**(F-NET-7:全局唯一读者=`net_poll`)。
4. **U15 内化**(若 E6 证实频率>0):reconciler 增一条"GET_RECV_LEN>0 但 readable=0 → 置 readable"
低频对账;随后 ui_net_sink.c:552-558、iec104_serv.c:246-253 的 32 轮盲收退役。
⏸ **受 E6 门禁暂缓**(E6 未给出"频率>0"的定案数据,按 Trap 25「不留未证明的复杂度」不动消费者盲收;
待 E6 专项窗口补数据后再裁决)。
5. **镜像降级**:`net_sock_state_t` 保留为展示/门控投影,accept/recv/can_* 决策点改读驱动缓存。
✅ **已实施(2026-10-13)**:`net_types.h` 注释把 8 态明确声明为"**本层会话投影**,非芯片状态事实源"
(芯片事实源=驱动缓存,刷新点=net_poll);reconciler / 探针 / U11 重同步等按芯片状态决策的点全部改读缓存,
事件路径(`handle_*_event`)保留**主动 SPI**(缓存刷新在 net_poll 末尾,事件路径上必然滞后)。
验收:完整台架回归 = Trap 22(:7003 快速复连 10/10、:21 100ms/0ms/600ms 各 50/50)、
Trap 23(RST 10/10 + FIN 5/5,SHA-256)、Trap 25(n30_repro 11/11)、E-02(e02_probe.py)、
104(iec104_master.py)、对时(time_sync_server.py)、`@test` 锚点 + `[STACK]` 水位对比。
✅ **2026-10-13 全绿**:`@test` PASS;`[STACK]` net peak 1116/3840(70% 余量);Trap 22 `:7003` 50/50 +
`:21` 100ms/0ms/600ms 各 50/50;Trap 23 RST 10/10 + FIN 5/5;Trap 25 n30 11/11;E-02 双 PASS;
iec104 18/18;对时跨会话连续更新;`ftp_regression` 11/11;integrity 15/15 SHA;
`stack_report` netTask 静态链 **1944 B 不变**(链尾改经 `net_listener_retry_tick`→`net_relisten_locked`)。
### 5.1 E-02 收口(N34 阶段 3 追加,2026-10-13)
E-02 的最后一环不是"挂起 recv 不冻结 netTask"(阶段 1.5 已解决),而是**应用放弃会话后单连接监听被挂起对端永久占住**:
- **新 API `net_drop_conn(sockfd)`**(`NET_MSG_DROP_CONN`):拆会话**保监听**——netTask 侧
`net_pending_settle_close` 先了结挂起 recv,再 `net_sock_reset_sync(QUARANTINE)`(400ms 底),
本层收口 CLOSED(不清 fd),`auto_relisten` fd 即时 `net_listener_try`(失败交 D10 + reconciler 形态④);
**幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调**(`auto_relisten` 已接管)。
timeSync 30s 预算耗尽路径已接入;模拟器(conn≠listen fd)实现为关数据连接。
- **`net_poll` 每轮步进 `ch395f_sock_reset_poll`**(驱动契约"在 net_poll 步进";IDLE 槽纯 RAM 判零 SPI):
同步包装 600ms 放弃后复位机不搁浅,对端 FIN 到达即自动走完、槽位可复用。
- **芯片定案(E-02 台架,2026-10-13)**:CH395F `close` **无 RST 语义**(Trap 07 定案更正已载)——
对**内核存活但不 close 的对端**发 FIN 后芯片停 `FIN_WAIT_2`(实测 0x05/0x06 长尾),监听恢复受限于对端 close;
对端配合则 `TIME_WAIT` 过渡 ~150ms → reconciler 形态④ relisten → 新连接接入。
不配合场景兜底 = 芯片 TIMEOUT/KeepAlive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` Trap 07 补充。
### 5.2 relisten 沉降段统一(延后项,登记不做强行收口)
`net_relisten_locked` 隔离段仍是**手拼**序列(排空 RX + 主动 close + 读本 Socket SINT + 轮询状态 + 30ms 重试轮),
未统一改走 `sock_reset` 原语。判定:它**不是忙等**(每轮短 SPI + 交重试轮询,netTask 无长阻塞),
且 Trap 23 的沉降等级(GRACEFUL 100ms / QUARANTINE 400ms / 800ms 强制放行 < 2s 时限)是**生产已调优**的时序,
统一改造的回归面覆盖每次断连热路径,收益只是"少一段重复代码"——按 Trap 25 纪律**延后**,
待下一次触碰该热路径的任务顺路处理(`sock_reset` 原语已具备承接条件:二段 CLOSE、quiescent 排空、等级化最短等待)。
## 6. 横向纪律
@@ -688,3 +688,34 @@ C 不等价(0/20 + 级联)→ **`ch395f_sock_reset` 内部序列定案**:
GRACEFUL 与 QUARANTINE **都必须排 RX**——E4 假设"差异仅在是否排 RX + 最短等待"中
"是否排 RX"一半被否(带脏缓冲关闭在任何释放形态下都危险),两模式差异只剩
最短等待(100ms/400ms,Trap 23 二阶段实测值)。
### E-02 收口台架(2026-10-13,阶段 3 产品固件 Code=211348,`test/host/e02_probe.py`)
**背景**:E13 §5 把 `e02_probe` 的 `listener=FAIL` 重解读为"探针预期与'EOF 不 close'设计冲突",
并指出真实缺口 = **应用层无法主动拆掉自己放弃的会话**。阶段 3 以 `net_drop_conn()` 收口该缺口,
本节记录收口后的台架定案时序。
**drop_conn 时序实测**(timeSync 30s 预算耗尽路径,:8888 单连接监听):
| 阶段 | 观察 |
|---|---|
| 预算耗尽 | `time_sync: no TIME packet in 30s, drop conn` → netTask 侧 `sock_reset QUARANTINE`(FIN + 排渣 + 排空事件 + 等 byte0=00H,400ms 底)+ 本层收口 CLOSED + `auto_relisten` 即时 `net_listener_try` |
| 对端不配合(PC socket 仍开) | 芯片停 **`FIN_WAIT_2`**(byte0=`05H` / byte1=`06H`)长尾——`close` 无 RST(Trap 07 定案更正),监听恢复**受限于对端 close**;复位机在 `net_poll` 步进里等待,不搁浅 |
| 对端配合(探针 close 自己的 socket) | 芯片 `TIME_WAIT` 过渡 ~150ms(TC-NET-204 模式 A 同形)→ byte0 回 `00H` → **reconciler 形态④**(本层 CLOSED + 芯片不在 LISTEN)relisten |
| 恢复 | 对端 close 后 **~500ms** 内 :8888 重新接入(形态④ 去抖 2 轮 = 200ms + TIME_WAIT ~150ms + open/listen);新连接发完整 8 字节 TIME 包 → 设备落 **`wall clock updated`** |
| 探针判定 | `OVERALL: queue=PASS listener=PASS`(双 PASS) |
**探针口径更新**(`test/host/e02_probe.py`,本轮改):
1. **模拟守规矩对端**:第 3 步预算耗尽后,探针**主动 close 自己的 socket**(配合设备 FIN)——
这是"可恢复边界"的显式模拟;**不配合**(内核存活、自动 ACK FIN、socket 不关)场景**不在探针判据内**,
其兜底 = 芯片 TIMEOUT/KeepAlive 拆僵尸(E13 定案 30~90s),探针头部已写明该定案口径。
2. **3s 重试窗**:第二次连接由"单次 5s 尝试"改为 **3s 窗内 0.3s 节奏重试**——
恢复本身要经过 TIME_WAIT + 形态④ 去抖(合计 ~500ms 量级),单次即时尝试会把
"正在恢复"误判成 FAIL;重试窗只放宽**时机**,不放宽"必须重新接入 + 对时落地"的判据。
3. 顺序修正:close 移到第二次连接**之前**(旧脚本在第二次尝试之后才 close → 内核替挂起 socket
ACK keepalive → 监听被合法占用 → 旧 FAIL 的根因,见 E13 §5)。
**结论**:E-02 三段收口齐备——① 挂起 recv 不冻结 netTask(阶段 1.5);② 应用可主动拆会话保监听
(`net_drop_conn`,幂等、EOF 路径不得调);③ 复位机不搁浅(`net_poll` 步进 `ch395f_sock_reset_poll`)。
芯片侧物理边界(无 RST、`FIN_WAIT_2` 长尾受限于对端)已登记 `docs/问题记录/CH395F_Trap_Records.md` Trap 07 补充。
@@ -43,7 +43,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
| 区域 | 内容 |
|---|---|
| **左栏 · 调用方任务** | `net_recv` → 判"数据已在?" → **是**走快速路径;**否**(`timeout > 0`)登记 pending 后在自己上下文睡任务通知 |
| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable)→ 4 个非阻塞 tick(`net_listener_retry_tick` D10 → `net_pending_tick` 了结挂起 → `net_reconcile_tick` Trap 22 对账 → `net_listener_pump` 监听重建唯一入口)→ 排空消息队列 |
| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable + 全量刷新驱动状态缓存)→ 3 个非阻塞 tick(`net_listener_retry_tick` D10 → `net_pending_tick` 了结挂起 → `net_reconcile_tick` Trap 22 对账 + 监听重建唯一入口,N34 阶段 3 起原 `net_listener_pump` 已并入)→ 排空消息队列 |
| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建;tripwire 60s 只留痕(N34 阶段 1.5 由 1s 看门狗降级) |
| **回环** | 判定完成经 `xTaskNotify` 唤醒调用方(结果 = 字节数 / 负错误码) |
@@ -226,3 +226,4 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
| 2026-09-29 | **改写为架构设计文档**:以"当前如何设计/为何如此"为主线重组织;历史演进与门禁实测降级为附录 A;迁入 `docs/网络/` |
| 2026-10-03 | **网络重建失败恢复(P1-8 / A2)落地并真机验证**:首启组网失败 = 自检 `net init fail` + 30s 周期重试(1s 分片喂心跳,**不自驻留**);重建(reconfig)失败 = 自检 `net reinit fail` + 30s 延迟重触发事件位(tick 回绕安全),成功即清零;PHY 链路超时仍走既有 `comms fault` 降级(语义不变)。真机:开机 `net init: OK` / `PHY: 100M FULL` / `netTask: started`,无 `init FAIL` / `retry in 30s` 误触发(**正常路径零回归**);芯片级 `net_init` 失败分支 / 拔线负向场景待专项窗口(`待处理内容.md` N-16)。**执行判据见 `测试说明/实机测试实施指南.md` §7.1 / §10** |
| 2026-10-12 | **N-32 监听生命周期模块化 + 单一所有者**:监听生命周期抽为 `net_listener.c`(`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick` / `net_listener_pump` / `net_listener_settle`,真机专属、模拟器不编译);**重建动作收敛到唯一所有者 `net_listener_pump`**(100ms,去抖+冷却),对账 `net_reconcile_tick` 只剩①②;主循环自愈 tick 由 3 个增至 **4 个**。详见 `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10 |
| 2026-10-13 | **N34 阶段 3:对账与 pump 合并 + 忙等统一 + E-02 收口**(实施规格 `docs/网络/N34-自愈审计/实施方案.md` §5):`net_listener_pump` 删除、并入 `net_reconcile_tick`(单一 100ms tick,形态①②③④⑤+U11,判据读驱动状态缓存,分级去抖 ②1/③④⑤2/①4 轮 + 1s 冷却)——**主循环自愈 tick 由 4 个回到 3 个**,单一重建所有者不变量不变(=形态③④);`net_listen_locked`/`net_connect_locked`/`net_close_locked` 的盲等改 `net_sock_reset_sync` 确定性排渣(GRACEFUL 100ms 底 / 600ms 封顶),close/connect 不再读清全局中断寄存器(F-NET-7);新增 `net_drop_conn()`(拆会话保监听,`NET_MSG_DROP_CONN`)+ `net_poll` 每轮步进 `ch395f_sock_reset_poll`。挂起 recv 模型本身不变(`net_drop_conn` 复用 `net_pending_settle_close` 先了结挂起)。台架回归全绿(Code=211348) |
+19
View File
@@ -181,6 +181,15 @@ Phase 2 测试,2025-07
**与 Trap 17 的张力已消解**:Trap 17 写"close 后芯片要等对端 FIN/ACK 才把 Socket 置 CLOSED(耗时数毫秒)",与本条原"close 发 RST 瞬间回 CLOSED"矛盾;实测支持 Trap 17 一侧(A 模式可见 `TIME_WAIT` 过渡、0~150 ms),不支持 RST 说法。
### 补充(2026-10-13,E-02 台架):`close` 无 RST 的**边界**——对端内核存活时芯片停 `FIN_WAIT_2` 长尾
上条定案("`close` 无 RST 语义")在 E-02 收口实验里暴露出一个必须登记的边界,**不推翻定案、补全其适用条件**:
- **对端配合**(收到 FIN 后其内核 close):芯片走 `TIME_WAIT` 过渡 ~150 ms → byte0 回 `00H`,reconciler 形态④ relisten → 新连接接入(`e02_probe` 更新后 queue=PASS / listener=PASS,对时"wall clock updated"落地)。
- **对端内核存活但不 close**(挂起但连接仍活的客户端):本层发 FIN 后芯片停在 **`FIN_WAIT_2`(实测 byte0=0x05 / byte1=0x06)长尾**——没有 RST 就拆不掉,**监听恢复受限于对端 close**。这正是 E-02 场景(timeSync 30s 预算耗尽后 PC 侧 socket 仍活)的芯片侧形态。
- **不配合场景的兜底** = 芯片自身 TIMEOUT / KeepAlive 拆僵尸会话(E13 定案:判死窗 30~90s,见 `docs/网络/N34-自愈审计/实验方案.md` E13)——**不是**应用可以加速的路径。
- **应用侧出口 = `net_drop_conn(fd)`**(N34 阶段 3,E-02 收口):netTask 侧走驱动 `sock_reset` **QUARANTINE**(FIN + 排渣 + 排空事件 + 等 byte0=00H,400ms 底)+ 本层收口 CLOSED + `auto_relisten` 即时重监听;**保监听不毁**(单连接模型不得 `net_close` 监听 fd)。对端不 close 时复位机等在 `net_poll` 步进里(`ch395f_sock_reset_poll`),对端 FIN 到达即自动走完——**不搁浅**。
### 发现时间
Phase 2 TCP Client 测试,2025-07(**日期有误**,实为 2026-07-19;定案更正与 TC-NET-204 实测 2026-10-11)
@@ -289,6 +298,16 @@ CH395F 的 PHY link up 后,内部 TCP/IP 协议栈需要额外时间完成初
- 此问题在单连接模式 (`FUN_PARA=0x00`) 和多连接模式 (`0x02`) 下均存在,与 FUN_PARA 无关
- 如果多个应用任务同时启动 listen,可能全部首次失败、全部重试成功——建议各任务错峰启动
### 补充(2026-10-13,E2 台架):LISTEN 态 Socket 的**二段关闭**——一次 CLOSE 只拆监听,二次 CLOSE 才回 CLOSED
E2/E4 芯片实验(`docs/网络/N34-自愈审计/实验方案.md`)补充发现,属本条"close 后需沉降才能重开"的**前置形态**:
- 对处于 **LISTEN**(byte0=`05H` / byte1=`01H`)的 Socket 发 `CLOSE_SOCKET`:命令返 **SUCCESS**,但芯片**只拆监听**(byte1 `01H→00H`),**byte0 恒 `05H`**——单次 CLOSE 后按"等 byte0=00H"的判据会永远等不到。
- **第二次 CLOSE** 才把 byte0 拉到 `00H`(Socket 关闭)。即 LISTEN 态的关闭是**两段**:拆监听 → 关 Socket。
- **驱动侧已内置补发**:`ch395f_sock_reset_*` 状态机的 `WAIT_CLOSE` 段检测到"TCP 已 00H 而 byte0 仍 05H"自动补发第二次 CLOSE(契约见 `Drivers/BSP/CH395F/ch395f.h`)。
- **生产路径由 Trap 09 的 open 重试环吸收**:`net_listen_locked` / `net_connect_locked` open 失败 → `net_sock_reset_sync`(确定性排渣,内置二段 CLOSE)→ 下一轮重试 open;E4 定案 60 轮实证"复位后 open 必成"。
- ⚠ 任何**新写的**"close 后等 CLOSED"逻辑都必须走驱动复位原语,**不要**手拼"一次 CLOSE + 轮询 byte0"——那会在 LISTEN 态上死等(本条补充的直接动因)。
---
## Trap 10:消息队列值拷贝导致结果无法回传