From c1ab902ef1aae014f126654436d3cd57b0d4b36a Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Mon, 12 Oct 2026 16:59:44 +0800 Subject: [PATCH] =?UTF-8?q?refactor(net):=20N-32=20=C2=A710=20=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E6=8A=BD=E4=B8=BA?= =?UTF-8?q?=20net=5Flistener.c=EF=BC=88=E7=BA=AF=E6=90=AC=E7=A7=BB?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把监听生命周期从 net_socket.c 整体抽出为真机专属模块 net_listener.c: 7 个函数 + 7 个状态数组 + 8 个常量 ≈470 行。 net_socket.c 4126 → 3656 行(-471)。 模块边界(详见 net_listener.h 头注释): - 搬入:relisten_locked / try / clear / done / retry_tick / pump / drain_chip_rx - 留在 net_socket.c:net_listen_locked(应用 API 路径)/ net_accept_locked (产出连接)/ net_lost_connect_probe(补 accept)/ net_listener_armed (双端公开 API)/ net_resync_listen / net_reconcile_tick / net_sock_t 字段 接口从「8 处散点直调 + 2 处直接写状态数组」收敛为 4 个窄接口: net_listener_settle(fd, level) / _try(fd) / _clear(fd) / _retry_tick() 新模块**不访问 s_net_socks**(改用 net_get_sock + ch395_sock)。 搬移中必要的两处可见性提升(非纯机械搬移,已注释留痕): - net_set_errno 由 static 提升为模块间可见(NET_ERR_RETURN_1 宏随迁头文件) - 新增 net_pending_active(fd),替代直接读 s_net_pending[i].active (L3 保险语义原样保留) 真机专属:net_listener.c 依赖 ch395f_* SPI 驱动,sim(Winsock)无对应 → 只注册 Keil 端,并在 check_registration.py 的 SIM_EXEMPT **显式点名** (不整目录一刀切,遵脚本既定原则)。 验证(纯搬移的可证判据): - 尺寸:**RW-data 980 与 ZI-data 143060 完全不变**(数据总量不变); Code 197236→197228(-8)、RO-data 17012→17000(-12)——接口改造的 极小开销,相对 471 行搬移可忽略 - 门禁全绿(cppcheck / C 规范 / 双端注册 / 主机侧单测 12 套 / mem-numbers) - @test PASS + 回读 No difference found - 全量回归::21 0ms×50 与 100ms×50 各 50/50;:8888 20/20;:7003 10/10; ftp_regression 11/11;Trap23 abort/graceful 各 5/5 - 设备侧:orphan listener **0**(保持 P2a 基线)、pump 留痕正常 --- Drivers/BSP/NET/net_listener.c | 554 +++++++++++++++++++++++++ Drivers/BSP/NET/net_listener.h | 103 +++++ Drivers/BSP/NET/net_socket.c | 531 ++---------------------- Drivers/BSP/NET/net_socket.h | 28 ++ MDK-ARM/STM32F407-Demo.uvprojx | 5 + docs/内存管理/RAM使用分析与优化方案.md | 4 +- scripts/check_registration.py | 1 + 7 files changed, 730 insertions(+), 496 deletions(-) create mode 100644 Drivers/BSP/NET/net_listener.c create mode 100644 Drivers/BSP/NET/net_listener.h diff --git a/Drivers/BSP/NET/net_listener.c b/Drivers/BSP/NET/net_listener.c new file mode 100644 index 00000000..941d9b2a --- /dev/null +++ b/Drivers/BSP/NET/net_listener.c @@ -0,0 +1,554 @@ +/* + * 模块名称:Network Listener Lifecycle + * 模块功能:监听器生命周期(真机专属)—— 维持 fd 处于 LISTEN 态的全部机制: + * 重建(open+listen)、重建前的沉降/隔离(Trap 23)、排空 RX 残渣、 + * D10 非阻塞重试、周期性自愈 pump(唯一的周期性重建决策者)。 + * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 + * 作者:王建锋 + * 创建日期:2026-10-12 + * 修改记录: + * 2026-10-12 王建锋 从 net_socket.c 整体抽出监听生命周期(N-32 §10 模块化) + * + * 职责与边界见 net_listener.h 头注释;设计依据见 + * docs/子系统设计/网络Socket监听生命周期设计说明.md §10。 + * + * 模块内分层: + * ① 策略常量与私有状态(s_relisten_*) + * ② 机制:net_drain_chip_rx(排空)/ net_relisten_locked(重建一次) + * ③ 对外接口:settle / try / clear / retry_tick / pump + * + * 依赖(全部为单向,无反向 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) + */ +#include +#include + +#include "FreeRTOS.h" +#include "task.h" +#include "cmsis_os.h" + +#include "net_socket.h" /* net_sock_t / net_get_sock / NET_MAX_SOCKETS / net_set_errno */ +#include "net_listener.h" +#include "ch395f.h" +#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */ +#include "net_latency_test.h" /* L3 探针接口(relisten 恢复计时采样) */ +#include "main.h" + +#include "dbg_log.h" + +/* ===== ① 策略常量与私有状态 ===== */ + +/* Trap 23(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)不走此路径——既有行为零改动 + */ +#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 通过 + */ +#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]; /* 隔离上限到期时刻 */ + +/* ===== Trap 09 非阻塞重监听重试(D10 修复,2026-09-22 M3 实测) ===== + * 旧实现:open 失败 → close → osDelay(500) → 重试,netTask 忙等 500ms(全网冻结) + * 且实测恢复 588ms、期间客户端重连被拒 + * 新实现:首次失败即登记待重试,由 netTask 每轮 net_process_messages() 到点重试 + * 恢复时间 ≈ NET_RELISTEN_RETRY_MS 量级,任何时刻都不阻塞任务 + */ +#define NET_RELISTEN_RETRY_MS 30U /* 重试间隔(Trap 09 沉降窗口;实测 100ms 首次必成, + * 收紧到 30ms 以缩短恢复时间——未成则下一轮到点再来) */ +#define NET_RELISTEN_DEADLINE_MS 2000U /* 放弃时限(自首次尝试起) */ + +static uint8_t s_relisten_wait[NET_MAX_SOCKETS]; /* 1 = 有待重试的重监听 */ +static uint32_t s_relisten_next[NET_MAX_SOCKETS]; /* 下次重试时刻(tick) */ +static uint32_t s_relisten_dead[NET_MAX_SOCKETS]; /* 放弃时刻(tick) */ +#if defined(TEST_LATENCY_PROBE) +static lat_tmark_t s_relisten_mark[NET_MAX_SOCKETS]; /* 恢复计时起点(首次尝试) */ +#endif + +/* ===== ② 机制 ===== */ + +/* + * 函数功能:清空芯片该 socket 的 RX 残留(Trap 23) + * 入口参数:sockfd - Socket 描述符 (0~7) int + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文;socket 即将 close/open(数据已无意义,丢弃) + * 函数说明:1. 中止/异常断开时芯片 RX 缓冲可能残留上一条连接的数据(实测残留约一个缓冲 = 2048B) + * 2. 残渣带入下一条连接会使其在约 2KB 处被芯片拆链(上传被静默截断) + * 3. 关闭前主动读空即清除;分块读避免新增静态缓冲 + * (2026-10-09 实测:ARMCC 把本 scratch 与同函数内生命周期不重叠的 + * 192B 日志缓冲叠放,局部缓冲边际栈成本仅 ~16B,静态化反亏 256B BSS,勿再"优化") + */ +static void net_drain_chip_rx(int sockfd) { + /* 复用分块缓冲(256B/块,避免新增静态缓冲) */ + uint8_t scratch[256]; + + /* 上限 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); + } +} + +/* + * 函数功能:单连接模式断连后自动重新进入 LISTEN(不清空控制块,保留 fd) + * 入口参数:sockfd - 已断开(CLOSED)的 TCP 监听型 Socket 描述符 (0~7) int + * 出口参数:无 + * 返回值:0 - 成功,-1 - 失败 + * 限定条件: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. Trap 09 沉降交给 net_listener_try / net_listener_retry_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 数组 + */ +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) { + return NET_ERR_RETURN_1(NET_ERR_BADF); + } + + /* 释放隔离期(Trap 23):重监听前先清芯片收尾状态 */ + if (s_relisten_quarantine[sockfd] != 0U) { + /* 芯片状态两字节:[0]=Socket,[1]=TCP */ + uint8_t st[2] = {0, 0}; + /* 当前 tick */ + uint32_t now = HAL_GetTick(); + /* 隔离等级(1=异常释放 2=正常释放) */ + uint8_t lvl = s_relisten_quarantine[sockfd]; + /* 最短等待由释放来源决定(NET_RELISTEN_SETTLE_*):异常释放 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 */ + 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); + /* 读即清:全局中断状态寄存器 */ + (void)ch395f_get_glob_int_status_all(); + /* 读芯片当前状态(两字节) */ + ch395f_get_socket_status((uint8_t)sockfd, st); + + /* 芯片尚未回到 CLOSED/TCP_CLOSED,或未满最短等待 */ + if (((st[0] != CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_CLOSED) || + ((uint32_t)(now - s_relisten_settle_start[sockfd]) < hold))) { + /* 未到上限:交重试轮询(30ms 后复检) */ + if ((int32_t)(now - s_relisten_settle_dead[sockfd]) < 0) { + DBG_INFO("sock%d relisten %s (sock=0x%02X tcp=0x%02X)", + sockfd, tag, st[0], st[1]); + /* 置 BUSY(结果回传调用方) */ + (void)net_set_errno(NET_ERR_BUSY); + /* 交重试轮询(30ms 后复检) */ + return -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; + /* 清上限时刻 */ + s_relisten_settle_dead[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); + + /* 打开芯片侧 Socket */ + status = ch395f_open_socket((uint8_t)sockfd); + /* open 失败 → BUSY(Trap 09:芯片需沉降时间才能重开) */ + if (status != CH395F_ERR_SUCCESS) { + /* Trap 09:断开后芯片需沉降时间才能重新 open。此处只清理并返回 + * (严禁 osDelay 忙等)→ 由重试轮询到点再来。 */ + /* 先关闭,交重试轮询(30ms 后复检) */ + ch395f_close_socket((uint8_t)sockfd); + /* 置 BUSY(结果回传调用方) */ + (void)net_set_errno(NET_ERR_BUSY); + /* 交重试轮询(30ms 后复检) */ + return -1; + } + /* 下发 TCP LISTEN 命令 */ + status = ch395f_tcp_listen((uint8_t)sockfd); + /* LISTEN 失败 → BUSY(同样交重试) */ + if (status != CH395F_ERR_SUCCESS) { + /* 置 BUSY(结果回传调用方) */ + return NET_ERR_RETURN_1(NET_ERR_BUSY); + } + + /* 状态置回 LISTENING(fd 保留) */ + 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)清零。 */ + /* 新监听不得继承上一条连接的释放原因(Trap 23 待办 #3) */ + p_sock->released_abnormal = 0U; + /* N-30(Trap 25):清零前把对端端口转登为幽灵甄别基准 */ + 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->recv_bytes = 0; + /* auto_relisten 保持为 1,下次断连仍自动重监听 */ + + /* 读即清:仅本 Socket 的中断状态(防上一条会话残留误触发)。 + * 原为"全 8 个 Socket + 全局"读清,与 net_listen_locked 同源(B3,机制 + * 与 git 考古见该处注释):本处是每次断连 auto-relisten 的热路径,全量清 + * 会持续吞掉其它在用 Socket 的 CONNECT/DISCONNECT 等边沿事件。全局中断 + * 寄存器交 net_poll 集中读清(AGENTS 模块约定)。 */ + (void)ch395f_get_sock_int_status((uint8_t)sockfd); + + /* 留痕:监听器重建完成 */ + DBG_INFO("sock%d auto-relisten ok", sockfd); + /* 重监听成功 */ + return 0; +} + +/* ===== ③ 对外接口 ===== */ + +/* + * 函数功能:登记「本 fd 的会话已结束,重建监听前需沉降/隔离」(契约见 net_listener.h) + * 入口参数:sockfd - Socket 描述符 (0~7) int + * level - 沉降等级(net_listener_settle_t,由释放来源决定) + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文;由 handle_disconnect_event / handle_timeout_event 调用 + * 函数说明:1. 只置事实,不做动作 —— 重建由 net_listener_pump / net_listener_try 负责 + * 2. 越界 sockfd 静默忽略(与旧直接写 s_relisten_quarantine 行为一致) + */ +void net_listener_settle(int sockfd, net_listener_settle_t level) { + /* 描述符越界:静默忽略(与旧直接写数组行为一致,不设错误码) */ + if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { + return; + } + /* 登记沉降/隔离等级 —— 只置事实,重建由 pump / try 负责 */ + s_relisten_quarantine[sockfd] = (uint8_t)level; +} + +/* + * 函数功能:清除本 fd 的待重监听重试状态(关闭/新建 fd 时调用) + * 入口参数:sockfd - Socket 描述符 (0~7) + * 出口参数:无 + * 返回值:无 + * 限定条件:无 + * 函数说明:1. 清"待重试"标志与重试时刻,fd 关闭/新建时调用(防残留重试) + */ +void net_listener_clear(int sockfd) { + /* 描述符越界:直接返回(本函数不设错误码) */ + if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { + return; + } + /* 清"待重试"标志 */ + s_relisten_wait[sockfd] = 0U; + /* 清下次重试时刻 */ + s_relisten_next[sockfd] = 0U; + /* 清放弃时刻 */ + s_relisten_dead[sockfd] = 0U; + /* Trap 23:一并清沉降/隔离状态 */ + s_relisten_settle_dead[sockfd] = 0U; + /* 清沉降起点 */ + s_relisten_settle_start[sockfd] = 0U; + /* 一并清隔离标志:关闭/重建即确定性释放, + * 防同 fd 下一生成继承上轮的 400/800ms + * 隔离等待(2026-09-24 台架发现跨轮残留) */ + s_relisten_quarantine[sockfd] = 0U; +} + +/* + * 函数功能:标记重监听完成(清重试状态;探针构建下报告恢复计时样本) + * 入口参数:sockfd - Socket 描述符 (0~7) + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文 + * 函数说明:1. 重监听成功收尾:清重试状态;探针构建(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 +} + +/* + * 函数功能:尝试重建监听一次(**不阻塞**);失败则登记"待重试"(契约见 net_listener.h) + * 入口参数:sockfd - Socket 描述符 (0~7) int + * 出口参数:无 + * 返回值:0 - 成功(监听器已就绪);-1 - 本次失败或已在重试队列中 int + * 限定条件:netTask 上下文;fd 为监听型(auto_relisten) + * 函数说明:1. 已在重试队列时直接返回 -1(不重复尝试、不重置重试时刻)—— + * 否则每 10ms 一次的 accept 轮询会不断把重试时刻往后推, + * 导致永不重试(重试饥饿) + * 2. N-32 §10:替代此前散落在 net_socket.c 各处的 net_relisten_try() + */ +int net_listener_try(int sockfd) { + /* 当前 tick */ + uint32_t now = HAL_GetTick(); + + /* 描述符越界 → BADF */ + if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { + return NET_ERR_RETURN_1(NET_ERR_BADF); + } + /* 已在重试队列:不重复尝试、不重置时刻(防重试饥饿) */ + if (s_relisten_wait[sockfd] != 0U) { + (void)net_set_errno(NET_ERR_BUSY); + /* 已在重试队列:由 tick 负责 */ + return -1; + } +#if defined(TEST_LATENCY_PROBE) + /* 恢复计时自首次尝试起 */ + lat_mark(&s_relisten_mark[sockfd]); +#endif + /* 本次尝试成功 */ + if (net_relisten_locked(sockfd) == 0) { + /* 标记完成(清状态 + 探针样本) */ + net_relisten_done(sockfd); + /* 监听器已就绪 */ + return 0; + } + /* 登记"待重试" */ + s_relisten_wait[sockfd] = 1U; + /* 下次重试时刻 = now + 30ms */ + s_relisten_next[sockfd] = now + NET_RELISTEN_RETRY_MS; + /* 放弃时刻 = now + 2s */ + s_relisten_dead[sockfd] = now + NET_RELISTEN_DEADLINE_MS; + /* 本次失败(重试交 tick) */ + return -1; +} + +/* + * 函数功能:D10 重试轮询(netTask 每轮调用一次,非阻塞) + * 入口参数:无 + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) + * 函数说明:1. 到点重试待办的重监听;成功即报告探针样本 + * 2. 本 fd 有在途挂起 recv 时跳过(net_pending_active,L3 保险: + * 重开 Socket 会摧毁在途连接) + * 3. 超过 NET_RELISTEN_DEADLINE_MS 放弃并记错误日志(防监听器永久失效) + */ +void net_listener_retry_tick(void) { + /* 当前 tick */ + uint32_t now = HAL_GetTick(); + /* Socket 循环索引 */ + int i; + + for (i = 0; i < NET_MAX_SOCKETS; i++) { + /* 本 fd 无待重试重监听:跳过 */ + if (s_relisten_wait[i] == 0U) { + continue; + } + /* 本 fd 有在途挂起 recv:重开 Socket 会摧毁在途连接(L3 保险) */ + if (net_pending_active(i) != 0U) { + continue; + } + /* 未到点(tick 差值按有符号比较,抗回绕) */ + if ((int32_t)(now - s_relisten_next[i]) < 0) { + continue; + } + /* 已到放弃时限 */ + if ((int32_t)(now - s_relisten_dead[i]) >= 0) { + /* 清重试状态 */ + net_listener_clear(i); + /* 放弃并记错误日志 */ + DBG_ERROR("sock%d relisten give up (Trap 09 not cleared)", i); + continue; + } + /* 重试成功 */ + if (net_relisten_locked(i) == 0) { + /* 标记完成(清状态 + 探针样本) */ + net_relisten_done(i); + /* 留痕:重试成功 */ + DBG_INFO("sock%d relisten retry ok", i); + } else { + /* 仍未成:置下次重试时刻 */ + s_relisten_next[i] = now + NET_RELISTEN_RETRY_MS; + } + } +} + +/* 监听器 pump 周期(对账 200ms 的 1/2:比它快、又不至于像 50ms 那样 + * 频繁 close/open 芯片 Socket 与消费者收发撞车——2026-10-12 台架实测 + * 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms) */ +#define NET_LISTENER_PUMP_PERIOD_MS 100U +/* pump 去抖与冷却(沿用原对账③④ 的实测结论,取值不变): + * 2026-09-23 实证——无去抖时监听被反复重建直至放弃 */ +#define NET_LISTENER_STABLE_N 2U /* 连续失配次数(去抖,≈200ms) */ +#define NET_LISTENER_COOLDOWN_MS 1000U /* 同一 socket 两次动手的最小间隔(防抖成风暴) */ + +/* + * 函数功能:监听器 pump(N-32 P2「单一所有者」)—— 本层**唯一**的周期性 + * 重建监听决策者 + * 入口参数:无 + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) + * 函数说明:1. 设计意图(设计文档 §4 不变量 II):其它所有位置只**置事实** + * (session_dead / 状态迁移 / 事件派发),不**做动作**——于是 + * "谁先看到 DISCONNECT 就谁重建"这类竞争在结构上消失 + * 2. 覆盖三种情形(前两种原属对账③④,第三种为 U11 缺口): + * ① 本层 LISTENING 而芯片监听已失位 → 重建(原对账③) + * ② 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原对账④) + * ③ 本层 CLOSED 而芯片**正在** LISTEN → **只做状态重同步** + * (U11):芯片监听器是健康的,重下发 open+listen 会因 USE + * 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故); + * 只把本层状态收回 LISTENING,**不发任何芯片命令** + * 3. 去抖/冷却沿用对账的实测结论(2026-09-23 实证:无去抖时监听被 + * 反复重建直至放弃) + * 4. session_dead **不在此清零**(与 net_relisten_locked 同一契约: + * 重建监听 ≠ 应用已确认会话结束;清零只发生在 handle_connect_event) + */ +void net_listener_pump(void) { + /* 上次轮次时刻 */ + static uint32_t s_last = 0U; + /* 连续失配计数(去抖) */ + static uint8_t s_bad[NET_MAX_SOCKETS]; + /* 冷却到期时刻(防抖成风暴) */ + static uint32_t s_cool[NET_MAX_SOCKETS]; + /* 当前 tick */ + uint32_t now = HAL_GetTick(); + /* Socket 循环索引 */ + int i; + + /* 未到 pump 周期:直接返回 */ + if ((uint32_t)(now - s_last) < NET_LISTENER_PUMP_PERIOD_MS) { + return; + } + s_last = now; + + for (i = 0; i < NET_MAX_SOCKETS; i++) { + /* 取控制块 */ + net_sock_t *p_sock = net_get_sock(i); + /* 芯片侧状态两字节 */ + uint8_t st[2] = {0, 0}; + /* 芯片当前是否在 LISTEN */ + int chip_listening; + + /* 控制块无效:跳过 */ + if (p_sock == NULL) { + s_bad[i] = 0U; + continue; + } + + /* 认领门槛:仅监听型 fd(在用 + 自动重监听 + TCP + 已绑端口)。 + * auto_relisten 是决定性门:少了它,"本层 CLOSED + 已绑端口"的出向 + * 连接(板级 Phase 7 TC701~703 用 net_connect 绑本地口)会被误判为 + * 监听器并被 relisten(同对账④的既定口径) */ + if ((p_sock->in_use == 0U) || + (p_sock->auto_relisten == 0U) || + (p_sock->type != NET_SOCK_STREAM) || + (p_sock->local_port == 0U)) { + s_bad[i] = 0U; + continue; + } + /* 只处理与监听生命周期相关的两个状态 */ + if ((p_sock->state != NET_SOCK_STATE_LISTENING) && + (p_sock->state != NET_SOCK_STATE_CLOSED)) { + s_bad[i] = 0U; + continue; + } + + /* 回读芯片状态(每监听型 fd 一条命令) */ + ch395f_get_socket_status((uint8_t)i, st); + chip_listening = ((st[0] != CH395F_SOCKET_CLOSED) && + (st[1] == CH395F_TCP_LISTEN)); + + /* 芯片在听 */ + if (chip_listening) { + s_bad[i] = 0U; + /* 本层 CLOSED 而芯片在听 = U11(本层状态滞后)→ 只重同步 */ + if (p_sock->state == NET_SOCK_STATE_CLOSED) { + DBG_INFO("sock%d pump: chip listening, resync CLOSED->LISTENING (U11)", i); + /* 只改本层状态,**不发任何芯片命令**(同 net_resync_listen 的 + * 既定纪律,理由见该函数注释)。session_dead 不清 */ + p_sock->state = NET_SOCK_STATE_LISTENING; + /* N-30(Trap 25):死连接对端端口转登幽灵甄别基准 + 去抖清零 + * —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的, + * 必须转登为基准,否则对账② 的幽灵拦截会失效(同 net_resync_listen) */ + p_sock->last_peer_port = p_sock->remote_port; + p_sock->lost_conn_seen = 0U; + } + continue; + } + + /* 芯片不在听:情形①②同一处置——重建(本层唯一重建入口) */ + if (s_bad[i] < NET_LISTENER_STABLE_N) { + s_bad[i]++; + } + /* 去抖未满或处于冷却期:本轮不动手 */ + if ((s_bad[i] < NET_LISTENER_STABLE_N) || + ((int32_t)(now - s_cool[i]) < 0)) { + continue; + } + s_bad[i] = 0U; + s_cool[i] = now + NET_LISTENER_COOLDOWN_MS; + DBG_INFO("sock%d pump: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten", + i, st[0], st[1], (int)p_sock->state); + /* 非阻塞:失败由 D10 重试轮询接管 */ + (void)net_listener_try(i); + } +} diff --git a/Drivers/BSP/NET/net_listener.h b/Drivers/BSP/NET/net_listener.h new file mode 100644 index 00000000..597cd352 --- /dev/null +++ b/Drivers/BSP/NET/net_listener.h @@ -0,0 +1,103 @@ +/* + * 模块名称:Network Listener Lifecycle (Header) + * 模块功能:监听器生命周期模块的对外接口(**真机专属**)—— 维持 fd 处于 + * LISTEN 态的全部机制:重建、重建前的沉降/隔离(Trap 23)、排空 RX + * 残渣、D10 非阻塞重试、周期性自愈 pump。 + * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 + * 作者:王建锋 + * 创建日期:2026-10-12 + * 修改记录: + * 2026-10-12 王建锋 从 net_socket.c 抽出监听生命周期(N-32 §10 模块化) + * + * 职责边界(详见 docs/子系统设计/网络Socket监听生命周期设计说明.md §10): + * 本模块只负责「让一个监听型 fd 保持/回到 LISTEN 态」。 + * + * **不属于本模块**(留在 net_socket.c): + * - `net_listen_locked`(首次监听的**应用 API 路径**,含 BOUND/CREATED 状态门与缓冲) + * - `net_accept_locked`(产出的是**连接**:LISTENING→ESTABLISHED 迁移) + * - `net_lost_connect_probe`(做的是**补 accept**,即连接建立) + * - `net_listener_armed` / `net_resync_listen`(双端公开 API,sim 也实现) + * + * ⚠ 本头文件**只被 net_socket.c include**(内部头),不进公开 net_socket.h —— + * 故模拟器(simulator/sim/net_socket_sim.c)对本模块**零依赖**,CMakeLists 不编本文件。 + */ +#ifndef NET_LISTENER_H +#define NET_LISTENER_H + +#include "net_types.h" + +/* + * 沉降/隔离等级:重建监听前"清芯片收尾状态"的强度 + * (替代此前外部直接写 s_relisten_quarantine[]) + * QUARANTINE - 异常释放(芯片 TIMEOUT / 对端 RST):对端可能仍有在途数据, + * 须排空残渣 + 最短等 400 ms + * GRACEFUL - 正常释放(芯片 DISCONNECT / 对端 FIN):无在途数据, + * 清理 + 最短等 100 ms(等芯片完成上一条连接的内部收尾) + */ +typedef enum { + NET_LISTENER_SETTLE_QUARANTINE = 1, /* 异常释放:隔离 400 ms + 排空 RX 残渣 */ + NET_LISTENER_SETTLE_GRACEFUL = 2, /* 正常释放:沉降 100 ms */ +} net_listener_settle_t; + +/* + * 函数功能:登记「本 fd 的会话已结束,重建监听前需沉降/隔离」 + * 入口参数:sockfd - Socket 描述符 (0~7) int + * level - 沉降等级(释放来源决定,见 net_listener_settle_t) + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文;由 handle_disconnect_event / handle_timeout_event 调用 + * 函数说明:1. 只置事实,不做动作 —— 重建由 net_listener_pump / net_listener_try 负责 + * 2. 越界 sockfd 静默忽略(不设错误码,与旧直接写数组行为一致) + */ +void net_listener_settle(int sockfd, net_listener_settle_t level); + +/* + * 函数功能:尝试重建监听一次(**不阻塞**);失败则登记"待重试" + * 入口参数:sockfd - Socket 描述符 (0~7) int + * 出口参数:无 + * 返回值:0 - 成功(监听器已就绪);-1 - 本次失败或已在重试队列中 int + * 限定条件:netTask 上下文;fd 为监听型(auto_relisten) + * 函数说明:1. 已在重试队列时直接返回 -1(不重复尝试、不重置重试时刻)—— + * 否则每 10 ms 一次的 accept 轮询会不断把重试时刻往后推, + * 导致永不重试(重试饥饿) + * 2. 替代此前散落在 net_socket.c 各处的 net_relisten_try() + */ +int net_listener_try(int sockfd); + +/* + * 函数功能:清除本 fd 的待重试/沉降状态(关闭或新建 fd 时调用) + * 入口参数:sockfd - Socket 描述符 (0~7) int + * 出口参数:无 + * 返回值:无 + * 限定条件:无(越界静默返回) + * 函数说明:防同 fd 下一生成继承上轮的重试队列或 400/800 ms 隔离等待 + */ +void net_listener_clear(int sockfd); + +/* + * 函数功能:D10 重试轮询(netTask 每轮调用,非阻塞) + * 入口参数:无 + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) + * 函数说明:到点重试待办的重监听;成功即报告探针样本;超时放弃并记错误日志 + */ +void net_listener_retry_tick(void); + +/* + * 函数功能:监听器 pump —— 本层**唯一**的周期性重建决策者 + * 入口参数:无 + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) + * 函数说明:1. 覆盖三种情形:① LISTENING 而芯片失位 → 重建 + * ② CLOSED 而芯片不在 LISTEN → 重建 + * ③ CLOSED 而芯片**正在** LISTEN → 只做状态重同步(U11,不发芯片命令) + * 2. 100 ms 周期 + 去抖 + 冷却(详见实现处注释) + */ +void net_listener_pump(void); + +/* 注:本模块消费的宿主能力 `net_pending_active()` 声明在 net_socket.h + * (它是 net_socket.c 的函数,须与配对头文件同处,见规范 S17 2.2)。 */ + +#endif /* NET_LISTENER_H */ diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index c8482180..b6a6858a 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -18,6 +18,7 @@ #include "cmsis_os.h" #include "net_socket.h" +#include "net_listener.h" #include "ch395f.h" #include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */ #include "net_latency_test.h" /* L3 探针接口(chunk_wait 采样) */ @@ -140,16 +141,15 @@ static uint32_t net_errno_slot(void) { * 出口参数:无 * 返回值:err 原值(便于写成 `return net_set_errno(NET_ERR_XXX);`) * 限定条件:无 - * 函数说明:只写本任务的槽位,其他任务读不到、也改不了 → 消除跨任务覆写竞态 + * 函数说明:1. 只写本任务的槽位,其他任务读不到、也改不了 → 消除跨任务覆写竞态 + * 2. N-32 §10 模块化:由 static 提升为**模块间可见**(net_listener.c + * 也需要设 errno),声明与 NET_ERR_RETURN_1 宏见 net_socket.h */ -static int net_set_errno(int err) { +int net_set_errno(int err) { s_net_errno_slots[net_errno_slot()].err = err; return err; } -/* 「记本任务错误码 + 统一失败返回 -1」的紧凑写法,取代原 `s_net_errno = X; return -1;` */ -#define NET_ERR_RETURN_1(err) (net_set_errno(err), -1) - /* @@ -168,11 +168,6 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len); static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, uint32_t timeout_ms, TaskHandle_t caller); static int net_close_locked(net_sock_t *p_sock); -static int net_relisten_locked(net_sock_t *p_sock); -/* D10 非阻塞重监听:单次尝试 + 延迟重试(实现见 net_relisten_locked 之后) */ -static void net_relisten_clear(int sockfd); -static int net_relisten_try(net_sock_t *p_sock); -static void net_relisten_retry_tick(void); static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen); static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen); @@ -645,118 +640,6 @@ static void net_reconcile_tick(void) { } } -/* 监听器 pump 周期(对账 200ms 的 1/2:比它快、又不至于像 50ms 那样 - * 频繁 close/open 芯片 Socket 与消费者收发撞车——2026-10-12 台架实测 - * 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms) */ -#define NET_LISTENER_PUMP_PERIOD_MS 100U - -/* - * 函数功能:监听器 pump(N-32 P2「单一所有者」)—— 本层**唯一**对监听型 - * Socket 调 net_relisten_try 的地方(应用显式 NET_MSG_RELISTEN 除外) - * 入口参数:无 - * 出口参数:无 - * 返回值:无 - * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) - * 函数说明:1. 设计意图(设计文档 §4 不变量 II):其它所有位置只**置事实** - * (session_dead / 状态迁移 / 事件派发),不**做动作**——于是 - * "谁先看到 DISCONNECT 就谁重建"这类竞争在结构上消失 - * 2. 覆盖三种情形(前两种原属对账③④,第三种为 U11 缺口): - * ① 本层 LISTENING 而芯片监听已失位 → 重建(原对账③) - * ② 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原对账④; - * 原 accept/recv/pending 三处散点只置事实,重建收敛到这里) - * ③ 本层 CLOSED 而芯片**正在** LISTEN → **只做状态重同步** - * (U11):芯片监听器是健康的,重下发 open+listen 会因 USE - * 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故); - * 只把本层状态收回 LISTENING,**不发任何芯片命令** - * 3. 去抖/冷却沿用对账的实测结论(2026-09-23 实证:无去抖时监听被 - * 反复重建直至放弃) - * 4. session_dead **不在此清零**(与 net_relisten_locked 同一契约: - * 重建监听 ≠ 应用已确认会话结束;清零只发生在 handle_connect_event) - */ -static void net_listener_pump(void) { - /* 上次轮次时刻 */ - static uint32_t s_last = 0U; - /* 连续失配计数(去抖) */ - static uint8_t s_bad[NET_MAX_SOCKETS]; - /* 冷却到期时刻(防抖成风暴) */ - static uint32_t s_cool[NET_MAX_SOCKETS]; - /* 当前 tick */ - uint32_t now = HAL_GetTick(); - /* Socket 循环索引 */ - int i; - - /* 未到 pump 周期:直接返回 */ - if ((uint32_t)(now - s_last) < NET_LISTENER_PUMP_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}; - /* 芯片当前是否在 LISTEN */ - int chip_listening; - - /* 认领门槛:仅监听型 fd(在用 + 自动重监听 + TCP + 已绑端口)。 - * auto_relisten 是决定性门:少了它,"本层 CLOSED + 已绑端口"的出向 - * 连接(板级 Phase 7 TC701~703 用 net_connect 绑本地口)会被误判为 - * 监听器并被 relisten(同对账④的既定口径) */ - if ((p_sock->in_use == 0U) || - (p_sock->auto_relisten == 0U) || - (p_sock->type != NET_SOCK_STREAM) || - (p_sock->local_port == 0U)) { - s_bad[i] = 0U; - continue; - } - /* 只处理与监听生命周期相关的两个状态 */ - if ((p_sock->state != NET_SOCK_STATE_LISTENING) && - (p_sock->state != NET_SOCK_STATE_CLOSED)) { - s_bad[i] = 0U; - continue; - } - - /* 回读芯片状态(每监听型 fd 一条命令) */ - ch395f_get_socket_status((uint8_t)i, st); - chip_listening = ((st[0] != CH395F_SOCKET_CLOSED) && - (st[1] == CH395F_TCP_LISTEN)); - - /* 芯片在听 */ - if (chip_listening) { - s_bad[i] = 0U; - /* 本层 CLOSED 而芯片在听 = U11(本层状态滞后)→ 只重同步 */ - if (p_sock->state == NET_SOCK_STATE_CLOSED) { - DBG_INFO("sock%d pump: chip listening, resync CLOSED->LISTENING (U11)", i); - /* 只改本层状态,**不发任何芯片命令**(同 net_resync_listen 的 - * 既定纪律,理由见该函数注释)。session_dead 不清 */ - p_sock->state = NET_SOCK_STATE_LISTENING; - /* N-30(Trap 25):死连接对端端口转登幽灵甄别基准 + 去抖清零 - * —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的, - * 必须转登为基准,否则对账② 的幽灵拦截会失效(同 net_resync_listen) */ - p_sock->last_peer_port = p_sock->remote_port; - p_sock->lost_conn_seen = 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; - DBG_INFO("sock%d pump: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten", - i, st[0], st[1], (int)p_sock->state); - /* 非阻塞:失败由 D10 重试轮询接管 */ - (void)net_relisten_try(p_sock); - } -} /* * 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮 @@ -792,7 +675,7 @@ void net_process_messages(void) { /* 三个非阻塞自愈 tick 置于消息处理之前:断连后即使无应用消息,监听器也能自主恢复 */ /* D10 重监听重试(Trap 09):沉降后重开芯片监听器 */ - net_relisten_retry_tick(); + net_listener_retry_tick(); /* L3 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */ net_pending_tick(); /* Trap 22 状态对账:周期回读芯片状态,自愈丢失的中断(①②;③④ 已归 pump) */ @@ -913,16 +796,17 @@ void net_process_messages(void) { break; } case NET_MSG_RELISTEN: { - /* 取控制块;NULL = fd 越界或已关闭 */ - net_sock_t *p_sock = net_get_sock(msg.sockfd); - if (p_sock == NULL) { + /* 描述符有效性检查(NULL = fd 越界或已关闭)→ 无效即 BADF; + * 有效则交 net_listener_try(它内部自行取控制块,本处不再持有指针, + * N-32 §10 入口收敛) */ + if (net_get_sock(msg.sockfd) == NULL) { /* 描述符无效 → BADF,结果 -1 */ net_set_errno(NET_ERR_BADF); msg.result = -1; } else { /* 无条件芯片侧重监听:单次尝试,失败登记非阻塞重试 * (芯片已在监听 → 下一轮到点再来;两种陈旧场景都收敛到"芯片监听器在位") */ - msg.result = net_relisten_try(p_sock); + msg.result = net_listener_try(msg.sockfd); } break; } @@ -1052,7 +936,7 @@ int net_socket(int domain, int type) { * 消费侧连数据都收不到)——与 released_abnormal 同理,显式清零 */ p_sock->session_dead = 0; /* 新 fd 不得继承上一次的待重试状态(D10) */ - net_relisten_clear(sockfd); + net_listener_clear(sockfd); /* 新 fd 不得继承上一次挂起的 recv 请求(L3) */ net_pending_clear(sockfd); @@ -1438,7 +1322,7 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen * (消费者正在等新连接),不是周期性抢先;删掉会把 CLOSED 窗口 * 拉长、导致调用方高频空转 accept(实测 [WD] task stall)。 * 非阻塞:失败由 D10 重试轮询接管 */ - (void)net_relisten_try(p_listen_sock); + (void)net_listener_try(sockfd); } } if (drain_pending == 0) { @@ -2049,7 +1933,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, * `[WD] task stall`(实测 uis 往返 1063ms)。周期性抢先由 pump 收敛即可。 */ if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) { /* 非阻塞:失败由 D10 重试轮询接管 */ - (void)net_relisten_try(p_sock); + (void)net_listener_try(sockfd); } /* EOF:返回 0(非错误) */ return 0; @@ -2074,365 +1958,6 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, * 函数说明:1. 调用 ch395f_close_socket 关闭 CH395F 硬件 Socket * 2. 清空整个控制块(memset 0),释放该 Socket 资源 */ -/* Trap 23(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)不走此路径——既有行为零改动 - * 声明须早于 net_relisten_locked(其余重试状态见其后区块) - */ -#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 取值) - * 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]; /* 隔离上限到期时刻 */ - -/* - * 函数功能:清空芯片该 socket 的 RX 残留(Trap 23) - * 入口参数:sockfd - Socket 描述符 (0~7) int - * 出口参数:无 - * 返回值:无 - * 限定条件:netTask 上下文;socket 即将 close/open(数据已无意义,丢弃) - * 函数说明:1. 中止/异常断开时芯片 RX 缓冲可能残留上一条连接的数据(实测残留约一个缓冲 = 2048B) - * 2. 残渣带入下一条连接会使其在约 2KB 处被芯片拆链(上传被静默截断) - * 3. 关闭前主动读空即清除;分块读避免新增静态缓冲 - * (2026-10-09 实测:ARMCC 把本 scratch 与同函数内生命周期不重叠的 - * 192B 日志缓冲叠放,局部缓冲边际栈成本仅 ~16B,静态化反亏 256B BSS,勿再"优化") - */ -static void net_drain_chip_rx(int sockfd) { - /* 复用分块缓冲(256B/块,避免新增静态缓冲) */ - uint8_t scratch[256]; - - /* 上限 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); - } -} - -/* - * 函数功能:单连接模式断连后自动重新进入 LISTEN(不清空控制块,保留 fd) - * 入口参数:p_sock - 已断开(CLOSED)的 TCP 监听型 Socket 控制块 net_sock_t * - * 出口参数:无 - * 返回值:0 - 成功,-1 - 失败 - * 限定条件:netTask 上下文;p_sock->in_use == 1;保留 local_port/auto_relisten - * 函数说明:1. 复用原本地端口与协议,重新 OPEN + TCP_LISTEN - * 2. 仅将 state 置回 LISTENING,不 memset(fd 与 auto_relisten 保持不变) - * 3. 实现 BSD 风格"监听 Socket 常驻",应用层无需手动重监听 - * 4. **单次尝试**:失败立即返回(不 osDelay 忙等) - * 5. Trap 09 沉降交给 net_relisten_try / net_relisten_retry_tick 非阻塞重试(见下) - * 6. 旧实现在此 osDelay(500) 忙等,netTask 冻结 500ms、实测恢复 588ms(M3 台架 D10) - */ -static int net_relisten_locked(net_sock_t *p_sock) { - /* 芯片命令返回状态 */ - uint8_t status; - /* 由控制块指针反推描述符 */ - int sockfd = (int)(p_sock - s_net_socks); - - /* 释放隔离期(Trap 23):重监听前先清芯片收尾状态 */ - if (s_relisten_quarantine[sockfd] != 0U) { - /* 芯片状态两字节:[0]=Socket,[1]=TCP */ - uint8_t st[2] = {0, 0}; - /* 当前 tick */ - uint32_t now = HAL_GetTick(); - /* 隔离等级(1=异常释放 2=正常释放) */ - uint8_t lvl = s_relisten_quarantine[sockfd]; - /* 最短等待由释放来源决定(NET_RELISTEN_SETTLE_*):异常释放 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 */ - 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); - /* 读即清:全局中断状态寄存器 */ - (void)ch395f_get_glob_int_status_all(); - /* 读芯片当前状态(两字节) */ - ch395f_get_socket_status((uint8_t)sockfd, st); - - /* 芯片尚未回到 CLOSED/TCP_CLOSED,或未满最短等待 */ - if (((st[0] != CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_CLOSED) || - ((uint32_t)(now - s_relisten_settle_start[sockfd]) < hold))) { - /* 未到上限:交重试轮询(30ms 后复检) */ - if ((int32_t)(now - s_relisten_settle_dead[sockfd]) < 0) { - DBG_INFO("sock%d relisten %s (sock=0x%02X tcp=0x%02X)", - sockfd, tag, st[0], st[1]); - /* 置 BUSY(结果回传调用方) */ - (void)net_set_errno(NET_ERR_BUSY); - /* 交重试轮询(30ms 后复检) */ - return -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; - /* 清上限时刻 */ - s_relisten_settle_dead[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); - - /* 打开芯片侧 Socket */ - status = ch395f_open_socket((uint8_t)sockfd); - /* open 失败 → BUSY(Trap 09:芯片需沉降时间才能重开) */ - if (status != CH395F_ERR_SUCCESS) { - /* Trap 09:断开后芯片需沉降时间才能重新 open。此处只清理并返回 - * (严禁 osDelay 忙等)→ 由重试轮询到点再来。 */ - /* 先关闭,交重试轮询(30ms 后复检) */ - ch395f_close_socket((uint8_t)sockfd); - /* 置 BUSY(结果回传调用方) */ - (void)net_set_errno(NET_ERR_BUSY); - /* 交重试轮询(30ms 后复检) */ - return -1; - } - /* 下发 TCP LISTEN 命令 */ - status = ch395f_tcp_listen((uint8_t)sockfd); - /* LISTEN 失败 → BUSY(同样交重试) */ - if (status != CH395F_ERR_SUCCESS) { - /* 置 BUSY(结果回传调用方) */ - return NET_ERR_RETURN_1(NET_ERR_BUSY); - } - - /* 状态置回 LISTENING(fd 保留) */ - 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)清零。 */ - /* 新监听不得继承上一条连接的释放原因(Trap 23 待办 #3) */ - p_sock->released_abnormal = 0U; - /* N-30(Trap 25):清零前把对端端口转登为幽灵甄别基准 */ - 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->recv_bytes = 0; - /* auto_relisten 保持为 1,下次断连仍自动重监听 */ - - /* 读即清:仅本 Socket 的中断状态(防上一条会话残留误触发)。 - * 原为"全 8 个 Socket + 全局"读清,与 net_listen_locked 同源(B3,机制 - * 与 git 考古见该处注释):本处是每次断连 auto-relisten 的热路径,全量清 - * 会持续吞掉其它在用 Socket 的 CONNECT/DISCONNECT 等边沿事件。全局中断 - * 寄存器交 net_poll 集中读清(AGENTS 模块约定)。 */ - (void)ch395f_get_sock_int_status((uint8_t)sockfd); - - /* 留痕:监听器重建完成 */ - DBG_INFO("sock%d auto-relisten ok", sockfd); - /* 重监听成功 */ - return 0; -} - -/* ===== Trap 09 非阻塞重监听重试(D10 修复,2026-09-22 M3 实测) ===== - * 旧实现:open 失败 → close → osDelay(500) → 重试,netTask 忙等 500ms(全网冻结) - * 且实测恢复 588ms、期间客户端重连被拒 - * 新实现:首次失败即登记待重试,由 netTask 每轮 net_process_messages() 到点重试 - * 恢复时间 ≈ NET_RELISTEN_RETRY_MS 量级,任何时刻都不阻塞任务 - */ -#define NET_RELISTEN_RETRY_MS 30U /* 重试间隔(Trap 09 沉降窗口;实测 100ms 首次必成, - * 收紧到 30ms 以缩短恢复时间——未成则下一轮到点再来) */ -#define NET_RELISTEN_DEADLINE_MS 2000U /* 放弃时限(自首次尝试起) */ - -static uint8_t s_relisten_wait[NET_MAX_SOCKETS]; /* 1 = 有待重试的重监听 */ -static uint32_t s_relisten_next[NET_MAX_SOCKETS]; /* 下次重试时刻(tick) */ -static uint32_t s_relisten_dead[NET_MAX_SOCKETS]; /* 放弃时刻(tick) */ -#if defined(TEST_LATENCY_PROBE) -static lat_tmark_t s_relisten_mark[NET_MAX_SOCKETS]; /* 恢复计时起点(首次尝试) */ -#endif - -/* - * 函数功能:清除本 fd 的待重监听重试状态(关闭/新建 fd 时调用) - * 入口参数:sockfd - Socket 描述符 (0~7) - * 出口参数:无 - * 返回值:无 - * 限定条件:无 - * 函数说明:1. 清"待重试"标志与重试时刻,fd 关闭/新建时调用(防残留重试) - */ -static void net_relisten_clear(int sockfd) { - /* 描述符越界:直接返回(本函数不设错误码) */ - if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { - return; - } - /* 清"待重试"标志 */ - s_relisten_wait[sockfd] = 0U; - /* 清下次重试时刻 */ - s_relisten_next[sockfd] = 0U; - /* 清放弃时刻 */ - s_relisten_dead[sockfd] = 0U; - /* Trap 23:一并清沉降/隔离状态 */ - s_relisten_settle_dead[sockfd] = 0U; - /* 清沉降起点 */ - s_relisten_settle_start[sockfd] = 0U; - /* 一并清隔离标志:关闭/重建即确定性释放, - * 防同 fd 下一生成继承上轮的 400/800ms - * 隔离等待(2026-09-24 台架发现跨轮残留) */ - s_relisten_quarantine[sockfd] = 0U; -} - -/* - * 函数功能:标记重监听完成(清重试状态;探针构建下报告恢复计时样本) - * 入口参数:sockfd - Socket 描述符 (0~7) - * 出口参数:无 - * 返回值:无 - * 限定条件:netTask 上下文 - * 函数说明:1. 重监听成功收尾:清重试状态;探针构建(TEST_LATENCY_PROBE)下上报恢复计时样本 - */ -static void net_relisten_done(int sockfd) { - /* 清重试状态(复用清除函数) */ - net_relisten_clear(sockfd); -#if defined(TEST_LATENCY_PROBE) - /* 上报恢复计时样本(首次尝试 → 成功) */ - lat_probe_add_seg("relisten", lat_mark_us(&s_relisten_mark[sockfd])); -#endif -} - -/* - * 函数功能:尝试重监听一次;失败则登记"待重试"(**不阻塞** netTask) - * 入口参数:p_sock - CLOSED 的监听型 Socket 控制块 net_sock_t * - * 出口参数:无 - * 返回值:0 - 成功(监听器已就绪);-1 - 本次失败或已在重试队列中 - * 限定条件:netTask 上下文 - * 函数说明:已有待重试时直接返回 -1(不重复尝试、不重置重试时刻)——否则每 10ms - * 一次的 accept 轮询会不断把重试时刻往后推,导致永不重试(饥饿)。 - */ -static int net_relisten_try(net_sock_t *p_sock) { - /* 由控制块指针反推描述符 */ - int sockfd = (int)(p_sock - s_net_socks); - /* 当前 tick */ - uint32_t now = HAL_GetTick(); - - /* 描述符越界 → BADF */ - if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { - return NET_ERR_RETURN_1(NET_ERR_BADF); - } - /* 已在重试队列:不重复尝试、不重置时刻(防重试饥饿) */ - if (s_relisten_wait[sockfd] != 0U) { - (void)net_set_errno(NET_ERR_BUSY); - /* 已在重试队列:由 tick 负责 */ - return -1; - } -#if defined(TEST_LATENCY_PROBE) - /* 恢复计时自首次尝试起 */ - lat_mark(&s_relisten_mark[sockfd]); -#endif - /* 本次尝试成功 */ - if (net_relisten_locked(p_sock) == 0) { - /* 标记完成(清状态 + 探针样本) */ - net_relisten_done(sockfd); - /* 监听器已就绪 */ - return 0; - } - /* 登记"待重试" */ - s_relisten_wait[sockfd] = 1U; - /* 下次重试时刻 = now + 30ms */ - s_relisten_next[sockfd] = now + NET_RELISTEN_RETRY_MS; - /* 放弃时刻 = now + 2s */ - s_relisten_dead[sockfd] = now + NET_RELISTEN_DEADLINE_MS; - /* 本次失败(重试交 tick) */ - return -1; -} - -/* - * 函数功能:重试轮询(netTask 每轮调用一次,非阻塞) - * 入口参数:无 - * 出口参数:无 - * 返回值:无 - * 限定条件:netTask 上下文(net_process_messages 开头调用) - * 函数说明:到点重试待办的自动重监听;成功即报告探针样本;超时放弃并记错误日志 - */ -static void net_relisten_retry_tick(void) { - /* 当前 tick */ - uint32_t now = HAL_GetTick(); - /* Socket 循环索引 */ - int i; - - for (i = 0; i < NET_MAX_SOCKETS; i++) { - /* 本 fd 无待重试重监听:跳过 */ - if (s_relisten_wait[i] == 0U) { - continue; - } - /* 本 fd 有在途挂起 recv:重开 Socket 会摧毁在途连接(L3 保险) */ - if (s_net_pending[i].active != 0U) { - continue; - } - /* 未到点(tick 差值按有符号比较,抗回绕) */ - if ((int32_t)(now - s_relisten_next[i]) < 0) { - continue; - } - /* 已到放弃时限 */ - if ((int32_t)(now - s_relisten_dead[i]) >= 0) { - /* 清重试状态 */ - net_relisten_clear(i); - /* 放弃并记错误日志 */ - DBG_ERROR("sock%d relisten give up (Trap 09 not cleared)", i); - continue; - } - /* 重试成功 */ - if (net_relisten_locked(&s_net_socks[i]) == 0) { - /* 标记完成(清状态 + 探针样本) */ - net_relisten_done(i); - /* 留痕:重试成功 */ - DBG_INFO("sock%d relisten retry ok", i); - } else { - /* 仍未成:置下次重试时刻 */ - s_relisten_next[i] = now + NET_RELISTEN_RETRY_MS; - } - } -} /* ===== L3 挂起 recv 请求的实现(一期,2026-09-22) ===== * 完成条件按 ①数据 ②到点 ③断连 顺序检查(逐项对齐旧自旋循环语义) @@ -2631,8 +2156,8 @@ static void net_pending_tick(void) { * 消费者驱动路径,不是周期性抢先(同 net_recv_locked 的理由)。 */ if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) { - /* 非阻塞:失败由 D10 重试轮询接管 */ - (void)net_relisten_try(p_sock); + /* 非阻塞:失败由 D10 重试轮询接管(N-32 §10:入口收敛到 net_listener_try) */ + (void)net_listener_try(i); } /* Trap 23 待办 #3:异常释放(TIMEOUT/RST)不是正常 EOF(对端未发正常 FIN) * → 以连接重置了结:应用(lftpd STOR)对异常结束回 5xx 而非误报 226; @@ -2662,6 +2187,24 @@ static void net_pending_tick(void) { } } +/* + * 函数功能:查询本 fd 是否有在途挂起 recv(L3 pending)—— 宿主能力,供 net_listener 用 + * 入口参数:sockfd - Socket 描述符 (0~7) int + * 出口参数:无 + * 返回值:1 - 有挂起;0 - 无(含描述符越界) int + * 限定条件:任意上下文(纯 RAM 读,不发起 RPC) + * 函数说明:1. N-32 §10 模块化:net_listener_retry_tick 用它跳过"重开 Socket + * 会摧毁在途连接"的 fd(L3 保险,原为直接读 s_net_pending[i].active) + * 2. 声明在 net_listener.h(本模块提供、监听器模块消费) + */ +int net_pending_active(int sockfd) { + /* 描述符越界:视为无挂起 */ + if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { + return 0; + } + return (s_net_pending[sockfd].active != 0U) ? 1 : 0; +} + /* * 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理) * 入口参数:sockfd - Socket 描述符 (0~7) int @@ -2806,7 +2349,7 @@ static int net_close_locked(net_sock_t *p_sock) { memset(p_sock, 0, sizeof(net_sock_t)); p_sock->last_peer_port = keep_peer; /* 关闭即取消该 fd 的待重试(D10 重试队列) */ - net_relisten_clear(sockfd); + net_listener_clear(sockfd); /* 关闭即清除该 fd 的挂起状态(L3;幂等) */ net_pending_clear(sockfd); @@ -3882,7 +3425,7 @@ static void handle_disconnect_event(net_sock_t *p_sock) { /* 描述符范围保护 */ if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) { /* 登记优雅沉降(NET_RELISTEN_SETTLE_GRACEFUL) */ - s_relisten_quarantine[qfd] = NET_RELISTEN_SETTLE_GRACEFUL; + net_listener_settle(qfd, NET_LISTENER_SETTLE_GRACEFUL); } } /* 正常释放(对端 FIN):断连了结回 EOF(0) */ @@ -3975,7 +3518,7 @@ static void handle_timeout_event(net_sock_t *p_sock) { /* 描述符范围保护 */ if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) { /* 登记异常释放隔离(重监听前等待在途数据落尽 + RX 排空) */ - s_relisten_quarantine[qfd] = 1U; + net_listener_settle(qfd, NET_LISTENER_SETTLE_QUARANTINE); } } /* Trap 23 待办 #3:标记异常释放——L3 挂起 recv 排空断连后以 diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h index 7e7360ae..d9f200dc 100644 --- a/Drivers/BSP/NET/net_socket.h +++ b/Drivers/BSP/NET/net_socket.h @@ -435,6 +435,34 @@ void net_process_messages(void); */ net_sock_t *net_get_sock(int sockfd); +/* + * 函数功能:记录**本任务**最后一次失败的精确错误码(net 层内部通用工具) + * 入口参数:err - 错误码(NET_ERR_xxx,负值) + * 出口参数:无 + * 返回值:err 原值(便于写成 `return net_set_errno(NET_ERR_XXX);`) + * 限定条件:无 + * 函数说明:1. 只写本任务的槽位,其他任务读不到、也改不了 → 消除跨任务覆写竞态 + * 2. N-32 §10 模块化:由 net_socket.c 的 static 提升为**模块间可见**, + * 供 net_listener.c 等同目录内部模块复用(定义仍在 net_socket.c) + */ +int net_set_errno(int err); + +/* 「记本任务错误码 + 统一失败返回 -1」的紧凑写法(取代 `s_net_errno = X; return -1;`) + * N-32 §10:原定义在 net_socket.c,随 net_set_errno 一并提升到本头文件 */ +#define NET_ERR_RETURN_1(err) (net_set_errno(err), -1) + +/* + * 函数功能:查询本 fd 是否有在途挂起 recv(L3 pending)—— 供 net_listener.c 消费 + * 入口参数:sockfd - Socket 描述符 (0~7) int + * 出口参数:无 + * 返回值:1 - 有挂起;0 - 无(含描述符越界) int + * 限定条件:任意上下文(纯 RAM 读,不发起 RPC) + * 函数说明:1. N-32 §10 模块化:net_listener_retry_tick 用它跳过"重开 Socket + * 会摧毁在途连接"的 fd(L3 保险,原为直接读 s_net_pending[i].active) + * 2. 定义在 net_socket.c(配对头即本文件) + */ +int net_pending_active(int sockfd); + /* * 函数功能:获取 Socket 本地地址(IP + Port) * 入口参数:sockfd - socket 描述符 diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 8982afef..41480b13 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -1553,6 +1553,11 @@ 1 ..\Drivers\BSP\NET\net_socket.c + + net_listener.c + 1 + ..\Drivers\BSP\NET\net_listener.c + net_cfg.c 1 diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index b9d28990..72e0841d 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 214,248 B (209.23 KB) | 1,048,576 B (1024 KB) | 20.4% | +| ER_IROM1 | 214,228 B (209.21 KB) | 1,048,576 B (1024 KB) | 20.4% | | RW_IRAM1 | 94,808 B (92.59 KB) | 131,072 B (128 KB) | 72.3% | | RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% | -| **Flash (ROM 合计)** | **215,228 B (210.2 KB)** | 1,048,576 B (1024 KB) | **20.5%** | +| **Flash (ROM 合计)** | **215,208 B (210.2 KB)** | 1,048,576 B (1024 KB) | **20.5%** | 各区的容量与常量来源: diff --git a/scripts/check_registration.py b/scripts/check_registration.py index 4ee0342d..6b769ca3 100644 --- a/scripts/check_registration.py +++ b/scripts/check_registration.py @@ -107,6 +107,7 @@ SIM_EXEMPT = [ ("Drivers/BSP/NET/lftpd/lftpd_string.c", "MCU FTP 字符串工具"), ("Drivers/BSP/NET/net_select.c", "MCU socket 选择层"), ("Drivers/BSP/NET/net_socket.c", "MCU socket 后端,模拟器另有 net_socket_sim.c"), + ("Drivers/BSP/NET/net_listener.c", "MCU 监听器生命周期(依赖 ch395f_* SPI 驱动与 net_socket.c 静态状态),模拟器无对应(Winsock 后端无孤儿态/重监听概念)"), ("Drivers/BSP/RS485/rs485.c", "MCU RS-485 驱动"), ("Drivers/BSP/SD2506/sd2506.c", "MCU RTC 驱动"), ("Drivers/BSP/TPAFE5160/tpafe5160.c", "MCU 计量芯片驱动"),