From dd51221ebc8ebd3080eaed62f8006e3b7dd612d8 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Wed, 14 Oct 2026 18:17:49 +0800 Subject: [PATCH] =?UTF-8?q?docs(NET):=20net=5Flistener.c=20=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E7=B2=BE=E7=AE=80=20=E2=88=9244%=EF=BC=88=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=80=90=E8=A1=8C=E6=9C=AA=E6=94=B9=EF=BC=89+=20?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E6=8C=87=E5=90=91=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 动机:508 行里注释占 277(54%),其中大量是逐行复述代码的噪音(/* 当前 tick */、/* 置 BUSY(结果回传调用方) */ ×4、/* 交重试轮询(30ms 后复检) */ ×3 等),以及把整段陷阱推演塞在调用点旁边。 做法:删逐行复述;删各函数的"出口参数:无"字段;陷阱叙述压成"一句结论 + 指向文档"(→ 网络自愈架构设计 §4.3 / §4.10.3 / §4.2 / §11)。 保留的判断性内容(不是噪音,删了会重新踩坑):2KB 残渣静默截断、 SETTLE < DEADLINE 的时序约束、"只清理不等待 0ms 实测 4/4 仍截断"、严禁 osDelay 忙等、排事件必须在 LISTEN 之前(E14)、U12 对称 close、芯片已在听 绝不发 open、session_dead 刻意不清、clear 必须全清(跨轮残留)、scratch 勿 静态化、预算耗尽 ≠ 放弃、留痕只打首轮。 ⚠ 另删一条**自相矛盾的过期注释**("上限 NET_RELISTEN_SETTLE_MS 后强制 放行")——它与 E19 把上限降级为"只留痕"直接冲突,留着比删掉更危险。同时 在相关处补一条显式守卫:此处**绝不**读全局中断寄存器(原警告埋在 7 行 "已删除"说明里,等于没有)。 结果:508 → 386 行(注释 277 → 155,−44%)。**代码逐行未改**:剥注释后 HEAD 与工作版均为 204 行且逐行一致;@build 尺寸完全不变 (Code 212296 / RO 18740 / RW 1112 / ZI 154456)。 顺带修正文件头指向:周期判据 net_reconcile_tick 已于 63cf73b 移入 net_events.c,原写"见 net_socket.c"(.c / .h 各一处)。 --- Drivers/BSP/NET/net_listener.c | 340 +++++++++++---------------------- Drivers/BSP/NET/net_listener.h | 2 +- 2 files changed, 110 insertions(+), 232 deletions(-) diff --git a/Drivers/BSP/NET/net_listener.c b/Drivers/BSP/NET/net_listener.c index b289fd68..f39bd20e 100644 --- a/Drivers/BSP/NET/net_listener.c +++ b/Drivers/BSP/NET/net_listener.c @@ -1,32 +1,22 @@ /* * 模块名称:Network Listener Lifecycle - * 模块功能:监听器生命周期(真机专属)—— 维持 fd 处于 LISTEN 态的全部机制: - * 重建(open+listen)、重建前的沉降/隔离(TRAP-NET-18)、排空 RX 残渣、 - * D10 非阻塞重试。**本模块是唯一的执行者**:外部只登记请求,动作只在本 - * 模块的 net_listener_tick 一处发生(周期性"该不该重建"的判据仍在 - * net_reconcile_tick 形态③④,见 net_socket.c)。 + * 模块功能:监听器生命周期(真机专属)—— 维持 fd 处于 LISTEN 态的全部机制:重建 + * (open+listen)、重建前的沉降/隔离、排空 RX 残渣、D10 非阻塞重试。 + * **唯一执行者**:外部只登记请求,动作只在本模块 net_listener_tick 一处发生 + * ("该不该重建"的周期判据在 net_reconcile_tick 形态③④,见 net_events.c)。 * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 * 作者:王建锋 * 创建日期:2026-10-12 * 修改记录: * 2026-10-12 王建锋 从 net_socket.c 整体抽出监听生命周期(N-32 §10 模块化) - * 2026-10-13 王建锋 自愈收敛阶段 3:pump 并入 net_reconcile_tick(《网络自愈架构设计》§4.1) - * 2026-10-14 王建锋 单一所有者收口(《网络自愈架构设计》§4.2 I2 加强):try 当场动作 - * + 四处调用点各自带门 → request 登记意图 + tick 唯一执行; - * 沉降留痕改"只留首末"(实测每次恢复 3 行 → 1 行) + * 2026-10-13 王建锋 自愈收敛阶段 3:pump 并入 net_reconcile_tick + * 2026-10-14 王建锋 单一所有者收口(I2 加强):try 当场动作 + 四处调用点各自带门 + * → request 登记意图 + tick 唯一执行;沉降留痕改"只留首末" + * 2026-10-14 王建锋 注释精简(−45%,代码零改动):陷阱详情沉淀进架构设计文档, + * 此处每条护栏只留一行结论 + 文档指针 * - * 职责与边界见 net_listener.h 头注释;设计依据见 - * docs/网络/网络自愈架构设计.md §11。 - * - * 模块内分层: - * ① 策略常量与私有状态(s_relisten_*) - * ② 机制:net_drain_chip_rx(排空)/ net_relisten_locked(重建一次,只由 tick 调) - * ③ 对外接口:settle / request / clear / tick - * - * 依赖(全部为单向,无反向 include): - * net_types.h(net_sock_t)/ net_socket.h(net_get_sock / NET_MAX_SOCKETS / - * net_set_errno / NET_ERR_* / net_pending_active)/ ch395f.h(驱动)/ - * dbg_log.h / main.h(HAL_GetTick) + * 设计依据 / 边界 / 接口契约 → docs/网络/网络自愈架构设计.md §4.2 §4.3 §11 + * ⚠ 本文件有 2 处"读即清"寄存器读(见 §4.10.2),改前三条准入判据 → 同文档 §4.10.5 */ #include #include @@ -47,27 +37,18 @@ /* ===== ① 策略常量与私有状态 ===== */ -/* TRAP-NET-18(2026-09-23 生产路径实测):异常释放(芯片 TIMEOUT,例如客户端中止上传) - * 后的**隔离期**。实测时序:中止时对端仍有在途数据 → 芯片 TIMEOUT(此时 MCU 侧 recv=0) - * 在途段随后落进芯片 RX 缓冲(约 2KB 残渣) - * 若立即 open+listen,残渣被下一条连接当作自己的数据读走 - * 新上传只收 2KB 就被判定结束、回报成功(静默截断) - * 处置:异常释放的 socket 先隔离 NET_RELISTEN_QUARANTINE_MS:期间持续排空 RX、主动 close 并排空中断 - * 等芯片回到 CLOSED/TCP_CLOSED;隔离期满才 open+listen - * 上限 NET_RELISTEN_SETTLE_MS 后强制放行,保证监听器不会永久失效 - * 正常 FIN 释放(handle_disconnect_event)不走此路径——既有行为零改动 - */ +/* TRAP-NET-18:异常释放(芯片 TIMEOUT,例如客户端中止上传)后必须**隔离**一段时间再重建。 + * 中止时对端仍有在途数据随后落进芯片 RX 缓冲(实测约 2KB 残渣);若立即 open+listen, + * 残渣会被下一条连接当作自己的数据读走 —— 上传只收 2KB 就"回报成功"(静默截断)。 + * 隔离期内持续排空 RX + 主动 close + 排中断,等芯片回 CLOSED/TCP_CLOSED 才重建。 + * 正常 FIN 释放走 GRACEFUL,既有行为零改动。完整时序与实测 → 架构设计 §4.3 */ #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 实测) */ -/* 重监听前"清芯片收尾状态"的两个等级(s_relisten_quarantine 取值,取值语义见 net_listener.h) - * 2026-09-24 台架实证:正常释放走的是既有"不复位芯片收尾状态"的路径 - * 残留的 DISCONNECT 会在复用同一 fd 的下一条连接上被投递(accept 后约 10~20ms) - * 下一条上传被静默截断到 2048B(一个芯片 RX 缓冲)且回报 226 - * 只清理不等待(0ms)实测无效(4/4 仍截断):芯片需要时间完成上一条连接的内部收尾 - * 给足沉降即 4/4 通过 - */ +/* 沉降等级(s_relisten_quarantine 取值;对外语义见 net_listener.h)。 + * ⚠ 正常释放**不得**退化成"只清理不等待"(0ms):2026-09-24 台架实测 4/4 仍把上一条残留 + * DISCONNECT 投到下一条连接上(accept 后 10~20ms)、上传静默截断到 2048B;给足沉降才 4/4 */ #define NET_RELISTEN_SETTLE_QUARANTINE 1U #define NET_RELISTEN_SETTLE_GRACEFUL 2U @@ -76,12 +57,9 @@ static uint32_t s_relisten_settle_start[NET_MAX_SOCKETS]; /* 隔离起点(0 = static uint32_t s_relisten_settle_dead[NET_MAX_SOCKETS]; /* 隔离上限到期时刻 */ static uint8_t s_relisten_capped[NET_MAX_SOCKETS]; /* 1=上限留痕已打(只留首末,防 30ms 刷屏) */ -/* ===== TRAP-NET-09 非阻塞重监听重试(D10 修复,2026-09-22 M3 实测) ===== - * 旧实现:open 失败 → close → osDelay(500) → 重试,netTask 忙等 500ms(全网冻结) - * 且实测恢复 588ms、期间客户端重连被拒 - * 新实现:外部只登记请求(net_listener_request),由 netTask 每轮 net_listener_tick 执行 - * 恢复时间 ≈ NET_RELISTEN_RETRY_MS 量级,任何时刻都不阻塞任务 - */ +/* TRAP-NET-09 / D10(2026-09-22 M3 实测):重建**必须非阻塞**。旧实现 open 失败 → close → + * osDelay(500) → 重试,netTask 忙等 500ms(全网冻结)、实测恢复 588ms 且期间重连被拒。 + * 现由外部登记意图、本模块 tick 到点重试 —— 恢复 ≈ RETRY_MS 量级,任何时刻不阻塞任务。 */ #define NET_RELISTEN_RETRY_MS 30U /* 重试间隔(TRAP-NET-09 沉降窗口;实测 100ms 首次必成, * 收紧到 30ms 以缩短恢复时间——未成则下一轮到点再来) */ #define NET_RELISTEN_DEADLINE_MS 2000U /* 放弃时限(自首次尝试起) */ @@ -96,103 +74,73 @@ static lat_tmark_t s_relisten_mark[NET_MAX_SOCKETS]; /* 恢复计时起点( /* ===== ② 机制 ===== */ /* - * 函数功能:清空芯片该 socket 的 RX 残留(TRAP-NET-18) - * 入口参数:sockfd - Socket 描述符 (0~7) int - * 出口参数:无 + * 函数功能:清空芯片该 socket 的 RX 残留(TRAP-NET-18;数据已无意义,直接丢弃) + * 入口参数:sockfd - Socket 描述符 (0~7) * 返回值:无 - * 限定条件:netTask 上下文;socket 即将 close/open(数据已无意义,丢弃) - * 函数说明:1. 中止/异常断开时芯片 RX 缓冲可能残留上一条连接的数据(实测残留约一个缓冲 = 2048B) - * 2. 残渣带入下一条连接会使其在约 2KB 处被芯片拆链(上传被静默截断) - * 3. 关闭前主动读空即清除;分块读避免新增静态缓冲 - * (2026-10-09 实测:ARMCC 把本 scratch 与同函数内生命周期不重叠的 - * 192B 日志缓冲叠放,局部缓冲边际栈成本仅 ~16B,静态化反亏 256B BSS,勿再"优化") + * 限定条件:netTask 上下文;socket 即将 close/open + * 函数说明:1. 分块读空,不新增静态缓冲。⚠ **勿把 scratch 静态化"省栈"**:2026-10-09 实测 + * ARMCC 会把它与同函数内生命周期不重叠的 192B 日志缓冲叠放,边际栈成本仅 + * ~16B;静态化反亏 256B BSS。 + * 2. 残渣危害与隔离期处置 → 文件头 TRAP-NET-18 与架构设计 §4.3。 */ static void net_drain_chip_rx(int sockfd) { - /* 复用分块缓冲(256B/块,避免新增静态缓冲) */ uint8_t scratch[256]; - /* 上限 16×256 = 4KB(缓冲仅 2KB),防死循环 */ + /* 上限 16×256 = 4KB(芯片缓冲仅 2KB),防死循环 */ for (int guard = 0; guard < 16; guard++) { - /* 查询芯片 RX 可用字节数 */ int n = (int)ch395f_get_recv_len((uint8_t)sockfd); - /* 已排空 → 结束 */ if (n <= 0) { break; } - /* 单块以缓冲容量为上限 */ if (n > (int)sizeof(scratch)) { n = (int)sizeof(scratch); } - /* 丢弃该块(读即清) */ - ch395f_read_recv_buf((uint8_t)sockfd, scratch, (uint16_t)n); + ch395f_read_recv_buf((uint8_t)sockfd, scratch, (uint16_t)n); /* 读即弃 */ } } /* - * 函数功能:单连接模式断连后自动重新进入 LISTEN(不清空控制块,保留 fd) - * 入口参数:sockfd - 已断开(CLOSED)的 TCP 监听型 Socket 描述符 (0~7) int - * 出口参数:无 - * 返回值:0 - 成功,-1 - 失败 + * 函数功能:单连接模式断连后重新进入 LISTEN(不清空控制块、保留 fd) + * 入口参数:sockfd - 已断开(CLOSED)的 TCP 监听型 Socket 描述符 (0~7) + * 返回值:0 - 成功,-1 - 失败(BUSY,交 tick 30ms 后重试) * 限定条件:netTask 上下文;控制块 in_use == 1;保留 local_port/auto_relisten - * 函数说明:1. 复用原本地端口与协议,重新 OPEN + TCP_LISTEN - * 2. 仅将 state 置回 LISTENING,不 memset(fd 与 auto_relisten 保持不变) - * 3. 实现 BSD 风格"监听 Socket 常驻",应用层无需手动重监听 - * 4. **单次尝试**:失败立即返回(不 osDelay 忙等) - * 5. **只由 net_listener_tick 调用**(唯一执行者);TRAP-NET-09 沉降由它的 - * 30ms 节奏重试(见文件末 net_listener_tick) - * 6. 旧实现在此 osDelay(500) 忙等,netTask 冻结 500ms、实测恢复 588ms(M3 台架 D10) - * 7. N-32 §10:fd 由参数传入(不再用 `p_sock - s_net_socks` 反推), - * 控制块经 net_get_sock() 取 —— 本模块**不访问** net_socket.c 的 static 数组 + * 函数说明:1. **单次尝试**,失败立即返回(严禁 osDelay 忙等,见文件头 D10)。 + * 2. **只由 net_listener_tick 调用**(唯一执行者)。 + * 3. 复用原本地端口重新 OPEN + LISTEN,仅把 state 置回 LISTENING、不 memset + * —— 实现 BSD 风格"监听 Socket 常驻",应用层无需手动重监听。 + * 4. fd 由参数传入(不用 `p_sock - s_net_socks` 反推),控制块经 net_get_sock() + * 取 —— 本模块**不访问** net_socket.c 的 static 数组(N-32 §10)。 */ static int net_relisten_locked(int sockfd) { - /* 芯片命令返回状态 */ uint8_t status; - /* Socket 控制块(fd 由调用方保证有效) */ net_sock_t *p_sock = net_get_sock(sockfd); - /* 控制块无效:拒绝(不应发生,调用方已判过) */ - if (p_sock == NULL) { + if (p_sock == NULL) { /* 不应发生,调用方已判过 */ return NET_ERR_RETURN_1(NET_ERR_BADF); } /* 释放隔离期(TRAP-NET-18):重监听前先清芯片收尾状态 */ if (s_relisten_quarantine[sockfd] != 0U) { - /* 芯片状态两字节:[0]=Socket,[1]=TCP */ - uint8_t st[2] = {0, 0}; - /* 当前 tick */ + uint8_t st[2] = {0, 0}; /* [0]=Socket 状态,[1]=TCP 状态 */ uint32_t now = HAL_GetTick(); - /* 隔离等级(1=异常释放 2=正常释放) */ uint8_t lvl = s_relisten_quarantine[sockfd]; - /* 是否隔离期首轮:留痕只打首轮(E0 结论「重试日志只留首末」,2026-10-14 落地) */ + /* 留痕只打首轮(E0「重试日志只留首末」) */ uint8_t first = (s_relisten_settle_start[sockfd] == 0U) ? 1U : 0U; - /* 最短等待由释放来源决定(NET_RELISTEN_SETTLE_*):异常释放 400ms(等在途数据落尽) - * 正常释放 100ms(等芯片内部收尾) */ + /* 最短等待按释放来源:异常 400ms(在途数据落尽)/ 正常 100ms(芯片内部收尾) */ 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) { - /* 隔离起点 = 本 tick */ + if (s_relisten_settle_start[sockfd] == 0U) { /* 首轮:记起点与上限 */ s_relisten_settle_start[sockfd] = now; - /* 隔离上限 = 起点 + 800ms */ s_relisten_settle_dead[sockfd] = now + NET_RELISTEN_SETTLE_MS; } - /* 隔离期内持续排空 RX 残渣 */ - net_drain_chip_rx(sockfd); - /* 主动关:清旧连接收尾状态 */ - ch395f_close_socket((uint8_t)sockfd); - /* 读即清:本 Socket 中断状态寄存器 */ - (void)ch395f_get_sock_int_status((uint8_t)sockfd); - /* F-NET-15 M4-lite(自愈专项实验 2026-10-12):删除沉降段的全局读即清—— - * ① 违反模块约定「所有 CH395F 中断在 net_poll 内集中读取并清除」; - * ② 全局寄存器整体读即清无按位手段(B3 实证),每 30ms 重试一轮即搁浅 - * 其它在用 Socket 的 CONNECT/DISCONNECT/RECV_OK(net_poll 门控焊死); - * ③ M1 使沉降成为每次断连热路径后,该搁浅被放大为 FTP 控制/数据通道 - * 互杀(E7 实测:lftpd 5s 超时、closed-with-data 救回全部迟到)。 - * 本 Socket 残留全局位交 net_poll 正常读到时 SINT 已为 0,自然收尾 */ - /* 读芯片当前状态(两字节) */ + net_drain_chip_rx(sockfd); /* 隔离期内持续排空 RX 残渣 */ + ch395f_close_socket((uint8_t)sockfd); /* 主动关:清旧连接收尾状态 */ + (void)ch395f_get_sock_int_status((uint8_t)sockfd); /* 读即清:只碰本 Socket */ + /* ⚠ 此处**绝不**读全局中断寄存器:它整体读即清、无按位手段,每 30ms 一轮会把 + * 其它在用 Socket 的门焊死(F-NET-15 M4-lite 删的就是它 → 架构设计 §4.10.3) */ ch395f_get_socket_status((uint8_t)sockfd, st); /* 芯片尚未回到 CLOSED/TCP_CLOSED,或未满最短等待 */ @@ -211,98 +159,69 @@ static int net_relisten_locked(int sockfd) { /* 交重试轮询(30ms 后复检) */ return -1; } - /* 隔离上限已至:**只留痕,不强行放行**(E19 反事实 A/B 实测,2026-10-14): - * 芯片未回 CLOSED 时强行 open 注定吃 USE 错(TRAP-NET-09),放行与不放行的 - * 端口恢复时刻同一分布(+512 vs +505/533 ms)——恢复由"芯片何时回 CLOSED"决定。 - * 故继续留在隔离期边等边排渣,交 D10 重试轮询。**"监听器不会永久失效"的真正 - * 兜底 = D10 预算到期 + 对账形态④ 重新登记**(构建 B 实测:抑制后仍持续重试 - * 并最终恢复),不是这里放行——勿再把本处当兜底。 - * ⚠ 上限留痕只打一次:每 30ms 重试一轮若都留痕会刷屏(构建 B 实测两行/30ms) */ + /* 隔离上限已至:**只留痕,不强行放行**(E19 反事实 A/B 实测)——芯片未回 CLOSED 时 + * 强行 open 注定吃 USE 错(TRAP-NET-09),放行与不放行的端口恢复时刻同一分布 + * (+512 vs +505/533 ms)。"监听器不会永久失效"的真正兜底 = **D10 预算到期 + + * 对账形态④ 重新登记**,不是这里放行。留痕只打一次(否则每 30ms 两行刷屏)。 + * ⚠ 勿再把本处当兜底 → 架构设计 §4.3 / §5.2 E19 */ if (s_relisten_capped[sockfd] == 0U) { s_relisten_capped[sockfd] = 1U; DBG_INFO("sock%d relisten %s timeout, keep waiting (sock=0x%02X tcp=0x%02X)", sockfd, tag, st[0], st[1]); } - /* 置 BUSY(结果回传调用方) */ (void)net_set_errno(NET_ERR_BUSY); - /* 交重试轮询(30ms 后复检);隔离标志**不清** → 下轮继续排空残渣 */ + /* 隔离标志**不清** → 下一轮继续排空残渣 */ return -1; } - /* 隔离完成(芯片已回 CLOSED 且已满最短等待) */ + /* 隔离完成:芯片已回 CLOSED 且已满最短等待 */ s_relisten_quarantine[sockfd] = 0U; - /* 清隔离起点(回到"未开始") */ s_relisten_settle_start[sockfd] = 0U; - /* 清上限时刻 */ s_relisten_settle_dead[sockfd] = 0U; - /* 清上限留痕标志(下一轮隔离可重新留一次痕) */ - s_relisten_capped[sockfd] = 0U; + s_relisten_capped[sockfd] = 0U; /* 下一轮隔离可重新留一次痕 */ } - /* 重置协议类型为 TCP */ ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_TCP); - /* 本地端口(沿用原监听) */ - ch395f_set_sour_port((uint8_t)sockfd, p_sock->local_port); + ch395f_set_sour_port((uint8_t)sockfd, p_sock->local_port); /* 沿用原本地端口 */ - /* 打开芯片侧 Socket */ status = ch395f_open_socket((uint8_t)sockfd); - /* open 失败 → BUSY(TRAP-NET-09:芯片需沉降时间才能重开) */ if (status != CH395F_ERR_SUCCESS) { - /* TRAP-NET-09:断开后芯片需沉降时间才能重新 open。此处只清理并返回 - * (严禁 osDelay 忙等)→ 由重试轮询到点再来。 */ - /* 先关闭,交重试轮询(30ms 后复检) */ + /* TRAP-NET-09:断开后芯片需沉降时间才能重开 → 只清理并返回(严禁 osDelay 忙等), + * 由 tick 30ms 后重试 */ ch395f_close_socket((uint8_t)sockfd); - /* 置 BUSY(结果回传调用方) */ (void)net_set_errno(NET_ERR_BUSY); - /* 交重试轮询(30ms 后复检) */ return -1; } - /* 排事件必须在 LISTEN 命令**之前**(E14 复核 2026-10-14:listen 后读清会把 - * 快速复连新连接的 CONNECT 吞掉——与 net_listen_locked 同源同改法;B3 机制 - * 与 git 考古见该处注释)。本处是每次断连 auto-relisten 的热路径:只清本 - * Socket,全局交 net_poll 集中读清(AGENTS 模块约定)。 - * 读即清:防上一条会话残留 DISCONNECT/TIMEOUT 误触发;挪后若仍有滞后事件 - * 落进新会话,事件处理器回读状态裁决(状态面为真相),无害 */ + /* ⚠ 排事件必须在 LISTEN 命令**之前**:listen 后读清会把快速复连新连接的 CONNECT 吞掉 + * (E14 定案,与 net_listen_locked 同源同改法)。只清本 Socket,全局交 net_poll + * (§4.10.2)。要清的正是上一条会话残留的 DISCONNECT/TIMEOUT;万一仍有滞后事件落进 + * 新会话,事件处理器回读状态裁决(状态面为真相),无害 → 架构设计 §4.10.3 */ (void)ch395f_get_sock_int_status((uint8_t)sockfd); - /* 下发 TCP LISTEN 命令 */ status = ch395f_tcp_listen((uint8_t)sockfd); - /* LISTEN 失败 → BUSY(同样交重试) */ if (status != CH395F_ERR_SUCCESS) { - /* 0-3(自愈收敛阶段0,U12 对称):open 已成而 LISTEN 失败——不 close 会把 - * 芯片 Socket 卡在"OPEN 无监听",下一轮 open 返 USE 又进失败分支 - * (U12 同源事故形态)。close 交重试轮,与 open 失败分支同形 */ + /* U12 对称:open 已成而 LISTEN 失败若不 close,芯片 Socket 会卡在"OPEN 无监听", + * 下一轮 open 返 USE 又进本分支(U12 同源事故形态)→ 与 open 失败分支同形处理 */ ch395f_close_socket((uint8_t)sockfd); - /* 置 BUSY(结果回传调用方) */ return NET_ERR_RETURN_1(NET_ERR_BUSY); } - /* 状态置回 LISTENING(fd 保留) */ + /* 状态置回 LISTENING(fd 保留;auto_relisten 保持为 1,下次断连仍自动重监听) */ p_sock->state = NET_SOCK_STATE_LISTENING; - /* N-32(P1,设计文档 §5 P1.1 的关键约定):此处**刻意不清 session_dead**。 - * 重建监听只是"芯片侧恢复就位",与"应用是否已确认上一条会话结束"是两件事; - * 清了它,抢先 relisten 之后消费者就再也得不到 EOF(B10"消费者卡死"原型)。 - * session_dead 只由新 CONNECT 成功(handle_connect_event)或 memset - * (net_close/net_socket)清零。 */ + /* ⚠ 此处**刻意不清 session_dead**(N-32 P1):重建监听只是"芯片侧恢复就位",与 + * "应用是否已确认上一条会话结束"是两件事;清了它,抢先 relisten 之后消费者就再也 + * 得不到 EOF(B10"消费者卡死"原型)。它只由新 CONNECT 成功或 memset 清零。 */ /* 新监听不得继承上一条连接的释放原因(TRAP-NET-18 待办 #3) */ p_sock->released_abnormal = 0U; - /* N-30(TRAP-NET-20):清零前把对端端口转登为幽灵甄别基准 */ + /* N-30(TRAP-NET-20):清零前把对端端口转登为幽灵甄别基准;去抖计数随之清零 */ p_sock->last_peer_port = p_sock->remote_port; - /* N-30:新监听会话,事件未收到去抖计数清零 */ p_sock->lost_conn_seen = 0U; - /* 清对端端口(新连接待用) */ + /* 以下四项 = 清上一条的对端信息,新连接待用 */ p_sock->remote_port = 0; - /* 清对端 IP */ p_sock->remote_ip = 0; - /* 清对端 IP 数组 */ memset(p_sock->remote_ip_arr, 0, sizeof(p_sock->remote_ip_arr)); - /* 新开 Socket 发送缓冲按占用计(由 SEND_OK 清除) */ - p_sock->send_ready = 0; - /* 清接收字节统计(新连接) */ + p_sock->send_ready = 0; /* 新开 Socket 发送缓冲按占用计(由 SEND_OK 清除) */ p_sock->recv_bytes = 0; - /* auto_relisten 保持为 1,下次断连仍自动重监听 */ - /* 成功留痕由唯一执行者 net_listener_tick 打(带登记来源):本函数此前也打一行, - * 与 tick 的"retry ok"是同一事实的两行(实测每次恢复 2 行 → 合并为 1 行) */ - /* 重监听成功 */ + /* 成功留痕由唯一执行者 net_listener_tick 打(带登记来源),本函数不再重复 */ return 0; } @@ -310,151 +229,118 @@ static int net_relisten_locked(int sockfd) { /* * 函数功能:登记「本 fd 的会话已结束,重建监听前需沉降/隔离」(契约见 net_listener.h) - * 入口参数:sockfd - Socket 描述符 (0~7) int + * 入口参数:sockfd - Socket 描述符 (0~7) * level - 沉降等级(net_listener_settle_t,由释放来源决定) - * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文;由 handle_disconnect_event / handle_timeout_event 调用 - * 函数说明:1. 只置事实,不做动作 —— 动作只在本模块 net_listener_tick 一处发生 - * 2. 越界 sockfd 静默忽略 + * 函数说明:只置事实、不做动作(动作只在 net_listener_tick 一处发生);越界静默忽略。 */ void net_listener_settle(int sockfd, net_listener_settle_t level) { - /* 描述符越界:静默忽略,不设错误码 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } - /* 登记沉降/隔离等级 —— 只置事实,重建由 reconciler / try 负责 */ s_relisten_quarantine[sockfd] = (uint8_t)level; } /* - * 函数功能:清除本 fd 的待重监听重试状态(关闭/新建 fd 时调用) + * 函数功能:清除本 fd 的重建 / 沉降状态(fd 关闭或新建时调用) * 入口参数:sockfd - Socket 描述符 (0~7) - * 出口参数:无 * 返回值:无 * 限定条件:无 - * 函数说明:1. 清重建请求登记与重试时刻,fd 关闭/新建时调用(防残留请求) + * 函数说明:⚠ 必须**全清**:2026-09-24 台架实测,漏清隔离标志会让同 fd 的下一生成继承 + * 上一轮的 400/800ms 等待(跨轮残留);上限留痕标志漏清则下一生成静默无声。 */ void net_listener_clear(int sockfd) { - /* 描述符越界:直接返回(本函数不设错误码) */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } - /* 清重建请求登记 */ s_relisten_req[sockfd] = 0U; - /* 清下次重试时刻 */ s_relisten_next[sockfd] = 0U; - /* 清放弃时刻 */ s_relisten_dead[sockfd] = 0U; - /* TRAP-NET-18:一并清沉降/隔离状态 */ s_relisten_settle_dead[sockfd] = 0U; - /* 清沉降起点 */ s_relisten_settle_start[sockfd] = 0U; - /* 清上限留痕标志(防同 fd 下一生成继承"已留痕"而静默) */ s_relisten_capped[sockfd] = 0U; - /* 一并清隔离标志:关闭/重建即确定性释放, - * 防同 fd 下一生成继承上轮的 400/800ms - * 隔离等待(2026-09-24 台架发现跨轮残留) */ s_relisten_quarantine[sockfd] = 0U; } /* - * 函数功能:标记重监听完成(清重试状态;探针构建下报告恢复计时样本) + * 函数功能:标记重监听完成(清重试状态;探针构建下上报恢复计时样本) * 入口参数:sockfd - Socket 描述符 (0~7) - * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文 - * 函数说明:1. 重监听成功收尾:清重试状态;探针构建(TEST_LATENCY_PROBE)下上报恢复计时样本 + * 函数说明:探针样本 = 首次尝试 → 成功的耗时(仅 TEST_LATENCY_PROBE 构建)。 */ static void net_relisten_done(int sockfd) { - /* 清重试状态(复用清除函数) */ net_listener_clear(sockfd); #if defined(TEST_LATENCY_PROBE) - /* 上报恢复计时样本(首次尝试 → 成功) */ lat_probe_add_seg("relisten", lat_mark_us(&s_relisten_mark[sockfd])); #endif } /* * 函数功能:登记「本 fd 期望回到 LISTEN」(**只登记意图,不碰芯片**;契约见 net_listener.h) - * 入口参数:sockfd - Socket 描述符 (0~7) int + * 入口参数:sockfd - Socket 描述符 (0~7) * reason - 登记来源(net_listener_req_t,仅用于留痕归因) - * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文(API 路径与自愈 tick 内) - * 函数说明:1. **取代原 net_listener_try**:try 是"当场动作",于是每个调用点都得 - * 自带状态门(U12 事故正是四处各防各的、漏一处就把活监听拆掉); - * request 只置意图,门集与"芯片在听就不许 open"收进 net_listener_tick - * 2. 已在请求中:不动重试时刻 —— 否则 10ms 一次的 accept 轮询会不断把 - * 重试时刻往后推 → 永不重试(重试饥饿,原 try 的同名防护保留) - * 3. 非阻塞:实际重建发生在下一轮 net_listener_tick(≤1ms) - * 4. 越界 sockfd 静默忽略(无返回值,不设错误码) + * 函数说明:1. **取代原 net_listener_try**:try 是"当场动作",每个调用点都得自带状态门 + * (U12 事故正是四处各防各的、漏一处就把活监听拆掉);门集与"芯片在听就不许 + * open"全部收进 net_listener_tick 一处。 + * 2. 已在请求中**不动重试时刻**:否则 10ms 一次的 accept 轮询会不断把重试时刻 + * 往后推 → 永不重试(重试饥饿)。 + * 3. 非阻塞:实际重建发生在下一轮 tick;越界静默忽略。 */ void net_listener_request(int sockfd, net_listener_req_t reason) { - /* 描述符越界:静默忽略 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } - /* 已在请求中:保持既有节奏,不重置(防重试饥饿) */ - if (s_relisten_req[sockfd] != 0U) { + if (s_relisten_req[sockfd] != 0U) { /* 已在请求中:保持既有节奏(防重试饥饿) */ return; } - /* 登记来源(+1:0 保留给"无请求") */ - s_relisten_req[sockfd] = (uint8_t)((uint8_t)reason + 1U); - /* 立即尝试(下一轮 tick);放弃时限自首次尝试起算,故此处清零 */ - s_relisten_next[sockfd] = 0U; - s_relisten_dead[sockfd] = 0U; + s_relisten_req[sockfd] = (uint8_t)((uint8_t)reason + 1U); /* +1:0 保留给"无请求" */ + s_relisten_next[sockfd] = 0U; /* 下一轮 tick 立即尝试 */ + s_relisten_dead[sockfd] = 0U; /* 放弃时限自首次尝试起算,故清零 */ } /* * 函数功能:监听重建的**唯一执行者**(netTask 每轮调用一次,非阻塞) * 入口参数:无 - * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) - * 函数说明:1. 遍历已登记请求的 fd,过**一处**门集后执行 open+listen;原名 - * net_listener_retry_tick(只做 D10 到点重试),现在它是唯一执行路径 - * 2. 门集(此前散在 4 个调用点,各防各的):控制块有效 + in_use + - * 状态 ∈ {LISTENING, CLOSED}(0-4 口径:已转 ESTABLISHED = 新会话抢先, - * 继续跑会 open→close 摧毁活连接)+ 监听型(auto_relisten / STREAM / - * local_port≠0,与原 pump 认领口径一致) - * 3. **芯片已在 LISTEN = 请求已达成,静默了结、绝不发 open**:U12 事故 - * (对在听新 SYN 的 Socket 再 open → 返 USE → 失败分支 close → 当场 - * 拆掉活监听)的结构性防护;读驱动状态缓存(net_poll 每轮刷新),零 SPI - * 4. 挂起保险:本 fd 有在途挂起 recv / send 时跳过(重开 Socket 会摧毁 - * 在途传输,net_pending_active / net_send_pending_active) - * 5. 节奏 30ms / 预算 2s(自首次尝试起);超预算清登记并留痕——监听不会 - * 永久失效,reconciler 100ms 周期仍按"芯片不在听"重新登记(形态③④) + * 函数说明:1. 遍历已登记请求的 fd,过**一处**门集后执行 open+listen。门集(此前散在 + * 4 个调用点各防各的):控制块有效 + in_use + 状态 ∈ {LISTENING, CLOSED} + * (已转 ESTABLISHED = 新会话抢先,继续跑会 open→close 摧毁活连接)+ + * 监听型(auto_relisten / STREAM / local_port≠0)。 + * 2. ⚠ **芯片已在 LISTEN = 请求已达成,静默了结、绝不发 open**:U12 事故 + * (对在听新 SYN 的 Socket 再 open → 返 USE → 失败分支 close → 当场拆掉 + * 活监听)的结构性防护。读驱动状态缓存(net_poll 每轮刷新),零 SPI。 + * 3. 挂起保险:本 fd 有在途挂起 recv / send 时跳过(重开会摧毁在途传输)。 + * 4. 节奏 30ms / 预算 2s(自首次尝试起);超预算清登记并留痕——监听不会永久 + * 失效,reconciler 100ms 周期仍按"芯片不在听"重新登记(形态③④)。 */ void net_listener_tick(void) { - /* 当前 tick */ uint32_t now = HAL_GetTick(); - /* Socket 循环索引 */ int i; /* 登记来源名(留痕分辨是谁登记的;与 net_listener_req_t 同序) */ static const char *const k_req_name[] = { "recon", "accept", "eof", "drop", "pending" }; for (i = 0; i < NET_MAX_SOCKETS; i++) { - /* 本 fd 无重建请求:跳过 */ if (s_relisten_req[i] == 0U) { continue; } /* --- 门集:不满足即"登记已陈旧",清掉让位 --- */ { - /* 取控制块 */ const net_sock_t *p_sock = net_get_sock(i); - /* 控制块失效 / 未在用 / 状态不允许 / 非监听型 fd:陈旧登记 */ if ((p_sock == NULL) || (p_sock->in_use == 0U) || ((p_sock->state != NET_SOCK_STATE_LISTENING) && (p_sock->state != NET_SOCK_STATE_CLOSED)) || (p_sock->auto_relisten == 0U) || (p_sock->type != NET_SOCK_STREAM) || (p_sock->local_port == 0U)) { - /* 陈旧登记:清状态让位 */ net_listener_clear(i); continue; } - /* 芯片已在听:请求已达成(见函数说明 3),静默了结 */ + /* 芯片已在听 → 请求已达成,静默了结(见函数说明 2) */ { ch395f_sock_state_t c = ch395f_sock_state_cached((uint8_t)i); if ((c.open != CH395F_SOCKET_CLOSED) && @@ -464,7 +350,7 @@ void net_listener_tick(void) { } } } - /* 本 fd 有在途挂起 recv / 挂起 send:重开 Socket 会摧毁在途连接(挂起保险) */ + /* 挂起保险:在途 recv/send 会被重开的 Socket 摧毁 */ if ((net_pending_active(i) != 0U) || (net_send_pending_active(i) != 0U)) { continue; } @@ -474,35 +360,27 @@ void net_listener_tick(void) { } /* 已到放弃时限(0 = 尚未起算,见下) */ if ((s_relisten_dead[i] != 0U) && ((int32_t)(now - s_relisten_dead[i]) >= 0)) { - /* 清重试状态 */ net_listener_clear(i); - /* D10 快速重试预算耗尽留痕:后续交 reconciler 形态③④ 周期接管 - * (N-32 单一重建入口),监听不会永久失效——旧文案"give up" - * 夸大了失效语义 */ + /* 预算耗尽 ≠ 放弃:交 reconciler 形态③④ 周期接管(N-32 单一重建入口), + * 监听不会永久失效——旧文案 "give up" 夸大了失效语义 */ DBG_ERROR("sock%d relisten retry budget exhausted, reconciler takes over", i); continue; } - /* 首次尝试:起算放弃时限与恢复计时起点 */ - if (s_relisten_dead[i] == 0U) { + if (s_relisten_dead[i] == 0U) { /* 首次尝试:起算放弃时限 + 恢复计时起点 */ s_relisten_dead[i] = now + NET_RELISTEN_DEADLINE_MS; #if defined(TEST_LATENCY_PROBE) - /* 恢复计时自首次尝试起 */ lat_mark(&s_relisten_mark[i]); #endif } - /* 执行一次重建(本函数是 net_relisten_locked 的唯一调用者) */ if (net_relisten_locked(i) == 0) { /* 登记来源名(越界兜底:枚举扩充时不得越界读) */ uint8_t idx = (uint8_t)(s_relisten_req[i] - 1U); const char *name = (idx < (uint8_t)(sizeof(k_req_name) / sizeof(k_req_name[0]))) ? k_req_name[idx] : "?"; - /* 留痕:监听器重建完成(带登记来源;成功留痕已从 net_relisten_locked 收并到此一处) */ DBG_INFO("sock%d auto-relisten ok (%s)", i, name); - /* 标记完成(清状态 + 探针样本) */ net_relisten_done(i); } else { - /* 仍未成:顺延 30ms 再来 */ - s_relisten_next[i] = now + NET_RELISTEN_RETRY_MS; + s_relisten_next[i] = now + NET_RELISTEN_RETRY_MS; /* 仍未成:顺延 30ms */ } } } diff --git a/Drivers/BSP/NET/net_listener.h b/Drivers/BSP/NET/net_listener.h index 069c7990..2ec9daab 100644 --- a/Drivers/BSP/NET/net_listener.h +++ b/Drivers/BSP/NET/net_listener.h @@ -4,7 +4,7 @@ * LISTEN 态的全部机制:重建、重建前的沉降/隔离(TRAP-NET-18)、排空 RX * 残渣、D10 非阻塞重试。**本模块是"把 fd 放回 LISTEN"的唯一执行者**: * 外部只登记请求(net_listener_request),动作只在 net_listener_tick 一处发生; - * "该不该重建"的周期判据仍在 net_reconcile_tick(形态③④,见 net_socket.c)。 + * "该不该重建"的周期判据仍在 net_reconcile_tick(形态③④,见 net_events.c)。 * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 * 作者:王建锋 * 创建日期:2026-10-12