From 63cf73b4d0b4004ddba998a2979743366facb614 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Wed, 14 Oct 2026 18:04:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor(NET):=20=E4=BA=8B=E4=BB=B6=E5=A4=84?= =?UTF-8?q?=E7=90=86=E4=B8=8E=E5=91=A8=E6=9C=9F=E5=AF=B9=E8=B4=A6=E6=8A=BD?= =?UTF-8?q?=E5=87=BA=20net=5Fevents.c=EF=BC=8Cnet=5Fsocket.c=203720?= =?UTF-8?q?=E2=86=923119?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 动机:net_socket.c 3720 行(注释 1878 / 代码 1518 / 54 函数),BSD 连接层 与"状态面收敛"两套职责混在一个文件里。16 个消费者只依赖 net_socket.h, 故搬移对应用层零波及;模拟器另有 net_socket_sim.c,新模块走 SIM_EXEMPT。 为什么是一个模块而不是两个:实测对账区真实调用事件处理器 2 处(形态② 补 accept 走 handle_connect_event),且 NET_RECON_STABLE_DEATH 两边共用—— 拆成 net_events.c + net_reconcile.c 会造出跨模块调用,合起来则跨模块导出 为零。二者同属"芯片状态面与本层镜像的收敛"一个职责。 搬出:5 个 handle_*_event + fire_event + net_reconcile_tick + net_lost_connect_probe + NET_RECONCILE_*/NET_RECON_STABLE_* 常量 + net_bench_cmd_log(含其 2 个 static)。 搬移时 4 处 fd 取法替换(其余代码逐行未改,已用剥注释后的代码行多重集 证明差异恰好等于声明的改动集): - p_sock - s_net_socks → p_sock->ch395_sock(net_socket() 里 ch395_sock = sockfd,恒等;模拟器同构造) - 对账循环 &s_net_socks[i] → net_sock_raw(i) ⚠ 随之收窄两处沉降登记的范围门 (qfd >= 0) && (qfd < MAX) → qfd < MAX: ch395_sock 是 uint8_t,(int) 后恒非负,下界在类型上不可能越界;留着会被 cppcheck 判 knownConditionTrueFalse 拦提交。已在文件头写"勿加回"。 新增 net_sock_raw():对账必须看到 in_use==0 / state==CLOSED 的槽位 (形态④ 认领孤儿监听正依赖 CLOSED 态),而 net_get_sock() 对未在用槽位返 回 NULL 会漏掉这一类。声明放 net_socket.h 而非内部头 net_events.h——规范 S17 2.2 要求非 static 函数声明在配对头文件(同 net_set_errno 先例)。 验证: - 代码行多重集差异 = 声明的改动集,无一处意外 - @build BUILD_OK 0 错 0 警;Code 212316→212296(−20 B,拆 TU 布局差), RO/RW/ZI 完全不变 - cppcheck / coding-standard / registration-check 全 PASS - @test 硬件闭环 PASS:635ms 到 net init: OK,:8888/:2404/FTP 均建立, [ERR]/Fault/relisten/reconcile 命中 0 条 - 台账随 Code 变化同步(ram_baseline --write / check_mem_numbers --write) 遗留(与仍未提交的文档工作同文件,另提):BSD_Socket_API 使用指南的文件 职责表、net_listener.c/.h 文件头"见 net_socket.c"、自愈架构设计 §11 仍把 net_reconcile_tick 记在 net_socket.c——doc-links 只校验 § 编号存在性, 照不出"函数在哪个文件",需在文档提交中一并修正。 --- Drivers/BSP/NET/net_events.c | 663 +++++++++++++++++++++++++ Drivers/BSP/NET/net_events.h | 46 ++ Drivers/BSP/NET/net_socket.c | 629 +---------------------- Drivers/BSP/NET/net_socket.h | 12 + MDK-ARM/STM32F407-Demo.uvprojx | 5 + docs/内存管理/RAM使用分析与优化方案.md | 4 +- docs/内存管理/RAM预算台账.md | 2 +- scripts/check_registration.py | 1 + scripts/ram_baseline.tsv | 2 +- 9 files changed, 745 insertions(+), 619 deletions(-) create mode 100644 Drivers/BSP/NET/net_events.c create mode 100644 Drivers/BSP/NET/net_events.h diff --git a/Drivers/BSP/NET/net_events.c b/Drivers/BSP/NET/net_events.c new file mode 100644 index 00000000..c7c64c4f --- /dev/null +++ b/Drivers/BSP/NET/net_events.c @@ -0,0 +1,663 @@ +/* + * 模块名称:Network Events & Reconcile + * 模块功能:芯片边沿事件处理 + 100ms 周期对账(真机专属)—— 本层状态镜像与芯片状态面的 + * **唯一收敛实现**:5 个 handle_*_event(net_poll 的分派目标)、 + * net_reconcile_tick(形态①②③④⑤/U11 判据)、net_lost_connect_probe + * (accept 路径补 accept)。**只置事实与登记意图**:重建监听的**动作**在 + * net_listener_tick(net_listener.c),本模块只调 net_listener_request。 + * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 + * 作者:王建锋 + * 创建日期:2026-10-14 + * 修改记录: + * 2026-10-14 王建锋 从 net_socket.c 抽出(与 net_listener.c 同族模块化):事件处理与 + * 周期对账同属"状态面收敛"一个职责,且对账直接调用事件处理器 2 处 + * → 合为一个模块,跨模块导出为零。搬移时 4 处 fd 取法替换(其余 + * 代码逐行未改):fd 反推 `p_sock - s_net_socks` → `p_sock->ch395_sock` + * (net_socket() 里 `ch395_sock = sockfd`,恒等,模拟器同构造); + * 对账循环 `&s_net_socks[i]` → `net_sock_raw(i)`。 + * ⚠ 随之**收窄**两处沉降登记的范围门 `(qfd >= 0) && (qfd < MAX)` → + * `qfd < MAX`:`ch395_sock` 是 uint8_t,下界在类型上不可能越界, + * 留着会被 cppcheck 判 `knownConditionTrueFalse` 拦提交。**勿加回**。 + * + * 设计依据 → docs/网络/网络自愈架构设计.md §4(形态表 §4.1 / 单一所有者 §4.2 / 沉降 §4.3) + * 依赖(单向,无反向 include):net_socket.h(net_sock_raw / net_set_errno / NET_ERR_*) + * net_events.h(本模块声明)/ net_listener.h(settle / request)/ net_pending.h(形态⑤ + * 取消挂起)/ 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" +#include "net_events.h" +#include "net_listener.h" +#include "net_pending.h" +#include "ch395f.h" +#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */ +#include "net_latency_test.h" +#include "main.h" + +/* 深调用链模块:与 net_socket.c 同口径收 128 B(最长行渲染后 ≈100 B < 128 → 不截断) */ +#define DBG_LINE_BUF_MAX 128U +#define DBG_TAG "[NET]" +#include "dbg_log.h" + +/* 模块内前向声明 */ +static void fire_event(net_sock_t *p_sock, net_event_t event); + + +#if defined(NET_BENCH_CMD_LOG) +/* + * 函数功能:台架 Bench 芯片命令序列日志(每条真实 SPI 命令记一行,排障用) + * 入口参数:tag - 命令类别 (P=poll全局中断 R=recv S=send C=close + * A=accept L=listen N=connect U=relisten M=其他) char + * sock - CH395F socket 号 int + * detail - 结果/中断状态 int + * 出口参数:无 + * 返回值:无 + * 限定条件:NET_BENCH_CMD_LOG 启用(2026-09-22 排障台架工具,默认关);netTask 上下文 + * 函数说明:格式 netcmd: fd= t= d=<距上条命令 tick 间隔> + * x=;台架专用,实验后回退 + */ +static uint32_t s_bench_prev_tick = 0U; +static uint8_t s_bench_prev_valid = 0U; + +void net_bench_cmd_log(char tag, int sock, int detail) { + /* 当前 tick(命令时间戳) */ + uint32_t now = (uint32_t)xTaskGetTickCount(); + /* 距上一条命令的 tick 间隔(首条命令记 0) */ + uint32_t delta = (s_bench_prev_valid != 0U) ? (now - s_bench_prev_tick) : 0U; + /* 更新"上一条命令"记录,供下次计算间隔 */ + s_bench_prev_tick = now; + s_bench_prev_valid = 1U; + /* 台架日志:命令类别 + fd + tick + 间隔 + 结果/中断状态 */ + DBG_INFO("netcmd: %c fd=%d t=%u d=%u x=%d", tag, sock, now, delta, detail); +} +#endif + + +/* 对账+pump 合并周期(自愈收敛阶段 3:原 200ms 对账 / 100ms pump 合一) */ +#define NET_RECONCILE_PERIOD_MS 100U +/* 同一 socket 两次动手的最小间隔(防抖成风暴) */ +#define NET_RECONCILE_COOLDOWN_MS 1000U +/* 分级去抖门槛(单位=轮) */ +#define NET_RECON_STABLE_ACCEPT 1U /* 形态② CONNECT 事件未收到补 accept(幽灵甄别兜底假阳性) */ +#define NET_RECON_STABLE_RELISTEN 2U /* 形态③④ 监听失位重建 / ⑤ 静默回收 RESYNC */ +#define NET_RECON_STABLE_DEATH 4U /* 形态① 连接已失(M3 让位窗 400ms,勿改小) */ + + +/* + * 函数功能:处理 CH395F CONNECT 中断事件(单连接到达 / Client 连接成功) + * 入口参数:p_sock - 发生连接事件的 Socket 控制块指针 net_sock_t * + * 出口参数:无 + * 返回值:无 + * 限定条件:net_poll() 检测到 CONNECT 中断后调用;netTask 上下文 + * 函数说明:1. 单连接:自身转 ESTABLISHED 并读取对端地址 + * 2. TCP Client(CONNECTING):连接成功,读取对端地址并置 ESTABLISHED + * 3. 连接成功后使能 KeepAlive 并触发 NET_EVENT_CONNECTED 回调 + */ +void handle_connect_event(net_sock_t *p_sock) { +#if defined(NET_BENCH_CMD_LOG) + /* Bench:记 'a'(accept)命令 */ + net_bench_cmd_log('a', (int)p_sock->ch395_sock, 0); +#endif + /* 监听态:有连接到达 */ + if (p_sock->state == NET_SOCK_STATE_LISTENING) { + /* 单连接:收到 CONNECT 中断 = 自身有连接到达,转为 ESTABLISHED */ + DBG_INFO("sock%d standalone accept", p_sock->ch395_sock); + /* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */ + uint8_t remote[6]; + /* 回读对端 IP + 端口 */ + ch395f_get_remot_ipp(p_sock->ch395_sock, remote); + /* 对端 IP 数组(前 4 字节,大端) */ + memcpy(p_sock->remote_ip_arr, remote, 4); + /* 小端 IP 组装为 32 位值 */ + p_sock->remote_ip = ((uint32_t)remote[0]) | + ((uint32_t)remote[1] << 8) | + ((uint32_t)remote[2] << 16) | + ((uint32_t)remote[3] << 24); + /* 对端端口(小端 2 字节) */ + p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); + /* 状态 → ESTABLISHED(监听自身转数据通道) */ + p_sock->state = NET_SOCK_STATE_ESTABLISHED; + /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置 + * ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内 + * recv 仍会误得 EOF(把新客户端踢开) */ + p_sock->session_dead = 0U; + /* F-NET-17 M3 前置:清可疑电平——探针(LISTENING 期)残留计数会带进新会话, + * 不补此清零则 M3 矛盾判据首轮即被旁路(T6 实测 grep:connect 原不清此字段) */ + p_sock->lost_conn_seen = 0U; + /* 新开 Socket 发送缓冲按空闲计 */ + p_sock->send_ready = 1; + + /* 清除残留的 TIMEOUT 中断标志 */ + /* 读即清:本 Socket 中断状态寄存器 */ + (void)ch395f_get_sock_int_status(p_sock->ch395_sock); + + /* §5.56:TCP 服务器须在 CONNECT 后开启 KeepAlive。2026-10-12 曾加后 + * 撤回(当时仅证明"物理断链"已被 +2s TIM_OUT 快速覆盖,其余场景 + * 无法注入、未证明);E11(2026-10-13,《网络自愈架构设计》)用"禁用 PC 网卡" + * 注入"链路在、对端静默"(NAT/黑洞)补上证据链:生产参数 60/5/3 + * 实测 ≈30 s 判死 → TIM_OUT → QUARANTINE → reconciler 重建;对端存活时 + * 探测被 ACK、空闲会话不误杀。消费链路现成,按手册位置转正。 */ + ch395f_set_keepalive_enable(p_sock->ch395_sock, 1); + + /* 触发连接建立事件回调 */ + fire_event(p_sock, NET_EVENT_CONNECTED); + } else if (p_sock->state == NET_SOCK_STATE_CONNECTING) { + /* TCP Client 连接成功 */ + /* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */ + uint8_t remote[6]; + /* 回读对端 IP + 端口 */ + ch395f_get_remot_ipp(p_sock->ch395_sock, remote); + /* 对端 IP 数组(前 4 字节) */ + memcpy(p_sock->remote_ip_arr, remote, 4); + /* 小端 IP 组装为 32 位值 */ + p_sock->remote_ip = ((uint32_t)remote[0]) | + ((uint32_t)remote[1] << 8) | + ((uint32_t)remote[2] << 16) | + ((uint32_t)remote[3] << 24); + /* 对端端口(小端 2 字节) */ + p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); + /* 状态 → ESTABLISHED */ + p_sock->state = NET_SOCK_STATE_ESTABLISHED; + /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING + * 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */ + p_sock->session_dead = 0U; + /* F-NET-17 M3 前置:清可疑电平(同 LISTENING 分支) */ + p_sock->lost_conn_seen = 0U; + + /* 使能 KeepAlive(客户端连接保活) */ + ch395f_set_keepalive_enable(p_sock->ch395_sock, 1); + + /* 触发连接建立事件回调 */ + fire_event(p_sock, NET_EVENT_CONNECTED); + } +} + +/* + * 函数功能:处理 Socket 断开(DISCONNECT)事件,更新 Socket 状态机 + * 入口参数:p_sock - 发生断开事件的 Socket 控制块指针 net_sock_t * + * 出口参数:无 + * 返回值:无 + * 限定条件:net_poll() 检测到 DISCONNECT 中断后调用;netTask 上下文 + * 函数说明:1. ⚠ FUN_PARA bit3=1(本仓 net_init 置 0x08):DISCONNECT/TIM_OUT 后芯片 + * **不**关 Socket、**不清**缓冲(停 0x05/0x00)——残留数据须由本层读出后,再由 + * 本层 ch395f_close_socket 关闭;芯片不代劳(§5.52② / §5.23),不得跳过 + * 显式 close(U12 成因之一)。之后需重新 OPEN + LISTEN + * 2. 单连接:置 CLOSED,交由 net_recv_locked 排空剩余数据后再关 + * 3. 触发 NET_EVENT_DISCONNECTED 回调 + */ +void handle_disconnect_event(net_sock_t *p_sock) { +#if defined(NET_BENCH_CMD_LOG) + /* Bench:记 'd'(disconnect)命令 */ + net_bench_cmd_log('d', (int)p_sock->ch395_sock, 0); +#endif + /* 由控制块指针反推描述符 */ + int sockfd = (int)p_sock->ch395_sock; + + /* 监听态:DISCONNECT 落在监听器上 */ + if (p_sock->state == NET_SOCK_STATE_LISTENING) { + /* TRAP-NET-17 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡; + * 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现); + * N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交 + * net_reconcile_tick 形态③(每 100 ms 读驱动缓存,监听失位即重建)。原处直接 + * relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先" + * 于消费者 recv(P1 已消除其后果,P2 消除其可能)。 + * ⚠ 本留痕回读保持**主动 SPI**(不用驱动缓存):事件路径上缓存必然滞后 + * 于刚到达的中断(刷新在 net_poll 末尾、事件处理之后),仅周期对账可用缓存 */ + /* 回读缓冲(芯片状态 2 字节) */ + uint8_t st[2] = {0, 0}; + + /* 回读芯片状态(仅为留痕:重建与否由 reconciler 裁决) */ + 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), reconciler will relisten", + sockfd, st[0], st[1]); + } 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 被"关闭 → 重新监听 → 再连接"循环复用,上一条连接的 FIN/ACK + * 可能在 fd 已复用为新连接**之后**才上报(net_close_locked 排空的中断里还没有它)→ 新连接被误判为断开(实测:FTP 同一会话第二个数据连接必失败 425); + * 处置:回读芯片状态,TCP 仍 ESTABLISHED 判陈旧丢弃(已读清不重复上报);真断开时芯片处于 CLOSE_WAIT(0x07)/CLOSED(0x00) 等状态照常处理;超时事件走 handle_timeout_event,不受影响 */ + /* 芯片状态回读缓冲(2 字节) */ + uint8_t st[2]; + + /* 回读芯片状态(Socket + TCP) */ + ch395f_get_socket_status((uint8_t)sockfd, st); + /* 芯片仍 ESTABLISHED:陈旧事件 */ + if (st[1] == CH395F_TCP_ESTABLISHED) { + /* 留痕:陈旧事件丢弃 */ + DBG_ERROR("sock%d stale DISCONNECT ignored (chip TCP still ESTABLISHED)", sockfd); + /* 直接返回(中断已读清,不会重复上报) */ + return; + } + /* F-NET-17 M3(自愈专项实验 2026-10-13):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05) + * 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT + * (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨 + * (结构性失明,F-NET-15 Q1)。处置:不吃边沿,置可疑电平返回,交对账① + * (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(自愈收敛阶段 3 合并 + * 后=4×100ms,NET_RECON_STABLE_DEATH,让位租约 400ms 不变 + 冷却): + * flag==1 旁路本门 → 正常释放;连接真活则租约内 + * 会话正常完成(风暴链掐断,E9a n30 0/50 实证)。 + * 零额外 SPI 于常规路径(st[] 已读;recv_len 仅可疑分支多读一条)。 + * ⚠ E9a/E9b 实测两轮收窄:①必须含 recv_bytes==0——已读数据的会话遇 + * 0x05/0x00 多为真死(探针控制通道教训);②必须含芯片 RX>0——延迟保护的 + * 本质是"在途投递不可毁"(closed-with-data 救回同源红线:RX 有数据=消费者 + * 资产)。@0ms 快复连(连上即关、零数据,E9b :21 46/50 教训)RX 空 → 立即 + * 释放,误杀也无数据损失,后到数据由救回路径接住。边界:滞后锁存杀 + * "已读数据或 RX 空"会话的形态不在本门保护域(E8 窗口未出现该受害形态)。 + * 零误伤:健康 FIN=0x07、静默回收=0x00/0x00 均不命中(E5 20/20 样本) */ + if ((st[0] == CH395F_SOCKET_OPEN) && (st[1] == CH395F_TCP_CLOSED) && + (p_sock->lost_conn_seen == 0U) && (p_sock->recv_bytes == 0U) && + (ch395f_get_recv_len((uint8_t)sockfd) > 0U)) { + /* 置可疑电平(复用探针字段,零 RAM;connect/relisten 清零) */ + p_sock->lost_conn_seen = 1U; + /* 留痕(INFO 级:这是预期自愈路径,非错误,勿污染 [ERR] 台架判据) */ + DBG_INFO("sock%d suspect DISCONNECT on live conn (chip 0x%02X/0x%02X), defer to reconcile", + sockfd, st[0], st[1]); + /* 不释放、不登记沉降、不动 session_dead——保留 400ms 租约窗口 */ + return; + } + /* 单连接:先不关 CH395F Socket, + * 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */ + DBG_INFO("sock%d DISCONNECTED, releasing (chip st=0x%02X/0x%02X)", + p_sock->ch395_sock, st[0], st[1]); + /* TRAP-NET-18 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态 + * 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226; + * 仅**本连接收过数据**时登记沉降(实测触发源 = 收过数据的数据通道被关闭),纯连接即关/只发不收的连接免沉降,保住 TRAP-NET-17 的快速复连时序(:21 @0ms ×50 全通过) */ + /* F-NET-15 M1(自愈专项实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的 + * 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是 + * 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测); + * 门原保护目标(:21 @0ms 快复连)已被 N-32 单一重建入口延迟化(恢复窗实测 0.3~1s)架空, + * +100ms 沉降落在窗内不可见。沉降窗口同时让残渣 DISCONNECT 在 state==CLOSED + * (无害、仅留痕)时被消费,断开"杀下一条 → 新拆链 → 再杀"的繁殖链 */ + { + /* fd 取自 uint8_t ch395_sock → 恒非负,只需上界门 */ + int qfd = (int)p_sock->ch395_sock; + if (qfd < NET_MAX_SOCKETS) { + /* 登记优雅沉降(NET_RELISTEN_SETTLE_GRACEFUL) */ + net_listener_settle(qfd, NET_LISTENER_SETTLE_GRACEFUL); + } + } + /* 正常释放(对端 FIN):断连时挂起项以 EOF(0) 结束 */ + p_sock->released_abnormal = 0U; + /* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**, + * 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间 + * recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据 + * 落到本标志 → 仍得 EOF,不因排空慢而漏报 */ + p_sock->session_dead = 1U; + /* N-30(TRAP-NET-20):登记本连接对端端口(幽灵态甄别基准,须在状态 + * 迁移前记录——remote_port 此刻仍有效,relisten 清零) */ + p_sock->last_peer_port = p_sock->remote_port; + /* 状态 → CLOSED */ + p_sock->state = NET_SOCK_STATE_CLOSED; + /* 触发断连事件回调 */ + /* 触发断连事件回调 */ + fire_event(p_sock, NET_EVENT_DISCONNECTED); + /* 其他状态:未预期的断开 */ + } else if (p_sock->state != NET_SOCK_STATE_CLOSED && + p_sock->state != NET_SOCK_STATE_BOUND && + p_sock->state != NET_SOCK_STATE_CREATED) { + /* 未预期的断开:记错误日志 */ + DBG_ERROR("sock%d disconnect in state %d", p_sock->ch395_sock, p_sock->state); + /* 触发断连事件回调(统一) */ + fire_event(p_sock, NET_EVENT_DISCONNECTED); + } +} + +/* + * 函数功能:处理 Socket 数据接收(RECV_OK)事件 + * 入口参数:p_sock - 发生接收事件的 Socket 控制块指针 net_sock_t * + * 出口参数:无 + * 返回值:无 + * 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文 + * 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层 + * 2. 不在此读取 recv_len:单一读取点归 net_recv_locked(省一次 SPI 查询) + */ +void handle_recv_event(net_sock_t *p_sock) { + /* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len, + * 单一读取点在 net_recv_locked(省 SPI;原"部分读归零"顾虑已降级) */ + /* 置可读标志,供 net_select 在无 SPI 访问下判读 */ + p_sock->readable = 1; + /* 触发数据到达事件回调 */ + fire_event(p_sock, NET_EVENT_DATA_RECEIVED); +} + +/* + * 函数功能:处理 Socket 发送完成(SEND_OK)事件 + * 入口参数:p_sock - 发生发送完成事件的 Socket 控制块指针 net_sock_t * + * 出口参数:无 + * 返回值:无 + * 限定条件:net_poll() 检测到 SEND_OK 中断后调用;netTask 上下文 + * 函数说明:触发 NET_EVENT_SEND_COMPLETE 回调通知上层 + */ +void handle_send_event(net_sock_t *p_sock) { + /* 触发发送完成事件回调 */ + fire_event(p_sock, NET_EVENT_SEND_COMPLETE); +} + +/* + * 函数功能:处理 Socket 超时(SOCK_TIMEOUT)事件,更新 Socket 状态机 + * 入口参数:p_sock - 发生超时事件的 Socket 控制块指针 net_sock_t * + * 出口参数:无 + * 返回值:无 + * 限定条件:net_poll() 检测到 SOCK_TIMEOUT 中断后调用;netTask 上下文 + * 函数说明:1. TCP Client(CONNECTING):连接超时,回到 CREATED 状态 + * 2. 已建立连接:回到 CLOSED(单连接) + * 3. 监听态:异常超时,记录错误日志 + * 4. 触发 NET_EVENT_TIMEOUT 回调 + */ +void handle_timeout_event(net_sock_t *p_sock) { +#if defined(NET_BENCH_CMD_LOG) + /* Bench:记 'x'(timeout)命令 */ + net_bench_cmd_log('x', (int)p_sock->ch395_sock, 0); +#endif + /* 连接中状态:连接超时 */ + if (p_sock->state == NET_SOCK_STATE_CONNECTING) { + /* TCP Client 连接超时 */ + DBG_ERROR("sock%d CONNECT TIMEOUT", p_sock->ch395_sock); + /* 状态 → CREATED(客户端槽位回可复用) */ + p_sock->state = NET_SOCK_STATE_CREATED; + } else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) { + /* 已建立连接超时 */ + DBG_ERROR("sock%d TIMEOUT", p_sock->ch395_sock); + /* TRAP-NET-18:异常释放(TIMEOUT,例如客户端中止上传)→ 复监听前隔离, + * 等对端在途数据落尽并排空 RX 残渣(详见 net_relisten_locked 区块注释) */ + { + /* fd 取自 uint8_t ch395_sock → 恒非负,只需上界门 */ + int qfd = (int)p_sock->ch395_sock; + if (qfd < NET_MAX_SOCKETS) { + /* 登记异常释放隔离(重监听前等待在途数据落尽 + RX 排空) */ + net_listener_settle(qfd, NET_LISTENER_SETTLE_QUARANTINE); + } + } + /* TRAP-NET-18 待办 #3:标记异常释放——挂起 recv 排空断连后以 + * NET_ERR_CONNRESET 结束(而非 EOF),应用(lftpd STOR)回报 5xx + * 而非误报 226(见 net_pending_tick 断连分支) */ + p_sock->released_abnormal = 1U; + /* N-30(TRAP-NET-20):异常释放同样登记对端端口(幽灵甄别基准) */ + p_sock->last_peer_port = p_sock->remote_port; + /* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST + * 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与 + * released_abnormal(结束得好不好看)正交) */ + p_sock->session_dead = 1U; + /* 状态 → CLOSED */ + p_sock->state = NET_SOCK_STATE_CLOSED; + } else if (p_sock->state == NET_SOCK_STATE_LISTENING) { + /* 监听超时(异常情况) */ + DBG_ERROR("sock%d LISTEN TIMEOUT", p_sock->ch395_sock); + } + + /* 触发超时事件回调(统一) */ + fire_event(p_sock, NET_EVENT_TIMEOUT); +} + +/* + * 函数功能:触发 Socket 事件回调(若已注册) + * 入口参数:p_sock - 触发事件的 Socket 控制块指针 net_sock_t * + * event - 事件类型 net_event_t + * 出口参数:无 + * 返回值:无 + * 限定条件:net_init() 已调用 + * 函数说明:1. 仅当 p_sock->event_cb 非空时调用回调 + * 2. 回调参数为 ch395_sock、event、event_arg + */ +static void fire_event(net_sock_t *p_sock, net_event_t event) { + /* 已注册回调 */ + if (p_sock->event_cb != NULL) { + /* 调用回调(ch395_sock、event、用户参数) */ + p_sock->event_cb(p_sock->ch395_sock, event, p_sock->event_arg); + } +} + + +/* + * 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 事件 + * 未收到时补做 accept(复用 CONNECT 事件簿记路径)。 + * 入口参数:p_sock - 处于 LISTENING 的监听控制块 net_sock_t * + * 出口参数:无(命中时 p_sock->state 经 handle_connect_event 转 ESTABLISHED) + * 返回值:无 + * 限定条件:netTask 上下文(net_accept_locked 内调用,其开头已 net_poll(), + * 驱动缓存新鲜度有保证) + * 函数说明:1. 自愈收敛阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一 + * 刷新点=net_poll),**探针去抖删除**——原去抖防的两类瞬态已 + * 结构性消除:"刚 listen 尚未生效"显示 TCP=00 永不命中 ESTABLISHED + * 门(E2:LISTEN 可见 100% <=10ms 无长尾);拆链窗假 ESTABLISHED + * 由幽灵甄别拦截。NET_LOST_CONN_STABLE_N 随之下线 + * 2. 幽灵态(对端端口 = last_peer_port)不动手,计数清零 + */ +void net_lost_connect_probe(net_sock_t *p_sock) { + /* 对端 IP+端口回读缓冲(4B IP + 2B 端口小端) */ + uint8_t remote[6] = {0, 0, 0, 0, 0, 0}; + /* 驱动状态缓存(net_poll 每轮刷新;本探针入口前 accept 已 net_poll()) */ + ch395f_sock_state_t c = ch395f_sock_state_cached(p_sock->ch395_sock); + + /* 芯片尚未完成握手:可疑电平计数清零,本轮无动作 */ + if (c.tcp_state != CH395F_TCP_ESTABLISHED) { + p_sock->lost_conn_seen = 0U; + return; + } + /* 已 ESTABLISHED:回读对端端口做幽灵甄别(按需 SPI,仅命中态发) */ + ch395f_get_remot_ipp(p_sock->ch395_sock, remote); + /* 对端端口(小端 2 字节) */ + uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8)); + /* 幽灵甄别:仅"端口非零且 = 上一条连接端口"才判幽灵拦截; + * peer==0(芯片对端端口提交滞后,TRAP-NET-13 同类)不拦截——宁可补 accept + * 也不退回事件未收口的死等(2026-10-11 验证轮实测:peer==0 拦截把首连接 + * 事件未收到的原有对账救援也挡死,LIST#1 卡满 20s,比修复前更糟) */ + if ((peer != 0U) && (peer == p_sock->last_peer_port)) { + p_sock->lost_conn_seen = 0U; + return; + } + /* 自愈收敛阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除, + * 见函数头注释 1);lost_conn_seen 仅作 M3 可疑电平标志用,动手前清零 */ + p_sock->lost_conn_seen = 0U; + /* 留痕:事件未收到自愈命中 */ + DBG_INFO("sock%d lost-CONNECT self-accept (peer=%u)", + (int)p_sock->ch395_sock, peer); + /* 复用 CONNECT 事件的簿记路径(转 ESTABLISHED + 记对端 + 事件回调) */ + handle_connect_event(p_sock); +} + + +/* + * 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器 pump(N-32 P2)—— 自愈收敛阶段 3 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1) + * 入口参数:无 + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) + * 函数说明:1. 芯片快速复连会收不到 CONNECT/DISCONNECT 事件(读清消费竞态,E14 复核:非芯片丢事件),本层无法靠事件自愈 + * 2. 判据读**驱动状态缓存**(唯一刷新点=net_poll 每轮全量刷新,E2 实测单次回读 18.1us) + * 3. 覆盖形态(原对账①②⑤ + 原 pump③④/U11): + * ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实) + * ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(命中时 + * 才回读对端端口做幽灵甄别——本 tick 唯一按需 SPI) + * ⑤(F-NET-2)本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01 无 + * DISCONNECT)→ RESYNC 只修本层 + * ③ 本层 LISTENING(监听型 fd)而芯片监听失位 → 重建 + * ④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建 + * U11 本层 CLOSED(监听型 fd)而芯片在听 → 只重同步,**不发芯片命令** + * (重下发因 USE 错误关掉正听着的 Socket,U12 同源事故) + * 4. 去抖分级(100ms/轮;E2:LISTEN 可见 100% <=10ms 无长尾):②=1 轮 + * (幽灵甄别挡拆链窗的旧连接 ESTABLISHED);③④⑤=2 轮(2026-09-23 实证: + * 无去抖时监听被反复重建直至放弃);①=4 轮(**勿改小**:F-NET-17 M3 + * 可疑电平让位再入,400ms 让位窗=在途投递不可毁的最后租约,E9a 实证) + * 5. 动手后冷却 NET_RECONCILE_COOLDOWN_MS 防风暴(U11 被动重同步不占) + * 6. 链路态订阅(F-DRV-13):驱动 link 变化计数变化 → 留痕(不自动重配) + */ +void net_reconcile_tick(void) { + /* 上次对账轮的时刻(tick) */ + 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; + + /* 未到对账周期(100ms):直接返回(缓存刷新在 net_poll,与本函数无关) */ + if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) { + return; + } + /* 更新本轮起始时刻 */ + s_last = now; + + /* 链路态订阅(F-DRV-13):net_poll 已喂驱动,这里只看计数变化 */ + { + /* 链路变化计数订阅基线(0xFFFF=未建档) */ + static uint16_t s_link_last = 0xFFFFU; + uint16_t lc = ch395f_link_change_count(); + if (s_link_last != lc) { + if (s_link_last != 0xFFFFU) { + DBG_INFO("net: PHY link %s (changes=%u)", + ch395f_link_up_cached() ? "up" : "down", (unsigned)lc); + } + s_link_last = lc; + } + } + + for (i = 0; i < NET_MAX_SOCKETS; i++) { + /* 取控制块(直接数组下标,无需越界判断) */ + net_sock_t *p_sock = net_sock_raw(i); + /* 驱动状态缓存(net_poll 每轮刷新,本 tick 读到的新鲜度 <=1ms) */ + ch395f_sock_state_t c = ch395f_sock_state_cached((uint8_t)i); + /* 芯片当前是否在 LISTEN(二段关闭中间态 05/00 不算在听) */ + int chip_listening = ((c.open != CH395F_SOCKET_CLOSED) && + (c.tcp_state == CH395F_TCP_LISTEN)); + /* 监听型 fd 门(原 pump 认领口径:auto_relisten 是决定性门——少了它, + * "本层 CLOSED + 已绑端口"的出向连接会被误判为监听器并被 relisten) */ + int listener_fd = ((p_sock->auto_relisten != 0U) && + (p_sock->type == NET_SOCK_STREAM) && + (p_sock->local_port != 0U)); + /* 失同步类型:1=连接已失 2=握手已成事件未收到 3=监听失位 4=孤儿监听 + * 5=芯片静默回收回监听(编号沿用历史口径,U11 重同步不设编号——被动零动作) */ + int bad = 0; + /* 本形态去抖门槛(默认按重建档) */ + uint8_t stable_need = NET_RECON_STABLE_RELISTEN; + + if (p_sock->in_use == 0U) { + s_bad[i] = 0U; + continue; + } + + if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) { + /* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理 */ + if ((c.open == CH395F_SOCKET_CLOSED) || (c.tcp_state == CH395F_TCP_CLOSED)) { + bad = 1; + stable_need = NET_RECON_STABLE_DEATH; + } else if ((c.open != CH395F_SOCKET_CLOSED) && + (c.tcp_state == CH395F_TCP_LISTEN)) { + /* ⑤(F-NET-2 缺口,自愈收敛阶段1):芯片静默回收——本层 ESTABLISHED + * 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT + * 中断**,run8 台架实证)→ RESYNC 语义 */ + bad = 5; + } + } else if (p_sock->state == NET_SOCK_STATE_LISTENING) { + if (c.tcp_state == CH395F_TCP_ESTABLISHED) { + /* ② 本层 LISTENING 而芯片已完成握手(CONNECT 事件未收到)→ 补做 accept + * N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实 + * 显示 ESTABLISHED(E15:bit3=1 保留现场契约 + TCP 拆链状态机,非谎报), + * 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给 + * 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。 + * 这是本 tick 唯一的按需 SPI(仅形态②命中时发) */ + uint8_t remote[6] = {0, 0, 0, 0, 0, 0}; + ch395f_get_remot_ipp((uint8_t)i, remote); + uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8)); + /* 仅"端口非零且 = 上一条连接端口"判幽灵拦截;peer==0 不拦截 + * (对端端口提交滞后时不得挡掉事件未收口的救援,见 net_lost_connect_probe 注释) */ + if (!((peer != 0U) && (peer == p_sock->last_peer_port))) { + bad = 2; + stable_need = NET_RECON_STABLE_ACCEPT; + } + } else if (!chip_listening && listener_fd) { + /* ③ 本层 LISTENING 而芯片监听失位 → 重建(原 pump①) */ + bad = 3; + } + } else if ((p_sock->state == NET_SOCK_STATE_CLOSED) && listener_fd) { + if (chip_listening) { + /* U11:本层 CLOSED 而芯片在听 = 本层状态滞后 → 只重同步。 + * **不发任何芯片命令**(重下发 open+listen 会因 USE 错误走进 + * 关闭分支、把正听着的 Socket 关掉,U12 同源事故)。 + * 被动修正零风险:不去抖、不占冷却(原 pump 同口径) */ + DBG_INFO("sock%d reconcile: chip listening, resync CLOSED->LISTENING (U11)", i); + p_sock->state = NET_SOCK_STATE_LISTENING; + /* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 + * —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的, + * 必须转登为基准,否则形态② 的幽灵拦截会失效 */ + p_sock->last_peer_port = p_sock->remote_port; + p_sock->lost_conn_seen = 0U; + s_bad[i] = 0U; + continue; + } + /* ④ 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原 pump②) */ + bad = 4; + } else { + /* 其余状态(CREATED/BOUND/UDP_OPEN 等)不归本 tick 认领 */ + s_bad[i] = 0U; + continue; + } + + /* 无失同步:清零计数,转下一 socket */ + if (bad == 0) { + s_bad[i] = 0U; + continue; + } + /* 累加连续失配计数(封顶至本形态门槛) */ + if (s_bad[i] < stable_need) { + s_bad[i]++; + } + /* 去抖未满或处于冷却期:本轮不动手 */ + if ((s_bad[i] < stable_need) || + ((int32_t)(now - s_cool[i]) < 0)) { + continue; + } + /* 动手前:清失配计数并置冷却到期时刻(1s) */ + s_bad[i] = 0U; + s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS; + + if (bad == 1) { + /* 类型①:按断开处理(应用读到 EOF 后由形态③④重建监听) */ + DBG_INFO("sock%d reconcile: chip closed (0x%02X/0x%02X) -> disconnect", + i, c.open, c.tcp_state); + /* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发); + * 只置事实,重建由本 tick 形态③④统一做(N-32 P2 单一所有者) */ + handle_disconnect_event(p_sock); + } else if (bad == 5) { + /* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体 + * 原样并入,自愈收敛阶段1;纪律同 U11:**不发任何芯片命令**—— + * 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒, + * run8 实证 170s 拒绝) */ + DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync", + i, c.open, c.tcp_state); + /* 仅修 MCU 侧;新连接的 CONNECT 中断会把两侧重新同步 */ + p_sock->state = NET_SOCK_STATE_LISTENING; + /* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 */ + p_sock->last_peer_port = p_sock->remote_port; + p_sock->lost_conn_seen = 0U; + /* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */ + net_pending_cancel(i, NET_ERR_CONNRESET); + /* 挂起 send 同样取消(会话已被芯片收走,剩余字节不可能送达) */ + net_send_pending_cancel(i, NET_ERR_CONNRESET); + } else if ((bad == 3) || (bad == 4)) { + /* 类型③④:监听失位/孤儿监听 → **登记**重建请求(判据在此,动作在 + * net_listener_tick 一处;非阻塞,30ms 节奏重试) */ + DBG_INFO("sock%d reconcile: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten", + i, c.open, c.tcp_state, (int)p_sock->state); + net_listener_request(i, NET_LISTENER_REQ_RECON); + } else { + /* 类型②:补做 standalone accept */ + DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i); + /* 复用 CONNECT 事件的簿记路径 */ + handle_connect_event(p_sock); + } + } +} diff --git a/Drivers/BSP/NET/net_events.h b/Drivers/BSP/NET/net_events.h new file mode 100644 index 00000000..76f89d43 --- /dev/null +++ b/Drivers/BSP/NET/net_events.h @@ -0,0 +1,46 @@ +/* + * 模块名称:Network Events & Reconcile —— 内部头 + * 模块功能:声明「芯片边沿事件处理 + 100ms 周期对账」模块对 net 层宿主提供的接口。 + * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 + * 作者:王建锋 + * 创建日期:2026-10-14 + * + * ⚠ **内部头**:只被 `net_socket.c`(宿主)与 `net_events.c`(实现)include, + * **不进** 公开的 `net_socket.h`,也**不被应用层引用**——与 `net_listener.h` 同族约定。 + * 本模块依赖 `ch395f_*` SPI 驱动,属**真机专属**:模拟器(Winsock 后端)不编译, + * 故已在 `scripts/check_registration.py` 的 SIM_EXEMPT 显式点名(每行附理由)。 + * + * 设计依据 → docs/网络/网络自愈架构设计.md §4 + * 形态表 §4.1 / 单一重建所有者 §4.2 / 沉降与隔离 §4.3 / 读即清信源纪律 §4.10 + */ +#ifndef NET_EVENTS_H +#define NET_EVENTS_H + +#include "net_types.h" /* net_sock_t / net_event_t / NET_MAX_SOCKETS */ + +/* ===== 宿主 net_socket.c 提供 ===== */ + +/* `net_sock_raw()`(对账循环需要的"不判 in_use"数组访问器)声明在**配对头** net_socket.h + * —— 规范 S17 2.2 要求非 static 函数声明在配对头文件,内部头不算(同 net_set_errno 先例)。 */ + +/* ===== 本模块 net_events.c 提供 ===== */ + +/* 芯片边沿事件处理器:由 net_poll() 在全局中断位命中后分派(netTask 上下文) */ +void handle_connect_event(net_sock_t *p_sock); /* CONNECT:转 ESTABLISHED + 记对端 + KeepAlive */ +void handle_disconnect_event(net_sock_t *p_sock); /* DISCONNECT:陈旧甄别 + 登记沉降事实 */ +void handle_recv_event(net_sock_t *p_sock); /* RECV_OK:置 readable 电平 */ +void handle_send_event(net_sock_t *p_sock); /* SEND_OK:清发送忙 + 回调 */ +void handle_timeout_event(net_sock_t *p_sock); /* TIM_OUT:按异常释放处理(隔离重建) */ + +/* 100ms 周期对账:形态①②③④⑤ + U11 的唯一判据所在;由 net_process_messages 每轮调用 */ +void net_reconcile_tick(void); + +/* accept 路径探针:芯片已 ESTABLISHED 而 CONNECT 事件未到时补做 accept */ +void net_lost_connect_probe(net_sock_t *p_sock); + +#if defined(NET_BENCH_CMD_LOG) +/* 台架芯片命令序列日志(编译开关默认关;net_poll / 消息分派 / 事件处理三处消费) */ +void net_bench_cmd_log(char tag, int sock, int detail); +#endif + +#endif /* NET_EVENTS_H */ diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 22dc07be..6af72cd4 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -19,6 +19,7 @@ #include "net_socket.h" #include "net_listener.h" +#include "net_events.h" /* 事件处理 + 周期对账(真机专属,同目录内部头) */ #include "net_pending.h" #include "ch395f.h" #include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */ @@ -170,18 +171,11 @@ int net_set_errno(int err) { } - /* * 私有函数声明区 */ static int alloc_socket(void); -static void handle_connect_event(net_sock_t *p_sock); -static void handle_disconnect_event(net_sock_t *p_sock); -static void handle_recv_event(net_sock_t *p_sock); -static void handle_send_event(net_sock_t *p_sock); -static void handle_timeout_event(net_sock_t *p_sock); -static void fire_event(net_sock_t *p_sock, net_event_t event); static void parse_ip_string(const char *str, uint8_t *ip_arr); static int net_send_locked(net_sock_t *p_sock, const void *buf, int len, TaskHandle_t caller); @@ -382,34 +376,6 @@ void net_generation_inc(void) { s_net_generation++; } -#if defined(NET_BENCH_CMD_LOG) -/* - * 函数功能:台架 Bench 芯片命令序列日志(每条真实 SPI 命令记一行,排障用) - * 入口参数:tag - 命令类别 (P=poll全局中断 R=recv S=send C=close - * A=accept L=listen N=connect U=relisten M=其他) char - * sock - CH395F socket 号 int - * detail - 结果/中断状态 int - * 出口参数:无 - * 返回值:无 - * 限定条件:NET_BENCH_CMD_LOG 启用(2026-09-22 排障台架工具,默认关);netTask 上下文 - * 函数说明:格式 netcmd: fd= t= d=<距上条命令 tick 间隔> - * x=;台架专用,实验后回退 - */ -static uint32_t s_bench_prev_tick = 0U; -static uint8_t s_bench_prev_valid = 0U; - -static void net_bench_cmd_log(char tag, int sock, int detail) { - /* 当前 tick(命令时间戳) */ - uint32_t now = (uint32_t)xTaskGetTickCount(); - /* 距上一条命令的 tick 间隔(首条命令记 0) */ - uint32_t delta = (s_bench_prev_valid != 0U) ? (now - s_bench_prev_tick) : 0U; - /* 更新"上一条命令"记录,供下次计算间隔 */ - s_bench_prev_tick = now; - s_bench_prev_valid = 1U; - /* 台架日志:命令类别 + fd + tick + 间隔 + 结果/中断状态 */ - DBG_INFO("netcmd: %c fd=%d t=%u d=%u x=%d", tag, sock, now, delta, detail); -} -#endif /* * 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件 @@ -590,210 +556,6 @@ int net_poll(void) { return event_count; } -/* 对账+pump 合并周期(自愈收敛阶段 3:原 200ms 对账 / 100ms pump 合一) */ -#define NET_RECONCILE_PERIOD_MS 100U -/* 同一 socket 两次动手的最小间隔(防抖成风暴) */ -#define NET_RECONCILE_COOLDOWN_MS 1000U -/* 分级去抖门槛(单位=轮) */ -#define NET_RECON_STABLE_ACCEPT 1U /* 形态② CONNECT 事件未收到补 accept(幽灵甄别兜底假阳性) */ -#define NET_RECON_STABLE_RELISTEN 2U /* 形态③④ 监听失位重建 / ⑤ 静默回收 RESYNC */ -#define NET_RECON_STABLE_DEATH 4U /* 形态① 连接已失(M3 让位窗 400ms,勿改小) */ - -/* - * 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器 pump(N-32 P2)—— 自愈收敛阶段 3 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1) - * 入口参数:无 - * 出口参数:无 - * 返回值:无 - * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) - * 函数说明:1. 芯片快速复连会收不到 CONNECT/DISCONNECT 事件(读清消费竞态,E14 复核:非芯片丢事件),本层无法靠事件自愈 - * 2. 判据读**驱动状态缓存**(唯一刷新点=net_poll 每轮全量刷新,E2 实测单次回读 18.1us) - * 3. 覆盖形态(原对账①②⑤ + 原 pump③④/U11): - * ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实) - * ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(命中时 - * 才回读对端端口做幽灵甄别——本 tick 唯一按需 SPI) - * ⑤(F-NET-2)本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01 无 - * DISCONNECT)→ RESYNC 只修本层 - * ③ 本层 LISTENING(监听型 fd)而芯片监听失位 → 重建 - * ④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建 - * U11 本层 CLOSED(监听型 fd)而芯片在听 → 只重同步,**不发芯片命令** - * (重下发因 USE 错误关掉正听着的 Socket,U12 同源事故) - * 4. 去抖分级(100ms/轮;E2:LISTEN 可见 100% <=10ms 无长尾):②=1 轮 - * (幽灵甄别挡拆链窗的旧连接 ESTABLISHED);③④⑤=2 轮(2026-09-23 实证: - * 无去抖时监听被反复重建直至放弃);①=4 轮(**勿改小**:F-NET-17 M3 - * 可疑电平让位再入,400ms 让位窗=在途投递不可毁的最后租约,E9a 实证) - * 5. 动手后冷却 NET_RECONCILE_COOLDOWN_MS 防风暴(U11 被动重同步不占) - * 6. 链路态订阅(F-DRV-13):驱动 link 变化计数变化 → 留痕(不自动重配) - */ -static void net_reconcile_tick(void) { - /* 上次对账轮的时刻(tick) */ - 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; - - /* 未到对账周期(100ms):直接返回(缓存刷新在 net_poll,与本函数无关) */ - if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) { - return; - } - /* 更新本轮起始时刻 */ - s_last = now; - - /* 链路态订阅(F-DRV-13):net_poll 已喂驱动,这里只看计数变化 */ - { - /* 链路变化计数订阅基线(0xFFFF=未建档) */ - static uint16_t s_link_last = 0xFFFFU; - uint16_t lc = ch395f_link_change_count(); - if (s_link_last != lc) { - if (s_link_last != 0xFFFFU) { - DBG_INFO("net: PHY link %s (changes=%u)", - ch395f_link_up_cached() ? "up" : "down", (unsigned)lc); - } - s_link_last = lc; - } - } - - for (i = 0; i < NET_MAX_SOCKETS; i++) { - /* 取控制块(直接数组下标,无需越界判断) */ - net_sock_t *p_sock = &s_net_socks[i]; - /* 驱动状态缓存(net_poll 每轮刷新,本 tick 读到的新鲜度 <=1ms) */ - ch395f_sock_state_t c = ch395f_sock_state_cached((uint8_t)i); - /* 芯片当前是否在 LISTEN(二段关闭中间态 05/00 不算在听) */ - int chip_listening = ((c.open != CH395F_SOCKET_CLOSED) && - (c.tcp_state == CH395F_TCP_LISTEN)); - /* 监听型 fd 门(原 pump 认领口径:auto_relisten 是决定性门——少了它, - * "本层 CLOSED + 已绑端口"的出向连接会被误判为监听器并被 relisten) */ - int listener_fd = ((p_sock->auto_relisten != 0U) && - (p_sock->type == NET_SOCK_STREAM) && - (p_sock->local_port != 0U)); - /* 失同步类型:1=连接已失 2=握手已成事件未收到 3=监听失位 4=孤儿监听 - * 5=芯片静默回收回监听(编号沿用历史口径,U11 重同步不设编号——被动零动作) */ - int bad = 0; - /* 本形态去抖门槛(默认按重建档) */ - uint8_t stable_need = NET_RECON_STABLE_RELISTEN; - - if (p_sock->in_use == 0U) { - s_bad[i] = 0U; - continue; - } - - if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) { - /* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理 */ - if ((c.open == CH395F_SOCKET_CLOSED) || (c.tcp_state == CH395F_TCP_CLOSED)) { - bad = 1; - stable_need = NET_RECON_STABLE_DEATH; - } else if ((c.open != CH395F_SOCKET_CLOSED) && - (c.tcp_state == CH395F_TCP_LISTEN)) { - /* ⑤(F-NET-2 缺口,自愈收敛阶段1):芯片静默回收——本层 ESTABLISHED - * 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT - * 中断**,run8 台架实证)→ RESYNC 语义 */ - bad = 5; - } - } else if (p_sock->state == NET_SOCK_STATE_LISTENING) { - if (c.tcp_state == CH395F_TCP_ESTABLISHED) { - /* ② 本层 LISTENING 而芯片已完成握手(CONNECT 事件未收到)→ 补做 accept - * N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实 - * 显示 ESTABLISHED(E15:bit3=1 保留现场契约 + TCP 拆链状态机,非谎报), - * 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给 - * 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。 - * 这是本 tick 唯一的按需 SPI(仅形态②命中时发) */ - uint8_t remote[6] = {0, 0, 0, 0, 0, 0}; - ch395f_get_remot_ipp((uint8_t)i, remote); - uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8)); - /* 仅"端口非零且 = 上一条连接端口"判幽灵拦截;peer==0 不拦截 - * (对端端口提交滞后时不得挡掉事件未收口的救援,见 net_lost_connect_probe 注释) */ - if (!((peer != 0U) && (peer == p_sock->last_peer_port))) { - bad = 2; - stable_need = NET_RECON_STABLE_ACCEPT; - } - } else if (!chip_listening && listener_fd) { - /* ③ 本层 LISTENING 而芯片监听失位 → 重建(原 pump①) */ - bad = 3; - } - } else if ((p_sock->state == NET_SOCK_STATE_CLOSED) && listener_fd) { - if (chip_listening) { - /* U11:本层 CLOSED 而芯片在听 = 本层状态滞后 → 只重同步。 - * **不发任何芯片命令**(重下发 open+listen 会因 USE 错误走进 - * 关闭分支、把正听着的 Socket 关掉,U12 同源事故)。 - * 被动修正零风险:不去抖、不占冷却(原 pump 同口径) */ - DBG_INFO("sock%d reconcile: chip listening, resync CLOSED->LISTENING (U11)", i); - p_sock->state = NET_SOCK_STATE_LISTENING; - /* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 - * —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的, - * 必须转登为基准,否则形态② 的幽灵拦截会失效 */ - p_sock->last_peer_port = p_sock->remote_port; - p_sock->lost_conn_seen = 0U; - s_bad[i] = 0U; - continue; - } - /* ④ 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原 pump②) */ - bad = 4; - } else { - /* 其余状态(CREATED/BOUND/UDP_OPEN 等)不归本 tick 认领 */ - s_bad[i] = 0U; - continue; - } - - /* 无失同步:清零计数,转下一 socket */ - if (bad == 0) { - s_bad[i] = 0U; - continue; - } - /* 累加连续失配计数(封顶至本形态门槛) */ - if (s_bad[i] < stable_need) { - s_bad[i]++; - } - /* 去抖未满或处于冷却期:本轮不动手 */ - if ((s_bad[i] < stable_need) || - ((int32_t)(now - s_cool[i]) < 0)) { - continue; - } - /* 动手前:清失配计数并置冷却到期时刻(1s) */ - s_bad[i] = 0U; - s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS; - - if (bad == 1) { - /* 类型①:按断开处理(应用读到 EOF 后由形态③④重建监听) */ - DBG_INFO("sock%d reconcile: chip closed (0x%02X/0x%02X) -> disconnect", - i, c.open, c.tcp_state); - /* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发); - * 只置事实,重建由本 tick 形态③④统一做(N-32 P2 单一所有者) */ - handle_disconnect_event(p_sock); - } else if (bad == 5) { - /* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体 - * 原样并入,自愈收敛阶段1;纪律同 U11:**不发任何芯片命令**—— - * 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒, - * run8 实证 170s 拒绝) */ - DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync", - i, c.open, c.tcp_state); - /* 仅修 MCU 侧;新连接的 CONNECT 中断会把两侧重新同步 */ - p_sock->state = NET_SOCK_STATE_LISTENING; - /* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 */ - p_sock->last_peer_port = p_sock->remote_port; - p_sock->lost_conn_seen = 0U; - /* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */ - net_pending_cancel(i, NET_ERR_CONNRESET); - /* 挂起 send 同样取消(会话已被芯片收走,剩余字节不可能送达) */ - net_send_pending_cancel(i, NET_ERR_CONNRESET); - } else if ((bad == 3) || (bad == 4)) { - /* 类型③④:监听失位/孤儿监听 → **登记**重建请求(判据在此,动作在 - * net_listener_tick 一处;非阻塞,30ms 节奏重试) */ - DBG_INFO("sock%d reconcile: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten", - i, c.open, c.tcp_state, (int)p_sock->state); - net_listener_request(i, NET_LISTENER_REQ_RECON); - } else { - /* 类型②:补做 standalone accept */ - DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i); - /* 复用 CONNECT 事件的簿记路径 */ - handle_connect_event(p_sock); - } - } -} - /* * 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮先跑四个非阻塞 @@ -1415,53 +1177,6 @@ int net_listen(int sockfd) { * 对端端口 = 上一条连接端口;新连接临时端口几乎必然不同 → 端口相同不 accept, * 等旧连接拆净 / 新 SYN 到达(新连接若事件也未收到,后续轮询以新端口命中)。 */ -/* - * 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 事件 - * 未收到时补做 accept(复用 CONNECT 事件簿记路径)。 - * 入口参数:p_sock - 处于 LISTENING 的监听控制块 net_sock_t * - * 出口参数:无(命中时 p_sock->state 经 handle_connect_event 转 ESTABLISHED) - * 返回值:无 - * 限定条件:netTask 上下文(net_accept_locked 内调用,其开头已 net_poll(), - * 驱动缓存新鲜度有保证) - * 函数说明:1. 自愈收敛阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一 - * 刷新点=net_poll),**探针去抖删除**——原去抖防的两类瞬态已 - * 结构性消除:"刚 listen 尚未生效"显示 TCP=00 永不命中 ESTABLISHED - * 门(E2:LISTEN 可见 100% <=10ms 无长尾);拆链窗假 ESTABLISHED - * 由幽灵甄别拦截。NET_LOST_CONN_STABLE_N 随之下线 - * 2. 幽灵态(对端端口 = last_peer_port)不动手,计数清零 - */ -static void net_lost_connect_probe(net_sock_t *p_sock) { - /* 对端 IP+端口回读缓冲(4B IP + 2B 端口小端) */ - uint8_t remote[6] = {0, 0, 0, 0, 0, 0}; - /* 驱动状态缓存(net_poll 每轮刷新;本探针入口前 accept 已 net_poll()) */ - ch395f_sock_state_t c = ch395f_sock_state_cached(p_sock->ch395_sock); - - /* 芯片尚未完成握手:可疑电平计数清零,本轮无动作 */ - if (c.tcp_state != CH395F_TCP_ESTABLISHED) { - p_sock->lost_conn_seen = 0U; - return; - } - /* 已 ESTABLISHED:回读对端端口做幽灵甄别(按需 SPI,仅命中态发) */ - ch395f_get_remot_ipp(p_sock->ch395_sock, remote); - /* 对端端口(小端 2 字节) */ - uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8)); - /* 幽灵甄别:仅"端口非零且 = 上一条连接端口"才判幽灵拦截; - * peer==0(芯片对端端口提交滞后,TRAP-NET-13 同类)不拦截——宁可补 accept - * 也不退回事件未收口的死等(2026-10-11 验证轮实测:peer==0 拦截把首连接 - * 事件未收到的原有对账救援也挡死,LIST#1 卡满 20s,比修复前更糟) */ - if ((peer != 0U) && (peer == p_sock->last_peer_port)) { - p_sock->lost_conn_seen = 0U; - return; - } - /* 自愈收敛阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除, - * 见函数头注释 1);lost_conn_seen 仅作 M3 可疑电平标志用,动手前清零 */ - p_sock->lost_conn_seen = 0U; - /* 留痕:事件未收到自愈命中 */ - DBG_INFO("sock%d lost-CONNECT self-accept (peer=%u)", - (int)p_sock->ch395_sock, peer); - /* 复用 CONNECT 事件的簿记路径(转 ESTABLISHED + 记对端 + 事件回调) */ - handle_connect_event(p_sock); -} /* * 函数功能:TCP Server 接受新连接(内部版本,必须在 netTask 上下文调用) @@ -3229,335 +2944,6 @@ static int alloc_socket(void) { return -1; } -/* - * 函数功能:处理 CH395F CONNECT 中断事件(单连接到达 / Client 连接成功) - * 入口参数:p_sock - 发生连接事件的 Socket 控制块指针 net_sock_t * - * 出口参数:无 - * 返回值:无 - * 限定条件:net_poll() 检测到 CONNECT 中断后调用;netTask 上下文 - * 函数说明:1. 单连接:自身转 ESTABLISHED 并读取对端地址 - * 2. TCP Client(CONNECTING):连接成功,读取对端地址并置 ESTABLISHED - * 3. 连接成功后使能 KeepAlive 并触发 NET_EVENT_CONNECTED 回调 - */ -static void handle_connect_event(net_sock_t *p_sock) { -#if defined(NET_BENCH_CMD_LOG) - /* Bench:记 'a'(accept)命令 */ - net_bench_cmd_log('a', (int)p_sock->ch395_sock, 0); -#endif - /* 监听态:有连接到达 */ - if (p_sock->state == NET_SOCK_STATE_LISTENING) { - /* 单连接:收到 CONNECT 中断 = 自身有连接到达,转为 ESTABLISHED */ - DBG_INFO("sock%d standalone accept", p_sock->ch395_sock); - /* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */ - uint8_t remote[6]; - /* 回读对端 IP + 端口 */ - ch395f_get_remot_ipp(p_sock->ch395_sock, remote); - /* 对端 IP 数组(前 4 字节,大端) */ - memcpy(p_sock->remote_ip_arr, remote, 4); - /* 小端 IP 组装为 32 位值 */ - p_sock->remote_ip = ((uint32_t)remote[0]) | - ((uint32_t)remote[1] << 8) | - ((uint32_t)remote[2] << 16) | - ((uint32_t)remote[3] << 24); - /* 对端端口(小端 2 字节) */ - p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); - /* 状态 → ESTABLISHED(监听自身转数据通道) */ - p_sock->state = NET_SOCK_STATE_ESTABLISHED; - /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置 - * ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内 - * recv 仍会误得 EOF(把新客户端踢开) */ - p_sock->session_dead = 0U; - /* F-NET-17 M3 前置:清可疑电平——探针(LISTENING 期)残留计数会带进新会话, - * 不补此清零则 M3 矛盾判据首轮即被旁路(T6 实测 grep:connect 原不清此字段) */ - p_sock->lost_conn_seen = 0U; - /* 新开 Socket 发送缓冲按空闲计 */ - p_sock->send_ready = 1; - - /* 清除残留的 TIMEOUT 中断标志 */ - /* 读即清:本 Socket 中断状态寄存器 */ - (void)ch395f_get_sock_int_status(p_sock->ch395_sock); - - /* §5.56:TCP 服务器须在 CONNECT 后开启 KeepAlive。2026-10-12 曾加后 - * 撤回(当时仅证明"物理断链"已被 +2s TIM_OUT 快速覆盖,其余场景 - * 无法注入、未证明);E11(2026-10-13,《网络自愈架构设计》)用"禁用 PC 网卡" - * 注入"链路在、对端静默"(NAT/黑洞)补上证据链:生产参数 60/5/3 - * 实测 ≈30 s 判死 → TIM_OUT → QUARANTINE → reconciler 重建;对端存活时 - * 探测被 ACK、空闲会话不误杀。消费链路现成,按手册位置转正。 */ - ch395f_set_keepalive_enable(p_sock->ch395_sock, 1); - - /* 触发连接建立事件回调 */ - fire_event(p_sock, NET_EVENT_CONNECTED); - } else if (p_sock->state == NET_SOCK_STATE_CONNECTING) { - /* TCP Client 连接成功 */ - /* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */ - uint8_t remote[6]; - /* 回读对端 IP + 端口 */ - ch395f_get_remot_ipp(p_sock->ch395_sock, remote); - /* 对端 IP 数组(前 4 字节) */ - memcpy(p_sock->remote_ip_arr, remote, 4); - /* 小端 IP 组装为 32 位值 */ - p_sock->remote_ip = ((uint32_t)remote[0]) | - ((uint32_t)remote[1] << 8) | - ((uint32_t)remote[2] << 16) | - ((uint32_t)remote[3] << 24); - /* 对端端口(小端 2 字节) */ - p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); - /* 状态 → ESTABLISHED */ - p_sock->state = NET_SOCK_STATE_ESTABLISHED; - /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING - * 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */ - p_sock->session_dead = 0U; - /* F-NET-17 M3 前置:清可疑电平(同 LISTENING 分支) */ - p_sock->lost_conn_seen = 0U; - - /* 使能 KeepAlive(客户端连接保活) */ - ch395f_set_keepalive_enable(p_sock->ch395_sock, 1); - - /* 触发连接建立事件回调 */ - fire_event(p_sock, NET_EVENT_CONNECTED); - } -} - -/* - * 函数功能:处理 Socket 断开(DISCONNECT)事件,更新 Socket 状态机 - * 入口参数:p_sock - 发生断开事件的 Socket 控制块指针 net_sock_t * - * 出口参数:无 - * 返回值:无 - * 限定条件:net_poll() 检测到 DISCONNECT 中断后调用;netTask 上下文 - * 函数说明:1. ⚠ FUN_PARA bit3=1(本仓 net_init 置 0x08):DISCONNECT/TIM_OUT 后芯片 - * **不**关 Socket、**不清**缓冲(停 0x05/0x00)——残留数据须由本层读出后,再由 - * 本层 ch395f_close_socket 关闭;芯片不代劳(§5.52② / §5.23),不得跳过 - * 显式 close(U12 成因之一)。之后需重新 OPEN + LISTEN - * 2. 单连接:置 CLOSED,交由 net_recv_locked 排空剩余数据后再关 - * 3. 触发 NET_EVENT_DISCONNECTED 回调 - */ -static void handle_disconnect_event(net_sock_t *p_sock) { -#if defined(NET_BENCH_CMD_LOG) - /* Bench:记 'd'(disconnect)命令 */ - net_bench_cmd_log('d', (int)p_sock->ch395_sock, 0); -#endif - /* 由控制块指针反推描述符 */ - int sockfd = (int)(p_sock - s_net_socks); - - /* 监听态:DISCONNECT 落在监听器上 */ - if (p_sock->state == NET_SOCK_STATE_LISTENING) { - /* TRAP-NET-17 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡; - * 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现); - * N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交 - * net_reconcile_tick 形态③(每 100 ms 读驱动缓存,监听失位即重建)。原处直接 - * relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先" - * 于消费者 recv(P1 已消除其后果,P2 消除其可能)。 - * ⚠ 本留痕回读保持**主动 SPI**(不用驱动缓存):事件路径上缓存必然滞后 - * 于刚到达的中断(刷新在 net_poll 末尾、事件处理之后),仅周期对账可用缓存 */ - /* 回读缓冲(芯片状态 2 字节) */ - uint8_t st[2] = {0, 0}; - - /* 回读芯片状态(仅为留痕:重建与否由 reconciler 裁决) */ - 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), reconciler will relisten", - sockfd, st[0], st[1]); - } 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 被"关闭 → 重新监听 → 再连接"循环复用,上一条连接的 FIN/ACK - * 可能在 fd 已复用为新连接**之后**才上报(net_close_locked 排空的中断里还没有它)→ 新连接被误判为断开(实测:FTP 同一会话第二个数据连接必失败 425); - * 处置:回读芯片状态,TCP 仍 ESTABLISHED 判陈旧丢弃(已读清不重复上报);真断开时芯片处于 CLOSE_WAIT(0x07)/CLOSED(0x00) 等状态照常处理;超时事件走 handle_timeout_event,不受影响 */ - /* 芯片状态回读缓冲(2 字节) */ - uint8_t st[2]; - - /* 回读芯片状态(Socket + TCP) */ - ch395f_get_socket_status((uint8_t)sockfd, st); - /* 芯片仍 ESTABLISHED:陈旧事件 */ - if (st[1] == CH395F_TCP_ESTABLISHED) { - /* 留痕:陈旧事件丢弃 */ - DBG_ERROR("sock%d stale DISCONNECT ignored (chip TCP still ESTABLISHED)", sockfd); - /* 直接返回(中断已读清,不会重复上报) */ - return; - } - /* F-NET-17 M3(自愈专项实验 2026-10-13):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05) - * 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT - * (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨 - * (结构性失明,F-NET-15 Q1)。处置:不吃边沿,置可疑电平返回,交对账① - * (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(自愈收敛阶段 3 合并 - * 后=4×100ms,NET_RECON_STABLE_DEATH,让位租约 400ms 不变 + 冷却): - * flag==1 旁路本门 → 正常释放;连接真活则租约内 - * 会话正常完成(风暴链掐断,E9a n30 0/50 实证)。 - * 零额外 SPI 于常规路径(st[] 已读;recv_len 仅可疑分支多读一条)。 - * ⚠ E9a/E9b 实测两轮收窄:①必须含 recv_bytes==0——已读数据的会话遇 - * 0x05/0x00 多为真死(探针控制通道教训);②必须含芯片 RX>0——延迟保护的 - * 本质是"在途投递不可毁"(closed-with-data 救回同源红线:RX 有数据=消费者 - * 资产)。@0ms 快复连(连上即关、零数据,E9b :21 46/50 教训)RX 空 → 立即 - * 释放,误杀也无数据损失,后到数据由救回路径接住。边界:滞后锁存杀 - * "已读数据或 RX 空"会话的形态不在本门保护域(E8 窗口未出现该受害形态)。 - * 零误伤:健康 FIN=0x07、静默回收=0x00/0x00 均不命中(E5 20/20 样本) */ - if ((st[0] == CH395F_SOCKET_OPEN) && (st[1] == CH395F_TCP_CLOSED) && - (p_sock->lost_conn_seen == 0U) && (p_sock->recv_bytes == 0U) && - (ch395f_get_recv_len((uint8_t)sockfd) > 0U)) { - /* 置可疑电平(复用探针字段,零 RAM;connect/relisten 清零) */ - p_sock->lost_conn_seen = 1U; - /* 留痕(INFO 级:这是预期自愈路径,非错误,勿污染 [ERR] 台架判据) */ - DBG_INFO("sock%d suspect DISCONNECT on live conn (chip 0x%02X/0x%02X), defer to reconcile", - sockfd, st[0], st[1]); - /* 不释放、不登记沉降、不动 session_dead——保留 400ms 租约窗口 */ - return; - } - /* 单连接:先不关 CH395F Socket, - * 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */ - DBG_INFO("sock%d DISCONNECTED, releasing (chip st=0x%02X/0x%02X)", - p_sock->ch395_sock, st[0], st[1]); - /* TRAP-NET-18 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态 - * 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226; - * 仅**本连接收过数据**时登记沉降(实测触发源 = 收过数据的数据通道被关闭),纯连接即关/只发不收的连接免沉降,保住 TRAP-NET-17 的快速复连时序(:21 @0ms ×50 全通过) */ - /* F-NET-15 M1(自愈专项实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的 - * 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是 - * 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测); - * 门原保护目标(:21 @0ms 快复连)已被 N-32 单一重建入口延迟化(恢复窗实测 0.3~1s)架空, - * +100ms 沉降落在窗内不可见。沉降窗口同时让残渣 DISCONNECT 在 state==CLOSED - * (无害、仅留痕)时被消费,断开"杀下一条 → 新拆链 → 再杀"的繁殖链 */ - { - /* 反推描述符(沉降按 fd 登记) */ - int qfd = (int)(p_sock - s_net_socks); - /* 描述符范围保护 */ - if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) { - /* 登记优雅沉降(NET_RELISTEN_SETTLE_GRACEFUL) */ - net_listener_settle(qfd, NET_LISTENER_SETTLE_GRACEFUL); - } - } - /* 正常释放(对端 FIN):断连时挂起项以 EOF(0) 结束 */ - p_sock->released_abnormal = 0U; - /* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**, - * 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间 - * recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据 - * 落到本标志 → 仍得 EOF,不因排空慢而漏报 */ - p_sock->session_dead = 1U; - /* N-30(TRAP-NET-20):登记本连接对端端口(幽灵态甄别基准,须在状态 - * 迁移前记录——remote_port 此刻仍有效,relisten 清零) */ - p_sock->last_peer_port = p_sock->remote_port; - /* 状态 → CLOSED */ - p_sock->state = NET_SOCK_STATE_CLOSED; - /* 触发断连事件回调 */ - /* 触发断连事件回调 */ - fire_event(p_sock, NET_EVENT_DISCONNECTED); - /* 其他状态:未预期的断开 */ - } else if (p_sock->state != NET_SOCK_STATE_CLOSED && - p_sock->state != NET_SOCK_STATE_BOUND && - p_sock->state != NET_SOCK_STATE_CREATED) { - /* 未预期的断开:记错误日志 */ - DBG_ERROR("sock%d disconnect in state %d", p_sock->ch395_sock, p_sock->state); - /* 触发断连事件回调(统一) */ - fire_event(p_sock, NET_EVENT_DISCONNECTED); - } -} - -/* - * 函数功能:处理 Socket 数据接收(RECV_OK)事件 - * 入口参数:p_sock - 发生接收事件的 Socket 控制块指针 net_sock_t * - * 出口参数:无 - * 返回值:无 - * 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文 - * 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层 - * 2. 不在此读取 recv_len:单一读取点归 net_recv_locked(省一次 SPI 查询) - */ -static void handle_recv_event(net_sock_t *p_sock) { - /* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len, - * 单一读取点在 net_recv_locked(省 SPI;原"部分读归零"顾虑已降级) */ - /* 置可读标志,供 net_select 在无 SPI 访问下判读 */ - p_sock->readable = 1; - /* 触发数据到达事件回调 */ - fire_event(p_sock, NET_EVENT_DATA_RECEIVED); -} - -/* - * 函数功能:处理 Socket 发送完成(SEND_OK)事件 - * 入口参数:p_sock - 发生发送完成事件的 Socket 控制块指针 net_sock_t * - * 出口参数:无 - * 返回值:无 - * 限定条件:net_poll() 检测到 SEND_OK 中断后调用;netTask 上下文 - * 函数说明:触发 NET_EVENT_SEND_COMPLETE 回调通知上层 - */ -static void handle_send_event(net_sock_t *p_sock) { - /* 触发发送完成事件回调 */ - fire_event(p_sock, NET_EVENT_SEND_COMPLETE); -} - -/* - * 函数功能:处理 Socket 超时(SOCK_TIMEOUT)事件,更新 Socket 状态机 - * 入口参数:p_sock - 发生超时事件的 Socket 控制块指针 net_sock_t * - * 出口参数:无 - * 返回值:无 - * 限定条件:net_poll() 检测到 SOCK_TIMEOUT 中断后调用;netTask 上下文 - * 函数说明:1. TCP Client(CONNECTING):连接超时,回到 CREATED 状态 - * 2. 已建立连接:回到 CLOSED(单连接) - * 3. 监听态:异常超时,记录错误日志 - * 4. 触发 NET_EVENT_TIMEOUT 回调 - */ -static void handle_timeout_event(net_sock_t *p_sock) { -#if defined(NET_BENCH_CMD_LOG) - /* Bench:记 'x'(timeout)命令 */ - net_bench_cmd_log('x', (int)p_sock->ch395_sock, 0); -#endif - /* 连接中状态:连接超时 */ - if (p_sock->state == NET_SOCK_STATE_CONNECTING) { - /* TCP Client 连接超时 */ - DBG_ERROR("sock%d CONNECT TIMEOUT", p_sock->ch395_sock); - /* 状态 → CREATED(客户端槽位回可复用) */ - p_sock->state = NET_SOCK_STATE_CREATED; - } else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) { - /* 已建立连接超时 */ - DBG_ERROR("sock%d TIMEOUT", p_sock->ch395_sock); - /* TRAP-NET-18:异常释放(TIMEOUT,例如客户端中止上传)→ 复监听前隔离, - * 等对端在途数据落尽并排空 RX 残渣(详见 net_relisten_locked 区块注释) */ - { - /* 反推描述符 */ - int qfd = (int)(p_sock - s_net_socks); - /* 描述符范围保护 */ - if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) { - /* 登记异常释放隔离(重监听前等待在途数据落尽 + RX 排空) */ - net_listener_settle(qfd, NET_LISTENER_SETTLE_QUARANTINE); - } - } - /* TRAP-NET-18 待办 #3:标记异常释放——挂起 recv 排空断连后以 - * NET_ERR_CONNRESET 结束(而非 EOF),应用(lftpd STOR)回报 5xx - * 而非误报 226(见 net_pending_tick 断连分支) */ - p_sock->released_abnormal = 1U; - /* N-30(TRAP-NET-20):异常释放同样登记对端端口(幽灵甄别基准) */ - p_sock->last_peer_port = p_sock->remote_port; - /* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST - * 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与 - * released_abnormal(结束得好不好看)正交) */ - p_sock->session_dead = 1U; - /* 状态 → CLOSED */ - p_sock->state = NET_SOCK_STATE_CLOSED; - } else if (p_sock->state == NET_SOCK_STATE_LISTENING) { - /* 监听超时(异常情况) */ - DBG_ERROR("sock%d LISTEN TIMEOUT", p_sock->ch395_sock); - } - - /* 触发超时事件回调(统一) */ - fire_event(p_sock, NET_EVENT_TIMEOUT); -} - -/* - * 函数功能:触发 Socket 事件回调(若已注册) - * 入口参数:p_sock - 触发事件的 Socket 控制块指针 net_sock_t * - * event - 事件类型 net_event_t - * 出口参数:无 - * 返回值:无 - * 限定条件:net_init() 已调用 - * 函数说明:1. 仅当 p_sock->event_cb 非空时调用回调 - * 2. 回调参数为 ch395_sock、event、event_arg - */ -static void fire_event(net_sock_t *p_sock, net_event_t event) { - /* 已注册回调 */ - if (p_sock->event_cb != NULL) { - /* 调用回调(ch395_sock、event、用户参数) */ - p_sock->event_cb(p_sock->ch395_sock, event, p_sock->event_arg); - } -} /* * 函数功能:将点分十进制 IP 地址字符串解析为 4 字节数组 @@ -3620,6 +3006,19 @@ net_sock_t *net_get_sock(int sockfd) { return &s_net_socks[sockfd]; } +/* + * 函数功能:直取 Socket 控制块数组元素,**不判 in_use**(仅供 net 层内部周期对账使用) + * 入口参数:sockfd - Socket 描述符 (0~7),调用方保证在范围内 + * 返回值:控制块指针(永不为 NULL) + * 限定条件:netTask 上下文 + * 函数说明:net_get_sock() 对未在用槽位返回 NULL,而周期对账必须看到 in_use==0 / + * state==CLOSED 的槽位(形态④ 认领孤儿监听正依赖 CLOSED 态)→ 单独提供。 + * 声明在内部头 net_events.h(不进公开 net_socket.h);应用层用 net_get_sock()。 + */ +net_sock_t *net_sock_raw(int sockfd) { + return &s_net_socks[sockfd]; +} + /* * 函数功能:获取 Socket 本地地址(IP + Port) * 入口参数:sockfd - socket 描述符 int diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h index ed36bbf1..ec76fba0 100644 --- a/Drivers/BSP/NET/net_socket.h +++ b/Drivers/BSP/NET/net_socket.h @@ -485,6 +485,18 @@ void net_process_messages(void); */ net_sock_t *net_get_sock(int sockfd); +/* + * 函数功能:直取 Socket 控制块数组元素,**不判 in_use**(net 层内部周期对账专用) + * 入口参数:sockfd - Socket 描述符 (0~7),调用方保证在范围内 + * 返回值:控制块指针(永不为 NULL) + * 限定条件:netTask 上下文 + * 函数说明:`net_get_sock()` 对未在用槽位返回 NULL,而周期对账必须看到 in_use==0 / + * state==CLOSED 的槽位(对账形态④ 认领孤儿监听正依赖 CLOSED 态)→ 单独提供。 + * ⚠ 应用层请一律用 `net_get_sock()`;本函数仅供 net_events.c 的对账循环使用。 + * 模拟器不实现(无调用者,链接期不需要)。 + */ +net_sock_t *net_sock_raw(int sockfd); + /* * 函数功能:记录**本任务**最后一次失败的精确错误码(net 层内部通用工具) * 入口参数:err - 错误码(NET_ERR_xxx,负值) diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 4d84efbf..f54f8627 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -1603,6 +1603,11 @@ 1 ..\Drivers\BSP\NET\net_listener.c + + net_events.c + 1 + ..\Drivers\BSP\NET\net_events.c + net_pending.c 1 diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index e46b391c..d78e6489 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 231,056 B (225.64 KB) | 1,048,576 B (1024 KB) | 22.0% | +| ER_IROM1 | 231,036 B (225.62 KB) | 1,048,576 B (1024 KB) | 22.0% | | RW_IRAM1 | 98,144 B (95.84 KB) | 131,072 B (128 KB) | 74.9% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **232,168 B (226.7 KB)** | 1,048,576 B (1024 KB) | **22.1%** | +| **Flash (ROM 合计)** | **232,148 B (226.7 KB)** | 1,048,576 B (1024 KB) | **22.1%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index 71ec7bca..ec19eddc 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -15,7 +15,7 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 231,056 B | 1,048,576 B | 22.0% | 参考 | +| ER_IROM1 (Flash) | 231,036 B | 1,048,576 B | 22.0% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | | RW_IRAM1 | 98,144 B | 131,072 B | 74.9% | PASS | diff --git a/scripts/check_registration.py b/scripts/check_registration.py index 651d1ca0..ea39e886 100644 --- a/scripts/check_registration.py +++ b/scripts/check_registration.py @@ -117,6 +117,7 @@ SIM_EXEMPT = [ ("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/NET/net_events.c", "MCU 芯片事件处理 + 100ms 周期对账(依赖 ch395f_* SPI 驱动与驱动状态缓存),模拟器无对应(Winsock 后端无\"事件未收到/静默回收\"概念,无失同步可收敛)"), ("Drivers/BSP/NET/net_pending.c", "MCU 挂起 recv 模型(依赖 ch395f_* SPI 读出与 FreeRTOS 通知),模拟器另有 net_socket_sim.c 按外部语义模拟"), ("Drivers/BSP/RS485/rs485.c", "MCU RS-485 驱动"), ("Drivers/BSP/SD2506/sd2506.c", "MCU RTC 驱动"), diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv index 936b2d3f..96fc2578 100644 --- a/scripts/ram_baseline.tsv +++ b/scripts/ram_baseline.tsv @@ -1,4 +1,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi 2026-10-12 209492 18332 1104 154184 227824 21.7 97864 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;console_log.o=4120;ch395f.o=4106;default_task.o=2736;sys_log_mcu.o=1924 2026-10-13 211456 18760 1104 154232 230216 22.0 97912 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924 -2026-10-14 212316 18740 1112 154456 231056 22.0 98144 74.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924 +2026-10-14 212296 18740 1112 154456 231036 22.0 98144 74.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924