Files
STM32F4-Base/Drivers/BSP/NET/net_events.c
T
wandering1 0054ae629a refactor(编号): F-* 网络设计条目号转语义说明——批次一收尾,代码注释 43 处 + 文档 9 处
批次一(不碰门禁即可清)的最后半批,原派道中途失败且**未留任何改动**(Lead 核实那批文件零 diff 后重派
setpage 执行,14 只文件 / 46 处),测试侧与一张生成器共 9 处由 Lead 收尾。

做法:**逐处先读属主文档 §7 速查表里那条的语义**,再改写成「一句话结论 + §指针」,同号跨文件同一措辞,
无一处机械删号(例:`F-NET-7` → 「唯一读者红线(docs/网络/网络自愈架构设计.md §7 I7)」)。属主登记处
**12 条定义行原样保留**、不删登记族(删了历史行会报未登记前缀),只在该章加停发句。

顺带清掉 **3 处裸形**(`T6` → 「台架实测」、`M4-lite`、`Q1` 语义化)——裸形正是 id-check 校验 2 抓的形态,
属净减。测试侧收尾时两条纪律:主机侧**断言字符串必须保持 ASCII**(`ns_s3_msg.c` 等三处的号在 CHECK 文本
里,改成 `sole reader of the global register` 而不是中文);板端注释可用中文。

生成器 gen_net_instability_svg.py 文案改动 → 同批重跑产物 `docs/images/网络不稳根因总览.svg`:只有那一
行的**框宽随文字自适应**(526 → 551,右缘 1411 未越界),画布 1560x1468 不变,无溢出告警。

⚠ 本笔**只含代码注释面**:该半批的文档面(BSD 函数调用关系 9 处、属主文档 §7 停发句、
生成器与产物那张网络不稳图)已被上一笔 `git add docs tools` 一并收走,两笔合起来才是完整的 F-* 批次。
自证:@build **0 错 0 警**且 Program Size 与改前**逐字节同数**(Code=233900 RO=26104 RW=1
536 ZI=136808,
注释无误伤的机器证据);net_socket 457/457、iec104_serv / net_pending 两套 exit 0;cppchec
k 钩子参数零
发现、coding-standard 0(lftpd_inet.c 那 4 条是存量且该目录本在钩子 exclude 内,用 HEAD 副本对照
甄别过)。
2026-10-27 09:05:27 +08:00

669 lines
39 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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 <string.h>
#include <stdbool.h>
#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: <tag> fd=<sock> t=<FreeRTOS tick> d=<距上条命令 tick 间隔>
* x=<detail>;台架专用,实验后回退
*/
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
/* 对账与监听重建合一周期(原 200ms 对账 / 100ms 重建合一) */
#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;
/* 监听生命周期 P1:新会话开始 —— 清"上一条会话已结束"电平。必须在置
* ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内
* recv 仍会误得 EOF(把新客户端踢开) */
p_sock->session_dead = 0U;
/* M3 可疑门前置(风暴态三机制,docs/网络/网络自愈架构设计.md §4.1.6):
* 清可疑电平——探针(LISTENING 期)残留计数会带进新会话,不补此清零则矛盾判据
* 首轮即被旁路(台架实测 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;
/* 监听生命周期 P1:新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING
* 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */
p_sock->session_dead = 0U;
/* M3 可疑门前置:清可疑电平(同 LISTENING 分支,§4.1.6 风暴态三机制) */
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:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡;
* 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现);
* 监听生命周期 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;
}
/* M3 可疑门(自愈专项实验 2026-10-13;风暴态三机制收口,
* docs/网络/网络自愈架构设计.md §4.1.6):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05)
* 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT
* (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨
* (结构性失明,同 GRACEFUL 沉降门放大器根因,§4.3)。处置:不吃边沿,置可疑电平返回,交对账①
* (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(对账与重建合一
* 后=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 全通过) */
/* M1 沉降门退役(自愈专项实验 2026-10-12;GRACEFUL 无条件化,
* docs/网络/网络自愈架构设计.md §4.3):沉降**无条件**登记。原 recv_bytes!=0 门的
* 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是
* 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测);
* 门原保护目标(:21 @0ms 快复连)已被监听生命周期单一重建入口延迟化(恢复窗实测 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;
/* 监听生命周期 P1 不变量 I:置"本会话已结束"电平。须在置 CLOSED **之前**,
* 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间
* recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据
* 落到本标志 → 仍得 EOF,不因排空慢而漏报 */
p_sock->session_dead = 1U;
/* 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;
/* TRAP-NET-20:异常释放同样登记对端端口(幽灵甄别基准) */
p_sock->last_peer_port = p_sock->remote_port;
/* 监听生命周期 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. 去抖自偿退役(镜像降级为会话投影的直接红利,
* docs/网络/网络自愈架构设计.md §3.3):判据改读**驱动状态缓存**(零 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;
}
/* 探针去抖已删(去抖自偿退役,判据读缓存、两类瞬态结构性消除,
* 见函数头注释 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 复核:非芯片丢事件是被读清消费)+ 监听器重建 —— 合并为单一 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)
* ⑤(静默回收认领,docs/网络/网络自愈架构设计.md §4.1)本层 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 轮(**勿改小**:M3 可疑门
* 让位再入(§4.1.6 让位红线),400ms 让位窗=在途投递不可毁的最后租约,E9a 实证)
* 5. 动手后冷却 NET_RECONCILE_COOLDOWN_MS 防风暴(U11 被动重同步不占)
* 6. 链路态订阅(链路态喂料缓存,docs/网络/网络自愈架构设计.md §4.4):
* 驱动 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;
/* 链路态订阅(§4.4 链路态喂料缓存):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)) {
/* ⑤(静默回收认领,docs/网络/网络自愈架构设计.md §4.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
* 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;
/* 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 形态③④统一做(监听生命周期 P2 单一所有者) */
handle_disconnect_event(p_sock);
} else if (bad == 5) {
/* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体
* 原样并入;纪律同 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;
/* 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);
}
}
}