d770e36c7c
- 源码注释:删去自愈收敛阶段 1/1.5/3等 scaffolding 称谓,改为语义引用 (E-02 收口 / F-NET-2/3/4 / docs/网络/网络自愈架构设计.md §5.2 E13); 涉及 net_socket/.h、net_events、net_listener、net_pending、net_types、 net_config、ch395f、time_sync、iec104_serv、ui_net_sink、lftpd、 simulator/sim/net_socket_sim、board/host 测试与探针 - 文档重构:网络自愈架构设计.md 与 Net层阻塞模型架构设计.md 改为现状口径 (删历史节与阶段叙事),NET/README.md、docs/README.md 同步 - 工具与配图:gen_net_instability_svg.py 与 docs/images/网络不稳根因总览.svg 随源码口径一并更新 - 门禁:@build 0 错误 0 警告
664 lines
38 KiB
C
664 lines
38 KiB
C
/*
|
||
* 模块名称: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;
|
||
/* 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)电平去抖再入本函数(对账与重建合一
|
||
* 后=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. 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;
|
||
}
|
||
/* 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 复核:非芯片丢事件是被读清消费)+ 监听器重建 —— 合并为单一 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 缺口):芯片静默回收——本层 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 分派体
|
||
* 原样并入;纪律同 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);
|
||
}
|
||
}
|
||
}
|