From 77ee778605ed70ac340c298d9fd723a7ccf0cc93 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Wed, 23 Sep 2026 02:27:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DTrap22=E5=BF=AB=E9=80=9F?= =?UTF-8?q?=E5=A4=8D=E8=BF=9E=E4=B8=A2=E4=B8=AD=E6=96=AD=E8=87=B4=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E6=AD=BB=E4=BA=A1=EF=BC=9Anet=E5=B1=82=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E5=AF=B9=E8=B4=A6=E8=87=AA=E6=84=88(=E5=8E=BB?= =?UTF-8?q?=E6=8A=96+=E5=86=B7=E5=8D=B4)=EF=BC=8C=E5=A4=8D=E7=8E=B0?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E4=B8=8E=E5=9B=9E=E5=BD=92=E5=85=A8=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Drivers/BSP/NET/net_socket.c | 107 ++++++++++++++++++++++++++- docs/待处理内容.md | 2 +- docs/问题记录/CH395F_Trap_Records.md | 55 ++++++++++---- 3 files changed, 146 insertions(+), 18 deletions(-) diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 7ceb232e..c6e590c5 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -412,6 +412,89 @@ int net_poll(void) { return event_count; } +/* + * 函数功能:芯片/本层 socket 状态周期对账(Trap 22 丢中断自愈) + * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) + * 函数说明:芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件 + * 自愈——典型:对端连接一直占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED), + * 后续 SYN 全被拒,而应用因未收到 DISCONNECT 仍以为有客户端(永不调用 accept, + * 事件驱动自愈无路可走)。故周期回读芯片状态,修正三类失同步: + * ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(应用读到 EOF 后自动重监听) + * ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event) + * ③ 本层 LISTENING 而芯片监听已消失 → 重建监听(net_relisten_try) + * **去抖 + 冷却**:单次状态读可能落在"刚 open/listen 尚未生效"的瞬态 + * (2026-09-23 实测:无去抖时监听被反复重建直至放弃),故同一失配需连续 + * NET_RECONCILE_STABLE_N 次才动手;同一 socket 动手后冷却 NET_RECONCILE_COOLDOWN_MS。 + * 周期 NET_RECONCILE_PERIOD_MS;每次每在用 socket 一条状态命令,开销可忽略。 + */ +#define NET_RECONCILE_PERIOD_MS 200U /* 对账周期 */ +#define NET_RECONCILE_STABLE_N 2U /* 连续失配次数(去抖,≈400ms) */ +#define NET_RECONCILE_COOLDOWN_MS 1000U /* 同一 socket 两次动手的最小间隔(防抖成风暴) */ + +static void net_reconcile_tick(void) { + static uint32_t s_last = 0U; + static uint8_t s_bad[NET_MAX_SOCKETS]; /* 连续失配计数(去抖) */ + static uint32_t s_cool[NET_MAX_SOCKETS]; /* 冷却到期时刻 */ + uint32_t now = HAL_GetTick(); + int i; + + if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) { + return; + } + s_last = now; + + for (i = 0; i < NET_MAX_SOCKETS; i++) { + net_sock_t *p_sock = &s_net_socks[i]; + uint8_t st[2] = {0, 0}; + int bad = 0; + + if ((p_sock->in_use == 0U) || + ((p_sock->state != NET_SOCK_STATE_ESTABLISHED) && + (p_sock->state != NET_SOCK_STATE_LISTENING))) { + s_bad[i] = 0U; + continue; + } + ch395f_get_socket_status((uint8_t)i, st); + if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) && + ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] == CH395F_TCP_CLOSED))) { + bad = 1; /* 芯片已无连接而本层以为还连着 */ + } else if ((p_sock->state == NET_SOCK_STATE_LISTENING) && + (st[1] == CH395F_TCP_ESTABLISHED)) { + bad = 2; /* 芯片已完成握手而无 CONNECT 中断 */ + } else if ((p_sock->state == NET_SOCK_STATE_LISTENING) && + ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN))) { + bad = 3; /* 芯片监听已消失(异常关闭) */ + } + + if (bad == 0) { + s_bad[i] = 0U; + continue; + } + if (s_bad[i] < NET_RECONCILE_STABLE_N) { + s_bad[i]++; + } + if ((s_bad[i] < NET_RECONCILE_STABLE_N) || + ((int32_t)(now - s_cool[i]) < 0)) { + continue; /* 去抖未满,或处于冷却期:本轮不动手 */ + } + s_bad[i] = 0U; + s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS; + + if (bad == 1) { + DBG_INFO("sock%d reconcile: chip closed (sock=0x%02X tcp=0x%02X) -> disconnect", + i, st[0], st[1]); + handle_disconnect_event(p_sock); /* 内置陈旧事件甄别 + 状态迁移 + 事件派发 */ + } else if (bad == 2) { + DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i); + handle_connect_event(p_sock); /* 补做 standalone accept 簿记 */ + } else { + DBG_INFO("sock%d reconcile: listen lost (sock=0x%02X tcp=0x%02X) -> relisten", + i, st[0], st[1]); + (void)net_relisten_try(p_sock); + } + } +} + /* * 函数功能:处理消息队列中的请求 * 限定条件:必须在 netTask 上下文中调用(独占 CH395F 和 s_net_socks 访问权) @@ -430,6 +513,7 @@ void net_process_messages(void) { * L3:挂起 recv 的完成检查同位(两 tick 共享主循环节拍,均为 O(每 fd) 非阻塞)。 */ net_relisten_retry_tick(); net_pending_tick(); + net_reconcile_tick(); /* Trap 22:芯片/本层状态周期对账(丢中断自愈) */ while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) { int do_notify = 1; @@ -851,7 +935,10 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen net_poll(); /* 单连接:本 Socket 自身在 CONNECT 后已转为 ESTABLISHED, - * 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接 */ + * 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接。 + * 注:芯片丢中断导致的失同步不在此处自愈——单次状态读会误判"刚 listen 尚未生效", + * 曾致监听反复重建直至放弃(2026-09-23 实测风暴)。统一由 net_reconcile_tick() + * 周期对账(带去抖)处理。 */ if (p_listen_sock->state != NET_SOCK_STATE_ESTABLISHED) { /* 连接已断开(CLOSED)且启用自动重监听:先把 Socket 重新置回 LISTEN, * 下一次 accept 即可等待新连接(BSD 风格监听常驻) */ @@ -2560,7 +2647,23 @@ static void handle_disconnect_event(net_sock_t *p_sock) { int sockfd = (int)(p_sock - s_net_socks); if (p_sock->state == NET_SOCK_STATE_LISTENING) { - DBG_ERROR("WARNING: listen sock%d got disconnect", p_sock->ch395_sock); + /* Trap 22 / N-6:监听态收到 DISCONNECT —— 芯片在断开后会自动关闭该 Socket + * (FUN_PARA 默认行为),本层若不重开则**监听永久死亡**(此后所有 SYN 被拒、不自恢复)。 + * 触发场景(2026-09-23 生产固件复现:FTP :21 断连后 100ms 快速复连,第 29 轮起全拒): + * 断连后 <~1s 的快速复连,芯片完成握手但 CONNECT 中断未上报(无 standalone accept, + * 控制块滞留 LISTENING),随后该连接断开 → DISCONNECT 落到本分支。 + * 处置:回读芯片 socket 状态——已不在监听(芯片已关闭)即重建监听 + * (net_relisten_try 走 D10 非阻塞重试,失败由重试轮询接管);仍在监听则判陈旧事件丢弃。 */ + uint8_t st[2] = {0, 0}; + + ch395f_get_socket_status((uint8_t)sockfd, st); + if ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN)) { + DBG_INFO("sock%d listen lost (chip sock=0x%02X tcp=0x%02X), relisten now", + sockfd, st[0], st[1]); + (void)net_relisten_try(p_sock); + } else { + DBG_INFO("sock%d stale listen DISCONNECT ignored (chip tcp=0x%02X)", sockfd, st[1]); + } } else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) { /* 陈旧 DISCONNECT 甄别(D20 修复,2026-09-22 台架实证): * 单连接模型下同一个 fd 会被"关闭 → 重新监听 → 再连接"循环复用,而上一条 diff --git a/docs/待处理内容.md b/docs/待处理内容.md index 062561d9..251be716 100644 --- a/docs/待处理内容.md +++ b/docs/待处理内容.md @@ -12,7 +12,7 @@ | N-1 | **✅ 已结案(2026-09-23):TC-NET-604"接收缓冲填满后 RX 窗口冻结"判定为非缺陷**——原报告(阶段 8:前序突发后 4096B 突发仅前 2KB 回显、其余无回显)在当前固件**不可复现**:真实生产路径(产品固件 + 真 lftpd FTP 上传,判据 = FTP 协议结果 + **上传后 RETR 回读 SHA-256**)**227 轮全过**(8KB/64KB/256KB/1MB,含零间隔快速重连与 100 轮浸泡),并发生产 UI 流 600 帧 CRC 全过,设备侧无 FAIL/ERROR/WARN。原台架"停摆"系**测试程序自吞数据**(Phase 9/11 的 64B 读、固件探针读走不回显;短收量恰等于被吞字节数)+ **判读工具误判**(`probe_classify.py` A/C 把通过运行报成 STALL,C0 脚本层实测 1460/1460 全匹配)——详见 `CH395F_Trap_Records.md` Trap 21"教训"节。**原残留决策项(数据通道预算缩短)随之关闭** | 2026-08-26 发现 / 2026-09-23 结案 | 结案依据与台架纪律见 Trap 21;真实路径工具 `MDK-ARM/ftp_freeze_probe.py`(上传+回读哈希)、判读 `MDK-ARM/bench_verdict.py`;同类真实缺陷见 Trap 23(已修复) | `CH395F_Trap_Records.md` Trap 21;`CH395F_Test_Guide.md` TC-NET-604 | | N-2 | **49s 全系统日志静默(defaultTask/NAND syslog 排空停滞)**——串口日志 49s 无行(testTask 10s 周期行/netTask 事件行/[STACK] 行全缺),期间 net 栈照常处理外部连接(FTP banner 92ms)→ 日志管线(defaultTask 排空 → NAND+UART)停滞,队列满后各任务日志行被丢;系统功能不受损 | 2026-09-22 17:23:52~17:24:41(L3 台架 F2 运行中,gen 181) | **A/B 归因:L3 不触碰 defaultTask/syslog 路径 → 既有存储侧现象**。待办:① 复现条件定位(NAND 写/GC 长尾 vs UART TX 卡滞——对照 `CH395F` 无关的 SD2506 侧日志)② 日志队列满的丢弃计数/告警(现静默丢行,排障不可见)③ 与 S8(非阻塞日志)合并评估 | `错误码传播审计.md` E-02 阶段二"台架新发现②";`链路延迟测试说明.md` §6.7 注 | | N-3 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变、pending tick 无 pending 时为 µs 级指针读 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer) | 2026-09-22(L3 收尾留档) | 低风险:10ms/100ms 阈值余量 >8×;下次 Phase 13 窗口顺带重锁即可 | `链路延迟测试说明.md` §6.4 L5 注¹ | -| N-6 | **Trap 22:快速重连致 CONNECT 中断丢失 → 单连接监听静默死亡(P1,修复待批准)**——轻负载 + 重监听历史 + 断连后 <~1s 重连:芯片握手正常但 MCU 无 standalone accept(状态滞留 LISTENING)→ `net_send` 全失败(-1)→ 断开时 DISCONNECT 落 LISTENING 分支仅告警 → 芯片自动关闭 Socket → 端口永久拒连(不自恢复) | 2026-09-22(Trap 21 二次台架 A 组 run2;出现 1/1,未做 10/10 规模复验) | 修复方案:`handle_disconnect_event` LISTENING 分支回读芯片 socket 状态,非可监听即 `net_relisten_try` 重建(D20 的 LISTENING 分支姊妹情形)+ 可选:重监听后清残留中断位;应用侧规避:单连接 socket 断连后重连 ≥1~2s 退避(lftpd 快速 PASV / 客户端快速重试是生产风险场景)。生产影响:数据通道监听静默死亡 → 后续数据连接全拒,严重度等同端口失效 | `CH395F_Trap_Records.md` Trap 22 | +| N-6 | **✅ 已修复(2026-09-23 当日闭环):Trap 22 快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡**——生产固件确定性复现(待机后 UI `:7003` 快速复连:修复前 2/10 后永久拒连;FTP `:21` 100ms 间隔:第 29 轮起死亡);机制 = 芯片丢 CONNECT 或 DISCONNECT 中断 → 那条连接一直占住单连接监听(后续 SYN 全拒),且丢事件后本层收不到任何中断、纯事件驱动无法自愈。修复 = `net_socket.c` 新增**周期对账 `net_reconcile_tick()`**(200ms,回读芯片状态修正三类失同步:本层 ESTABLISHED 而芯片已断 → 断开;本层 LISTENING 而芯片已握手 → 补 accept;本层 LISTENING 而芯片监听消失 → 重建)+ 去抖(连续 2 次)+ 冷却(1s);`handle_disconnect_event` LISTENING 分支改为"已不可监听即重建"。验证:待机 `:7003` 10/10、`:21` 100ms/0ms/600ms 各 50/50、UI 20/20、FTP 正常 10/10(哈希)、中止 RST/FIN 5/5+5/5、零重建风暴 | 2026-09-22 发现 / 2026-09-23 复现+修复闭环 | 踩坑留档:自愈若放 accept 路径且用单次状态读,会因"刚 listen 未生效"瞬态反复重建直至 `relisten give up`(三监听口全死)——**单次状态读的重建动作必须去抖**。工具 `MDK-ARM/tcp_reconnect_probe.py` | `CH395F_Trap_Records.md` Trap 22;`Drivers/BSP/NET/net_socket.c`(`net_reconcile_tick` / `handle_disconnect_event`) | | N-8 | **✅ 已修复(2026-09-23 当日闭环):Trap 23 上传中止后首笔复连上传被静默截断(≤2048B)并误报成功**——修复前可信复现 15/15(RST 10/10 + 优雅 FIN 5/5);修复后 RST **10/10**、FIN **5/5** 全通过,正常路径回归 30/30 + 20/20(SHA-256 全匹配)。**真因**:中止时对端仍有在途数据 → 芯片 TIMEOUT 后才把在途段落进 RX 缓冲(约 2048B 残渣)→ 立即复监听时残渣被下一条连接当作自己的数据读走(新上传只"收"2048B 就判结束并回 226)。**修复**:异常释放(TIMEOUT)的 socket 复监听前隔离(持续排空 RX 残渣 + 主动 close + 等 CLOSED/TCP_CLOSED),400ms 起、800ms 上限强制放行(上限须 < 重试放弃时限 2000ms,否则监听被放弃,已实测修正);`net_drain_chip_rx()` 分块读空、零新增静态缓冲;仅异常路径生效,正常 FIN 路径零改动。详见 Trap 23"修复实施与验证"——生产路径 FTP STOR 中途断开(RST 或 FIN 均可)后,紧跟的第一笔复连上传被截断到 ≤2048B(芯片单 socket RX 缓冲)且回报 226 成功 → 客户端认为上传成功 = 静默数据损坏;第二笔起恢复正常 | 2026-09-23(可信复现 + 当日修复闭环;`MDK-ARM/ftp_abort_probe.py` 自动判定) | 已实施:`net_socket.c` 新增 `s_relisten_quarantine[]`(`handle_timeout_event` 置位)+ `net_drain_chip_rx()` + `net_relisten_locked` 隔离门(`NET_RELISTEN_QUARANTINE_MS`=400 / `NET_RELISTEN_SETTLE_MS`=800 < `NET_RELISTEN_DEADLINE_MS`=2000) | `CH395F_Trap_Records.md` Trap 23;`Drivers/BSP/NET/net_socket.c`(`net_relisten_locked` / `handle_timeout_event` / `net_drain_chip_rx`) | ## 存储 / 日志管线 diff --git a/docs/问题记录/CH395F_Trap_Records.md b/docs/问题记录/CH395F_Trap_Records.md index 75b4289a..a8659e6d 100644 --- a/docs/问题记录/CH395F_Trap_Records.md +++ b/docs/问题记录/CH395F_Trap_Records.md @@ -560,28 +560,53 @@ NET 层 TCP Client(阶段 7)`net_connect()` 返回 0,但 `net_poll()` 报 - 真实缺陷见 **Trap 23**(上传中止后首笔复连上传被静默截断,已可信复现并修复); - 发现:2026-08-26(阶段 8);结案:2026-09-23。 -## Trap 22:重监听后快速重连丢失 CONNECT 中断 → 监听静默死亡 +## Trap 22:快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡(2026-09-23 复现并修复) -### 现象 -轻负载构建(Phase 6 only)+ 带历史的重监听 socket:上一条连接关闭后约 600ms 到达的第二条连接,芯片侧硬件完成 TCP 握手(PC 连接成功、数据可流入),但 MCU 侧**无 `standalone accept`**(socket 控制块滞留 LISTENING)→ `net_send` 全部被状态机拒绝(`echo: send -1 != recv N`),PC 侧回显 0 字节;接收路径仍可读出数据(RECV_OK 正常、缓冲可排空)。断开时 DISCONNECT 中断命中 `handle_disconnect_event` 的 LISTENING 分支 → 仅记 `WARNING: listen sockN got disconnect` 而无任何处置 → 芯片在 DISCONNECT 后**自动关闭 Socket**(FUN_PARA 默认行为)→ MCU 未再重开 → **监听静默死亡**:此后所有 SYN 被拒(connection refused),不自恢复(仅复位/网络重建可恢复)。 -复现:Phase 6-only 构建 + `test/ch395f_socket_test.py tcp_client` 无间隔连续运行 → run1 全过、run2 失同步、run3 起全部 connection refused(2026-09-22 台架 A 组;出现 1/1,未做 10/10 规模复验)。 +### 现象(生产固件实测) +单连接监听 socket 在快速复连后**永久失效**(后续 SYN 被拒或超时,10s 内不自恢复),而芯片/网栈其余部分完全正常(其他监听端口照常工作)。两种表现: +- **拒连(ConnectionRefused)**:芯片完成握手但 **CONNECT 中断未上报** → 控制块滞留 LISTENING;或本层因 **DISCONNECT 中断丢失**仍以为有客户端 → 两种情况都会让芯片侧那条连接**一直占住单连接监听**,后续 SYN 全被拒;且丢事件后本层收不到任何中断,无法靠事件自愈。 +- 应用侧连锁:`net_send` 被拒(send -1);或应用读不到 EOF(其 `g_client` 永不释放,因而**永不调用 accept**)。 -### 机制(实证边界;芯片侧微观机理未解) -1. 芯片硬件完成握手但 CONNECT 中断未上报(或 MCU 侧丢失)→ `handle_connect_event` 未执行 → 状态滞留 LISTENING。 -2. **实证边界(2×2 台架)**:失同步仅现于"轻负载 + 重监听历史 + 快速重连(~600ms)"组合;重负载(Phase 6/9/11)下断连后 ~500ms 重连仍同步正常(B' 组 20/20 standalone accept、10/10 全连接成功);轻负载 + 15s 重连间隔亦全正常(A' 组 20/20)。即中断丢失窗口 ≈ "close → 重监听 → SYN" 间隔 < ~1s 且 netTask 相对空闲。芯片侧丢中断的具体原因未解(列为后续观察项,不阻塞下述修复)。 -3. 后果链确定:状态滞留 LISTENING → `net_send` 被拒(send -1)→ 断开时 DISCONNECT 落 LISTENING 分支(现有代码仅告警)→ 芯片自动关闭 Socket → MCU 不再重开 → 监听死亡。 -4. 与 Trap 21 的关系:Trap 21(TC-NET-604 "RX 窗口冻结")**已结案为非缺陷**(真实生产路径 227 轮全过;原台架"停摆"系测试程序自吞数据 + 判读工具误判,见 Trap 21);本条(Trap 22)是**独立且仍待修**的失效模式:连接状态失同步(缺 standalone accept + `send -1` + `WARNING: listen`),须与之分开跟踪。 +### 复现(可信用例,纯客户端判据;工具 `MDK-ARM/tcp_reconnect_probe.py`) +- **确定性复现**:产品固件 + 待机 60s 后,对 UI 监听 `:7003` 以 300ms 间隔快速复连 → 修复前**首 2 次成功、之后永久拒连**(10 轮 2/10,10s 恢复窗口内无自恢复); +- **概率复现**:FTP 控制 `:21` 以 100ms 间隔连 50 次 → 修复前**第 29 轮起永久拒连**; +- 对照:FTP `:21` 在 0ms / 600ms 间隔下通过;`:8888`(对时)同期亦正常 → 触发与"复连间隔 <~1s + 轻负载(待机)"相关,与 2026-09-22 记录的边界一致。 -### 修复(方案,未实施 → N-6) -1. `handle_disconnect_event` LISTENING 分支:回读芯片 socket 状态,非可监听状态(即芯片已自动关闭)时执行 `net_relisten_try` 重建(重 OPEN + TCP_LISTEN)——D20 陈旧事件修复的 LISTENING 分支姊妹情形(D20 只处理 ESTABLISHED 分支)。 -2. 可选加固:重监听完成后清除该 socket 残留中断位(`handle_connect_event` 已有同模式),防陈旧位致下一次事件误判。 -3. 应用侧规避(可立即执行):单连接 socket 断连后重连留 ≥1~2s 退避——客户端快速重试 / lftpd 快速 PASV 序列是生产主要风险场景。 +### 机制(设备侧日志实证) +1. 芯片丢 **CONNECT** 中断 → 无 `standalone accept`,控制块滞留 LISTENING;或丢 **DISCONNECT** 中断 → 本层仍以为 ESTABLISHED; +2. 此后本层**收不到任何中断**(芯片静默持有/丢弃该连接),纯事件驱动无路可走——实测把 `handle_disconnect_event` 的 LISTENING 分支从"仅告警"改为"重建监听"后**仍然无效**(因为连 DISCONNECT 都没来); +3. 故必须**周期回读芯片状态对账**才能发现失同步。 + +### 修复(已实施,`Drivers/BSP/NET/net_socket.c`) +1. **周期对账 `net_reconcile_tick()`**(`net_process_messages` 每轮调用;周期 200ms):对在用 socket 回读芯片状态,修正三类失同步—— + ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(应用读到 EOF 后自动重监听); + ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 `handle_connect_event`); + ③ 本层 LISTENING 而芯片监听已消失 → 重建监听(`net_relisten_try`)。 + **去抖 + 冷却**:同一失配需连续 2 次(≈400ms)才动手;同一 socket 动手后冷却 1s。 +2. `handle_disconnect_event` 的 LISTENING 分支:由"仅告警"改为"回读芯片状态——已不可监听即重建"(仍在监听则判陈旧事件丢弃)。 + +### 踩坑记录(务必先读) +最初的修法放在 **accept 路径**(每次 accept 时回读状态并自愈):单次状态读会把"刚 open/listen 尚未生效"的瞬态误判为失配 → 监听被反复重建直至 `relisten give up` → **三个监听口全部死亡(风暴)**。改为"周期对账 + 去抖 + 冷却"后消除。 +**结论:凡基于单次状态读的重建动作,必须去抖。** + +### 验证(修复前 → 修复后) +| 用例 | 修复前 | 修复后 | +|---|---|---| +| 待机后 UI `:7003` 快速复连 ×10 | 2/10(之后永久拒连) | **10/10** | +| FTP `:21` 间隔 100ms ×50 | 28/50(第 29 轮起死亡) | **50/50** | +| FTP `:21` 间隔 0ms / 600ms ×50 | 通过 | **50/50 / 50/50** | +| UI `:7003` 间隔 200ms ×20 | — | **20/20** | +| FTP 正常上传 10×64KB(SHA-256 往返) | 通过 | **10/10** | +| 上传中止 RST / FIN 回归(Trap 23) | 通过 | **5/5 + 5/5** | +| 设备侧 `relisten give up` / 重建风暴 | — | **0** | + +自愈实测计数(整轮会话):`chip closed -> disconnect` **12** 次、`connection completed w/o CONNECT irq -> accept` **1** 次、`listen lost -> relisten` **1** 次——三类失同步均真实出现并被自愈;`relisten give up` 0 次。 ### 对应文件 -`Drivers/BSP/NET/net_socket.c`(handle_disconnect_event LISTENING 分支);`test/net_test_task.c`(复现:Phase 6-only 构建 + 快速 tcp_client);`docs/待处理内容.md` N-6(修复待办登记)。 +`Drivers/BSP/NET/net_socket.c`(`net_reconcile_tick` / `handle_disconnect_event` LISTENING 分支);`MDK-ARM/tcp_reconnect_probe.py`(可信用例);`docs/待处理内容.md` N-6(已闭环)。 ### 发现时间 -2026-09-22(Trap 21 二次 2×2 复核台架副产品,A 组 run2)。 +2026-09-22(台架偶发 1/1)→ 2026-09-23(生产固件确定性复现 + 当日修复闭环)。 ## Trap 23:上传中止后首笔复连上传被静默截断(≤2048B)并误报成功