From a1f1f462629f8dee412b999009e7b71a009e6551 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 25 Sep 2026 01:57:03 +0800 Subject: [PATCH] =?UTF-8?q?Trap23=E4=B8=A4=E9=98=B6=E6=AE=B5=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D:=20=E5=BC=82=E5=B8=B8=E9=87=8A=E6=94=BE400ms=E9=9A=94?= =?UTF-8?q?=E7=A6=BB+=E6=AD=A3=E5=B8=B8=E9=87=8A=E6=94=BE100ms=E6=B2=89?= =?UTF-8?q?=E9=99=8D(=E4=BB=85=E6=94=B6=E8=BF=87=E6=95=B0=E6=8D=AE),=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=AD=E6=AD=A2=E5=90=8E=E6=96=B0=E4=BC=A0?= =?UTF-8?q?=E8=BE=93=E9=A6=96=E7=AC=94=E9=9D=99=E9=BB=98=E6=88=AA=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Drivers/BSP/NET/net_socket.c | 43 +++++++++++++++++++++++----- docs/问题记录/CH395F_Trap_Records.md | 37 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 3682c479..ab105c4d 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -1429,10 +1429,20 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, * NET_RELISTEN_SETTLE_MS 后强制放行,保证监听器不会永久失效。 * 正常 FIN 释放(handle_disconnect_event)不走此路径——既有行为零改动。 * 声明须早于 net_relisten_locked(其余重试状态见其后区块)。 */ -#define NET_RELISTEN_QUARANTINE_MS 400U /* 隔离时长(等在途数据落尽 + 排空残渣) */ +#define NET_RELISTEN_QUARANTINE_MS 400U /* 异常释放最短隔离(等在途数据落尽 + 排空残渣) */ +#define NET_RELISTEN_GRACEFUL_MS 100U /* 正常释放最短沉降(等芯片完成上一条连接的收尾) */ #define NET_RELISTEN_SETTLE_MS 800U /* 隔离上限(须 < NET_RELISTEN_DEADLINE_MS, * 否则隔离耗尽重试预算 → 监听被放弃,2026-09-23 实测) */ -static uint8_t s_relisten_quarantine[NET_MAX_SOCKETS]; /* 1 = 本次释放为异常,需隔离 */ +/* 重监听前"清芯片收尾状态"的两个等级(s_relisten_quarantine 取值): + * QUARANTINE - 异常释放(芯片 TIMEOUT):对端可能仍有在途数据,须清理 + 最短等 400ms; + * GRACEFUL - 正常释放(芯片 DISCONNECT):无在途数据,清理 + 最短等 100ms。 + * 2026-09-24 台架实证:正常释放走的是既有"不复位芯片收尾状态"的路径,残留的 DISCONNECT + * 会在**复用同一 fd 的下一条连接**上被投递(accept 后约 10~20ms)→ 下一条上传被静默截断 + * 到 2048B(一个芯片 RX 缓冲)且回报 226。只清理不等待(0ms)实测**无效**(4/4 仍截断), + * 说明芯片需要时间完成上一条连接的内部收尾;给足沉降即 4/4 通过。 */ +#define NET_RELISTEN_SETTLE_QUARANTINE 1U +#define NET_RELISTEN_SETTLE_GRACEFUL 2U +static uint8_t s_relisten_quarantine[NET_MAX_SOCKETS]; /* 0=免清;1=异常释放;2=正常释放 */ static uint32_t s_relisten_settle_start[NET_MAX_SOCKETS]; /* 隔离起点(0 = 未开始) */ static uint32_t s_relisten_settle_dead[NET_MAX_SOCKETS]; /* 隔离上限到期时刻 */ @@ -1479,6 +1489,13 @@ static int net_relisten_locked(net_sock_t *p_sock) { if (s_relisten_quarantine[sockfd] != 0U) { uint8_t st[2] = {0, 0}; uint32_t now = HAL_GetTick(); + /* 最短等待由释放来源决定(见 NET_RELISTEN_SETTLE_* 定义):异常释放等 400ms + * 让在途数据落尽;正常释放等 100ms 让芯片完成上一条连接的内部收尾。 + * 日志按等级区分 quarantine(异常)/ settle(正常),便于台架分辨触发路径。 */ + uint8_t lvl = s_relisten_quarantine[sockfd]; + uint32_t hold = (lvl == NET_RELISTEN_SETTLE_QUARANTINE) ? NET_RELISTEN_QUARANTINE_MS + : NET_RELISTEN_GRACEFUL_MS; + const char *tag = (lvl == NET_RELISTEN_SETTLE_QUARANTINE) ? "quarantine" : "settle"; if (s_relisten_settle_start[sockfd] == 0U) { s_relisten_settle_start[sockfd] = now; @@ -1491,15 +1508,15 @@ static int net_relisten_locked(net_sock_t *p_sock) { ch395f_get_socket_status((uint8_t)sockfd, st); if (((st[0] != CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_CLOSED) || - ((uint32_t)(now - s_relisten_settle_start[sockfd]) < NET_RELISTEN_QUARANTINE_MS))) { + ((uint32_t)(now - s_relisten_settle_start[sockfd]) < hold))) { if ((int32_t)(now - s_relisten_settle_dead[sockfd]) < 0) { - DBG_INFO("sock%d relisten quarantine (sock=0x%02X tcp=0x%02X)", - sockfd, st[0], st[1]); + DBG_INFO("sock%d relisten %s (sock=0x%02X tcp=0x%02X)", + sockfd, tag, st[0], st[1]); s_net_errno = NET_ERR_BUSY; return -1; /* 交重试轮询(30ms 后复检) */ } - DBG_INFO("sock%d relisten quarantine timeout, force open (sock=0x%02X tcp=0x%02X)", - sockfd, st[0], st[1]); + DBG_INFO("sock%d relisten %s timeout, force open (sock=0x%02X tcp=0x%02X)", + sockfd, tag, st[0], st[1]); } s_relisten_quarantine[sockfd] = 0U; /* 隔离完成(或强制放行) */ s_relisten_settle_start[sockfd] = 0U; @@ -2780,6 +2797,18 @@ static void handle_disconnect_event(net_sock_t *p_sock) { /* 单连接:先不关 CH395F Socket, * 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */ DBG_INFO("sock%d DISCONNECTED, releasing (chip tcp=0x%02X)", p_sock->ch395_sock, st[1]); + /* Trap 23 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态 + * 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 + * 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226。 + * 仅在**本连接收过数据**时登记沉降:实测触发源是"收过数据的数据通道被关闭", + * 而纯连接即关(tcp_reconnect_probe 的 0ms 复连用例)与只发不收的下载连接不残留 + * —— 对它们免沉降即可保住 Trap 22 的快速复连时序(:21 @0ms ×50 全通过)。 */ + if (p_sock->recv_bytes != 0U) { + int qfd = (int)(p_sock - s_net_socks); + if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) { + s_relisten_quarantine[qfd] = NET_RELISTEN_SETTLE_GRACEFUL; + } + } p_sock->state = NET_SOCK_STATE_CLOSED; fire_event(p_sock, NET_EVENT_DISCONNECTED); } else if (p_sock->state != NET_SOCK_STATE_CLOSED && diff --git a/docs/问题记录/CH395F_Trap_Records.md b/docs/问题记录/CH395F_Trap_Records.md index a8659e6d..337c20d7 100644 --- a/docs/问题记录/CH395F_Trap_Records.md +++ b/docs/问题记录/CH395F_Trap_Records.md @@ -664,3 +664,40 @@ NET 层 TCP Client(阶段 7)`net_connect()` 返回 0,但 `net_poll()` 报 - 修复后:RST 中止 **10/10**、优雅 FIN 中止 **5/5** 全部通过; - 正常路径回归:30×64KB 零间隔 **30/30**、20×64KB **20/20**,SHA-256 全匹配; - 启动门禁:`App init OK` / `net init: OK`、FORBID 0 行。 + +### 第二阶段:正常释放(FIN)路径——边界甄别与 100ms 沉降(2026-09-24) + +**边界再甄别(现场"1~2s"时间假设被推翻)**:中止后 gap 500/2000/5000ms 台架复测 → 设备实存尺寸**全部仍 2048**(各 4/4,合计 12/12)→ **与时间无关**;监听重开本征即时(`t_accept` 30~47ms)。真实边界: + +| 维度 | 结论 | 证据 | +|---|---|---| +| 触发 | 设备侧**收过数据**的数据连接被对端关闭(纯 FIN 即触发,不要求 RST) | 纯 `close()`(FIN)4/4 复现;RST 变体 5/5 | +| 受影响路径 | 仅**正常 DISCONNECT 释放路径**缺芯片状态沉降 | RST(TIMEOUT)路径已有一阶段 400ms 隔离保护 | +| 投递形式 | 芯片上一条连接的收尾残留,投递给**复用同一 fd 的下一条**连接(accept 后约 10~20ms 一条伪 DISCONNECT) | 设备日志:`standalone accept` → `DISCONNECTED, releasing (chip tcp=0x00)` | +| 影响面 | **一次性**(首条新连接中招,第二条起干净);同 fd 非 FTP 服务(`:7003` UI sink)不受影响(其 socket 不按传输复用) | 一次性 3/3 全对;`:7003` 复用 8/8 全过 | +| 时间 | 无关(gap 5s 仍失败)→ 现场"1~2s"只是人工重触的常见窗口,不是芯片机制 | gap 500/2000/5000ms 共 12/12 全部失败 | + +**修复(第二阶段,`Drivers/BSP/NET/net_socket.c` +36/−7,netTask 串行上下文,零新增缓冲)**: +1. relisten 前沉降分两级:异常释放(芯片 TIMEOUT)=清理 + 最短等 400ms(`NET_RELISTEN_QUARANTINE_MS`,一阶段行为不变);正常释放(芯片 DISCONNECT)=清理 + 最短等 **100ms**(`NET_RELISTEN_GRACEFUL_MS`,新增); +2. 正常释放沉降**仅在本连接收过数据时登记**(`recv_bytes != 0`):纯连接即关(Trap 22 的 0ms 复连用例)与只发不收的下载连接无残留 → 免沉降保住快速复连时序; +3. 日志区分 `relisten settle`(正常)/ `relisten quarantine`(异常),便于台架分辨触发路径。 + +**沉降值实测悬崖**(只清理不等待 = 0ms):0ms **无效**(4/4 仍截断)→ 30ms 仍失败(4/4)→ **100ms 通过**(多轮稳定)→ 取 100ms(对 30ms 重试节拍 `NET_RELISTEN_RETRY_MS` 留 3× 余量)。 + +**验证(第二阶段后;设备日志 `UPLOAD '' N bytes OK` 为权威真值)**: + +| 用例 | 修复前 | 修复后 | +|---|---|---| +| FIN 关闭后**首笔**上传 ×8 | **0/8**(实存 2048 + 回报 226) | **8/8**(实存 4096,sha OK) | +| 纯 `close()` 变体 ×4 | 中招 | **4/4** | +| `ftp_abort_probe --graceful` ×5 | **0/5** | **5/5** | +| `ftp_abort_probe`(RST)×5 | 5/5(一阶段) | **5/5** | +| 无中止对照 ×4 / 一次性连发 3 笔 ×1 | 4/4 / 第 1 笔坏 | **4/4 / 3/3** | +| 正常往返 freeze 4096×4 / 64KB×2 | 通过 | **4/4 / 2/2**(t_stor 0.68→**0.18s**,t_retr 1.13→**0.19s**) | +| 非 FTP `:7003` sink 复用 ×8(RST+FIN) | 8/8 | **8/8** | +| Trap 22:`:21` 0ms×50 / 100ms×50,`:7003` 300ms×10,`:8888` 0ms×20 | 50/50 / 50/50 / 10/10 / 20/20 | **50/50 / 50/50 / 10/10 / 20/20** | +| 启动门禁 / 控制台 / 堆 | — | `App init OK` / `net init: OK`,FORBID **0**,`CONSOLE dropped 0`,`heap free=8776 min_ever=4816` | + +**结论**:缺陷位于 **fd 复用生命周期**(芯片上一条连接的残留状态投递给下一条连接),不在 FTP 命令逻辑——lftpd 只是把它放大成误报 226。RST/TIMEOUT 半边由一阶段隔离处理,DISCONNECT 半边由本阶段沉降处理,合计覆盖全部"收过数据后关闭"的释放路径。已知残余边界:中止那一笔 STOR 自身仍回报 226(STOR 无长度声明、在途 FIN 与正常完成不可区分)——后续硬化见待办 #3。 + +**对应文件**:`Drivers/BSP/NET/net_socket.c`(`handle_disconnect_event` 沉降登记 + `net_relisten_locked` 两级沉降);`MDK-ARM/ftp_abort_probe.py` / `MDK-ARM/bench_diag_abort_gap.py`(边界甄别与回归判据)。