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