refactor(net)!: N34 阶段1 API 面收敛(对账⑤ + 删 3 个手动自愈 API)

BREAKING CHANGE: 删除公开 API net_relisten / net_resync_listen /
net_can_accept 与消息类型 NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN。
生产调用者为零(仅看门狗文档与 iec104 门控),应用侧无迁移成本;
如有外部消费者须改用对账/pump 自动机制(见下)。

1. 对账①扩展为①②⑤(F-NET-2 缺口,先补缺口再删 API)
   net_reconcile_tick 新增 bad=5:本层 ESTABLISHED 且芯片 0x05/0x01
   (Socket OPEN + TCP LISTEN,静默回收回监听、不发 DISCONNECT)
   → RESYNC 语义只修本层(state→LISTENING + last_peer_port 转登 +
   lost_conn_seen 清零 + 取消挂起 recv),**不发任何芯片命令**
   (纪律同 pump③ U11,run8 实证重下发会 170s 永拒)。
   原判据①要求 TCP==CLOSED、②要求本层 LISTENING,均不命中此形态
   ——此前生产无人认领(仅手动 API 能修)。
2. 删 net_relisten(net_socket.c 函数 + 消息分派 + 头文件声明)
   footgun:对健康监听器无条件 open→USE→close 自毁 ~1s(原注释自认);
   与 pump(100ms)/D10(30ms) 完全重叠。
3. 删 net_resync_listen(同上;语义并入第 1 条)
4. 删 net_can_accept(net_socket.c + net_socket.h + sim 三处)
   只认 ESTABLISHED 的旧门控把"监听器在服役、待 CONNECT"窗口全挡掉,
   :2404 丢 CONNECT 探针因此从不运行;iec104 门控迁 net_listener_armed。
5. timeSync :8888 会话收尾不再 net_close(同 fd 续 accept,监听跨会话
   常驻;原 close→重建每会话一个 Trap 09 沉降窗)。server 脚本注释同步
   新契约(PC 侧 2s 宽限后自行关闭)。
6. lftpd 应用层阻塞重试退役:lftpd_inet_listen 5 轮(500ms/2000ms,最坏
   10s 阻塞 ftpTask) → 单次 + 失败即 425;cmd_pasv 3×200ms 同删。
   就绪屏障已消除 Trap 09 启动形态,客户端自带传输重试语义。

台架实证修正(本提交内二次修复,务必留痕):
- net_listener_armed 补齐 **ESTABLISHED 放行**。首版迁移只放行
  LISTENING/CLOSED+残留,漏了 ESTABLISHED=单连接模型的"待 accept"电平
  → iec104 200ms 任务周期抢不过对账② 的 ~400ms 去抖,对账②先 standalone
  accept 转 ESTABLISHED 后 armed=0 → 应用整场不 accept,18/18 检查全 FAIL
  (设备日志证据:sock1 standalone accept 后无 i104 accepted,直至对端 FIN
  才由 closed-with-data 救回)。补放行后 18/18 PASS,且耗时 127s→8.3s。
  该态 accept 必返 fd,无 RPC 风暴风险。
- 期间顺带验证 M3 无辜:sock1 suspect DISCONNECT on live conn 命中后
  defer→对账①→closed-with-data accept 把未 accept 的 RX 数据救回,
  正是 M3 设计意图(资产门保护)。

验证(全绿):@build 0/0(Code=209580;含 CH395F 套件+Phase13 全量重编
0/0 的编译验证);@test PASS(回读无差异);iec104 18/18(8.3s);
n30 0/50;:21 @0ms 50/50 p50=512;:7003 50/50 p50=517;ftp_regression
11/11;Trap23 RST 5/5 + FIN 5/5;time_sync :8888 连续推送正常(RTC 更新、
无 [ERR])。文档同步:BSD 指南(§3.3/3.6/10.1/10.2/12 + 变更记录)、
函数调用关系、监听生命周期设计说明、IEC104 上行设计说明、模拟器差距
清单、链路延迟测试说明。

门禁说明:doc-links 工作树 junction 伪影按既定口径 SKIP;mem-budget
分支 .map 漂移同前;cppcheck/coding-standard 手跑 Passed。
This commit is contained in:
2026-10-13 06:44:26 +08:00
parent 4c0a0af9f8
commit b8c6a3dab0
16 changed files with 196 additions and 374 deletions
+54 -170
View File
@@ -542,9 +542,11 @@ int net_poll(void) {
* 函数说明:1. 芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件自愈
* 2. 典型:对端连接占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒
* 3. 应用因未收到 DISCONNECT 仍以为有客户端,永不调用 accept,事件驱动自愈无路可走
* 4. 故周期回读芯片状态,修正**两类**失同步:
* 4. 故周期回读芯片状态,修正**三类**失同步:
* 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实;重建交 pump)
* 6. ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event)
* 6b.⑤(N34 阶段1,F-NET-2)本层 ESTABLISHED 而芯片已静默回收回 LISTEN
* (0x05/0x01 无 DISCONNECT)→ RESYNC 只修本层,纪律同 pump③ U11
* 7. ⚠ N-32 P2(2026-10-12):原③(本层 LISTENING 丢监听)与④(孤儿监听)
* 已**整体移交** `net_listener_pump` —— 本函数内不再有任何重建动作
* (单一所有者);认领门槛相应收窄为 ESTABLISHED/LISTENING
@@ -581,7 +583,8 @@ static void net_reconcile_tick(void) {
net_sock_t *p_sock = &s_net_socks[i];
/* 芯片侧状态两字节:[0]=socket 状态,[1]=TCP 状态 */
uint8_t st[2] = {0, 0};
/* 失同步类型:1=连接已失 2=握手已成丢中断 3=监听已失 */
/* 失同步类型:1=连接已失 2=握手已成丢中断 5=芯片静默回收回监听
*(原 3/4=监听失位已移交 pump,编号不复用以避与历史口径混淆) */
int bad = 0;
/* 对账认领范围:**仅 ESTABLISHED / LISTENING**。
@@ -614,6 +617,15 @@ static void net_reconcile_tick(void) {
if (!((peer != 0U) && (peer == p_sock->last_peer_port))) {
bad = 2;
}
} else if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) &&
(st[0] != CH395F_SOCKET_CLOSED) &&
(st[1] == CH395F_TCP_LISTEN)) {
/* ⑤(F-NET-2 缺口,N34 阶段1):芯片静默回收——本层 ESTABLISHED
* 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT
* 中断**,run8 台架实证)。判据①要求 TCP==CLOSED 不命中此形态、
* 判据②要求本层 LISTENING 也不命中——此前生产无人认领(原仅手动
* API net_resync_listen 能修且生产零调用)。→ bad=5 RESYNC 语义 */
bad = 5;
}
/* ③④(本层 LISTENING 丢监听 / 本层 CLOSED 孤儿监听)已移交
* net_listener_pump —— N-32 P2 单一所有者,本函数不再做重建动作 */
@@ -643,6 +655,20 @@ static void net_reconcile_tick(void) {
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发);
* 只置事实,重建由 net_listener_pump 统一做(N-32 P2) */
handle_disconnect_event(p_sock);
} else if (bad == 5) {
/* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体
* 原样并入,N34 阶段1;纪律同 pump③ U11:**不发任何芯片命令**——
* 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒,
* run8 实证 170s 拒绝) */
DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync",
i, st[0], st[1]);
/* 仅修 MCU 侧;新连接的 CONNECT 中断会把两侧重新同步 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(Trap 25):死连接对端端口转登幽灵甄别基准 + 去抖清零 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
/* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */
net_pending_cancel(i, NET_ERR_CONNRESET);
} else {
/* 类型②:补做 standalone accept */
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
@@ -783,47 +809,10 @@ void net_process_messages(void) {
msg.result = net_accept_locked(msg.sockfd, p_addr, p_addrlen);
break;
}
case NET_MSG_RESYNC_LISTEN: {
/* 取控制块;NULL = fd 越界或已关闭 */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF,结果 -1 */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
/* 非 ESTABLISHED(LISTENING/CLOSED 等):两侧已一致,无需动作 */
msg.result = 0;
} else {
/* 陈旧 ESTABLISHED:芯片已静默回收连接(无 DISCONNECT 中断,
* 2026-09-22 台架实证)→ 仅修 MCU 侧状态;严禁发芯片
* close/重监听命令(会摧毁芯片侧仍健康的监听器 → 端口永拒,
* run8 实证 170s 拒绝);新连接的 CONNECT 中断会把两侧重新同步 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(Trap 25):死连接对端端口转登幽灵甄别基准 + 去抖清零 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
/* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */
net_pending_cancel(msg.sockfd, NET_ERR_CONNRESET);
/* 仅修正 MCU 侧状态,不发芯片命令,直接返回成功 */
msg.result = 0;
}
break;
}
case NET_MSG_RELISTEN: {
/* 描述符有效性检查(NULL = fd 越界或已关闭)→ 无效即 BADF;
* 有效则交 net_listener_try(它内部自行取控制块,本处不再持有指针,
* N-32 §10 入口收敛) */
if (net_get_sock(msg.sockfd) == NULL) {
/* 描述符无效 → BADF,结果 -1 */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else {
/* 无条件芯片侧重监听:单次尝试,失败登记非阻塞重试
* (芯片已在监听 → 下一轮到点再来;两种陈旧场景都收敛到"芯片监听器在位") */
msg.result = net_listener_try(msg.sockfd);
}
break;
}
/* N34 阶段1(F-NET-3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 两个
* 手动自愈消息已删——RESYNC 语义并入对账⑤(net_reconcile_tick),
* RELISTEN 与 pump/D10 完全重叠且对健康监听器是 footgun;
* 生产调用者本就为零(仅 test/board Phase 13,已改由自动机制接管) */
default:
/* 未知消息类型(枚举闭合,正常不应触发):置通用错误码与结果 -1,
* 保证通知值恒为本条消息的确定结果(无上一条残留) */
@@ -1429,26 +1418,10 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
return msg.result;
}
/*
* 函数功能:查询监听 Socket 是否有可接受的连接(纯 RAM 读,不发起 RPC)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:无
* 返回值:1 - 有连接可接受;0 - 无(含描述符无效) int
* 限定条件:任意任务上下文;net_init() 已调用
* 函数说明:1. 判据与 net_accept_locked() 一致(单连接模型:监听 Socket 自身在
* CONNECT 后转 ESTABLISHED),故"门控放行"与"accept 会成功"严格等价
* 2. 只读控制块,不入队、不等通知 → 调用方(uiTask 等)不被 netTask 拖住
* 3. 丢 CONNECT 中断由 net_reconcile_tick() 对账自愈,本函数不做自愈
*/
int net_can_accept(int sockfd) {
/* Socket 控制块(纯 RAM 读,可能为 NULL = 描述符越界或未分配) */
const net_sock_t *p_sock = net_get_sock(sockfd);
if (p_sock == NULL) {
return 0;
}
return (p_sock->state == NET_SOCK_STATE_ESTABLISHED) ? 1 : 0;
}
/* N34 阶段1(F-NET-4):net_can_accept 已删除——它只认 ESTABLISHED,作为 accept
* 门控会把"监听器在服役但本轮还没 CONNECT"的窗口全挡掉(:2404 丢 CONNECT 探针
* 因此从不运行,与已修的 :7003 探针失效同型);唯一生产调用者 iec104 已迁
* net_listener_armed。net_can_read(RECV 门控)不受影响、保留。 */
/*
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(纯 RAM 读,不发起 RPC)
@@ -1457,8 +1430,9 @@ int net_can_accept(int sockfd) {
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);
* 0 - 未就绪(fd 未分配 / 非监听型 / 正处隔离期以外的不就绪态) int
* 限定条件:任意任务上下文;net_init() 已调用
* 函数说明:1. N-32(P1 不变量 II):取代 net_can_accept()(要求 ESTABLISHED)作为
* ui_net_sink 的 accept 门控。原门控下 :7003 永不进 net_accept →
* 函数说明:1. N-32(P1 不变量 II):取代已退役的 net_can_accept()(要求
* ESTABLISHED,N34 阶段1 删除)作为监听 accept 门控。原门控下
* :7003 永不进 net_accept →
* net_lost_connect_probe(丢 CONNECT 中断补 accept)对该端口从不运行
* (设计文档 §2.3 末"探针失效")
* 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338)
@@ -1469,7 +1443,7 @@ int net_can_accept(int sockfd) {
* 可能已被芯片接住(对账②)→ 放行让 net_accept_locked 的状态门
* 自行裁决(含 RX 有数据的 drain_pending 接受)
* 4. 只读控制块,不入队、不等通知 → uiTask 不被 netTask 拖住
* 5. 与 net_can_accept 语义差异是**有意的**:本函数表达"这个监听器
* 5. 与已退役 net_can_accept 的语义差异是**有意的**:本函数表达"这个监听器
* 还在服役",而非"这一轮一定有连接"
*/
int net_listener_armed(int sockfd) {
@@ -1479,6 +1453,15 @@ int net_listener_armed(int sockfd) {
if (p_sock == NULL) {
return 0;
}
/* 连接已到、应用尚未 accept:单连接模型下 CONNECT 处理(net_poll 分派 /
* 探针补 accept / 对账② standalone accept)把监听 fd 转 ESTABLISHED,
* 这就是"待 accept"电平——**必须放行**(原 net_can_accept 唯一放行态;
* N34 阶段1 i104 台架实证:200ms 任务周期抢不过对账②的 ~400ms 去抖,
* 对账②先转 ESTABLISHED → 不放行则应用永不 accept、会话整场饿死)。
* 无 RPC 风暴风险:此态下 accept 必返 fd,一次调用即离开本分支 */
if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
return 1;
}
/* 正常监听态:放行。net_lost_connect_probe(丢 CONNECT 补 accept)与常规
* accept 都发生在 net_accept_locked 内部,门控挡在外面就永远到不了 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
@@ -3032,110 +3015,11 @@ int net_sock_state(int sockfd) {
return (int)s_net_socks[sockfd].state;
}
/*
* 函数功能:断连重同步(仅 MCU 侧状态,见头文件注释的适用场景)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:0 - 已同步/原本即一致;-1 - 失败
* 限定条件:连接已死且芯片已静默回收后调用(应用层判定)
* 函数说明:1. 适用:芯片已静默回收连接(无 DISCONNECT 中断)而本层状态滞留 ESTABLISHED
* 2. 状态收回 LISTENING 并重建监听,后续 accept 恢复正常
*/
int net_resync_listen(int sockfd) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:断连重同步(MCU 侧收回 LISTENING 并重建监听) */
msg.type = NET_MSG_RESYNC_LISTEN;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 无超时(重同步立即完成) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("rsl", sockfd);
/* 失败:恢复 errno(对外统一 -1) */
if (msg.result < 0) {
return NET_ERR_RETURN_1(msg.result);
}
/* 重同步成功(0) */
return 0;
}
/*
* 函数功能:无条件重新监听(芯片侧 open+listen,含 Trap 09 失败重试)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:0 - 成功;-1 - 失败
* 限定条件:TCP 监听型 Socket;net_init() 已完成
* 函数说明:1. 用于"断连后监听器可能已失位"的自愈场景(2026-09-22 run9 实证)
* 2. 一次性 net_listen 落在 Trap 09 窗口失败且被静默忽略 → 监听器永久丢失
* 3. 后续连接全部拒绝
* 4. 与 auto_relisten 内部路径同一实现(net_relisten_locked:单次尝试 + 非阻塞重试,见 net_listener_retry_tick)
* 5. 芯片侧已在监听时:首次 open 失败 → close 重建 → 监听短暂中断 ~1s
*/
int net_relisten(int sockfd) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:无条件重新监听(含 Trap 09 非阻塞重试) */
msg.type = NET_MSG_RELISTEN;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 无超时(重监听立即尝试) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("rel", sockfd);
/* 失败:恢复 errno(对外统一 -1) */
if (msg.result < 0) {
return NET_ERR_RETURN_1(msg.result);
}
/* 重监听成功(0) */
return 0;
}
/* N34 阶段1(F-NET-3):手动自愈 API net_resync_listen / net_relisten 已删除——
* RESYNC 语义并入对账⑤(net_reconcile_tick 自动认领"芯片静默回收回 LISTEN"形态,
* 200ms×2 去抖 + 1s 冷却沿用);RELISTEN 与 pump/D10 完全重叠且对健康监听器是
* footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。生产调用者本就为零
* (仅 test/board Phase 13,已改由自动机制接管)。历史实现见本文件 git 历史。 */
/*