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
+29 -11
View File
@@ -124,9 +124,11 @@ static int time_sync_open_listener(void) {
* 入口参数:conn_sock - 已建立的连接 Socket 描述符(单连接模式下即监听 Socket 自身) * 入口参数:conn_sock - 已建立的连接 Socket 描述符(单连接模式下即监听 Socket 自身)
* 返回值:无 * 返回值:无
* 函数说明:pending 读取 PC 发来的时间包("TIME"+uint32 大端 本地时间),解析后 * 函数说明:pending 读取 PC 发来的时间包("TIME"+uint32 大端 本地时间),解析后
* 写回 RTC,然后关闭连接。每次等待 = 一次挂起请求(netTask 零占用, * 写回 RTC。**不在此 close**(N34 阶段1-5:单连接模式下 conn_sock 即
* L3 pending 化),单次超时 = 剩余总预算(≤30s,语义与旧 30s 阻塞一致); * 监听 fd,close 连监听一起毁、重建窗 ~0.3-1s;net 层 auto_relisten
* 跨调用累积,容忍时间包被 TCP 分段送达 * 已把同 fd 收回监听,外层直接续 accept——lftpd 样板)。每次等待 =
* 一次挂起请求(netTask 零占用,L3 pending 化),单次超时 = 剩余总预算
* (≤30s,语义与旧 30s 阻塞一致);跨调用累积,容忍时间包被 TCP 分段送达
*/ */
static void time_sync_handle_conn(int conn_sock) { static void time_sync_handle_conn(int conn_sock) {
uint8_t buf[16]; uint8_t buf[16];
@@ -179,7 +181,10 @@ static void time_sync_handle_conn(int conn_sock) {
} }
} }
net_close(conn_sock); /* N34 阶段1-5:原 net_close(conn_sock) 已删——单连接模式 conn==监听 fd,
* close 毁监听(AGENTS 模块约定:EOF 时应用层不得 close);net 层
* auto_relisten 已把同 fd 收回 LISTENING,外层循环直接续 accept。
* 模拟器多连接后端 conn≠ls,由调用方(外层)负责关数据 fd。 */
} }
/* /*
@@ -187,8 +192,10 @@ static void time_sync_handle_conn(int conn_sock) {
* 入口参数:argument - 未使用 * 入口参数:argument - 未使用
* 返回值:无 * 返回值:无
* 限定条件:net_init() 已由 netTask 完成;netTask 正在处理消息队列 * 限定条件:net_init() 已由 netTask 完成;netTask 正在处理消息队列
* 函数说明:等待网络栈就绪后,循环:建监听 -> 等连接 -> 处理 -> 关闭 -> 重建监听。 * 函数说明:等待网络栈就绪后,循环:建监听 -> 等连接 -> 处理 -> **同 fd 续
* 全部 socket 操作经消息队列由 netTask 串行执行,与本任务/其它网络任务并发安全。 * accept**(N34 阶段1-5:会话收尾不 close,监听跨会话常驻,仅网络
* 代次变化时重建)。全部 socket 操作经消息队列由 netTask 串行执行,
* 与本任务/其它网络任务并发安全。
*/ */
void time_sync_task(void *argument) { void time_sync_task(void *argument) {
(void)argument; (void)argument;
@@ -213,6 +220,9 @@ void time_sync_task(void *argument) {
} }
} }
/* 监听 fd 跨会话常驻(N34 阶段1-5):仅首次/网络代次变化/被毁时重建 */
int ls = -1;
for (;;) { for (;;) {
/* 心跳:本任务在 required_mask 内。以下每一处可能长于 100ms 抽样窗的 /* 心跳:本任务在 required_mask 内。以下每一处可能长于 100ms 抽样窗的
* 阻塞点(监听重试 2s、accept 轮询 200ms/次、收尾 50ms)都就近补心跳。 */ * 阻塞点(监听重试 2s、accept 轮询 200ms/次、收尾 50ms)都就近补心跳。 */
@@ -221,11 +231,13 @@ void time_sync_task(void *argument) {
/* 记录本次监听对应的网络代次;代次变化(网络重建)则放弃旧监听重建 */ /* 记录本次监听对应的网络代次;代次变化(网络重建)则放弃旧监听重建 */
uint32_t gen = net_get_generation(); uint32_t gen = net_get_generation();
int ls = time_sync_open_listener();
if (ls < 0) { if (ls < 0) {
DBG_ERROR("time_sync: open listener FAIL, retry later"); ls = time_sync_open_listener();
time_sync_delay_fed(2000U); if (ls < 0) {
continue; DBG_ERROR("time_sync: open listener FAIL, retry later");
time_sync_delay_fed(2000U);
continue;
}
} }
/* 等待 PC 连接:net_accept 在无连接时立即返回 -1,循环重试同一监听 Socket */ /* 等待 PC 连接:net_accept 在无连接时立即返回 -1,循环重试同一监听 Socket */
@@ -235,6 +247,7 @@ void time_sync_task(void *argument) {
if (conn < 0) { if (conn < 0) {
if (net_get_generation() != gen) { if (net_get_generation() != gen) {
net_close(ls); /* 网络重建:旧监听失效,回外层重建 */ net_close(ls); /* 网络重建:旧监听失效,回外层重建 */
ls = -1;
break; break;
} }
time_sync_delay_fed(200U); time_sync_delay_fed(200U);
@@ -247,7 +260,12 @@ void time_sync_task(void *argument) {
} }
time_sync_handle_conn(conn); time_sync_handle_conn(conn);
/* conn(单连接模式下即 ls)已被 net_close 释放,下次循环重建监听 */ /* N34 阶段1-5:会话收尾不 close(单连接模式 conn==ls,close 连监听
* 一起毁);net 层 auto_relisten 已把同 fd 收回 LISTENING,下轮循环
* 直接续 accept。模拟器多连接后端(conn≠ls)才关数据 fd。 */
if (conn != ls) {
net_close(conn);
}
time_sync_delay_fed(50U); time_sync_delay_fed(50U);
} }
+8 -2
View File
@@ -189,7 +189,7 @@ static void s_service_link(uint32_t now) {
* 函数功能:单次服务循环(代次自愈 → accept → 门控收 → 喂 link → 节拍) * 函数功能:单次服务循环(代次自愈 → accept → 门控收 → 喂 link → 节拍)
* 返回值:无 * 返回值:无
* 限定条件:NET_READY 已置位;s_cfg/s_link 已 init * 限定条件:NET_READY 已置位;s_cfg/s_link 已 init
* 函数说明:门控(net_can_accept/net_can_read 纯 RAM 读)+ 兜底节拍照 * 函数说明:门控(net_listener_armed/net_can_read 纯 RAM 读)+ 兜底节拍照
* ui_net_sink I-1/I-2;EOF 时 client==listen 严禁 close(net 层 * ui_net_sink I-1/I-2;EOF 时 client==listen 严禁 close(net 层
* 已 auto_relisten,close 会连监听一起毁) * 已 auto_relisten,close 会连监听一起毁)
*/ */
@@ -224,7 +224,13 @@ static void s_serv_poll(void) {
if (s_client < 0) { if (s_client < 0) {
int c; int c;
if (net_can_accept(s_listen) == 0) { /* N34 阶段1(F-NET-4):门控从 net_can_accept(只认 ESTABLISHED,已退役)
* 迁 net_listener_armed——旧门控把 LISTENING 窗口全挡掉,:2404 的丢
* CONNECT 探针(net_accept_locked 内)因此从不运行(与已修的 :7003
* 同型);armed=ESTABLISHED 待 accept / LISTENING /
* CLOSED+auto_relisten+残留(ESTABLISHED 放行为 2026-10-13 台架补齐:
* 漏它则本任务 200ms 周期抢不过对账② ~400ms 去抖、整场饿死) */
if (net_listener_armed(s_listen) == 0) {
if (net_get_sock(s_listen) == NULL) { if (net_get_sock(s_listen) == NULL) {
s_listen = -1; /* 控制块消失:交回上方重建 */ s_listen = -1; /* 控制块消失:交回上方重建 */
} }
+3 -8
View File
@@ -462,19 +462,14 @@ static int cmd_pass(lftpd_client_t *client, const char *arg)
static int cmd_pasv(lftpd_client_t *client, const char *arg) static int cmd_pasv(lftpd_client_t *client, const char *arg)
{ {
int listener_socket = -1;
int retry;
if (client->data_socket >= 0) { if (client->data_socket >= 0) {
lftpd_inet_close(client->data_socket); lftpd_inet_close(client->data_socket);
client->data_socket = -1; client->data_socket = -1;
} }
for (retry = 0; retry < 3; retry++) { /* N34 阶段1-6:原 3×200ms 应用层重试已删(lftpd_inet_listen 单次化)——
listener_socket = lftpd_inet_listen(0); * 失败直接 425,FTP 客户端自带传输重试语义;服务端不再叠阻塞重试 */
if (listener_socket >= 0) break; int listener_socket = lftpd_inet_listen(0);
vTaskDelay(pdMS_TO_TICKS(200));
}
if (listener_socket < 0) { if (listener_socket < 0) {
send_simple_response(client->socket, 425, STATUS_425); send_simple_response(client->socket, 425, STATUS_425);
return -1; return -1;
+27 -31
View File
@@ -34,39 +34,35 @@ void lftpd_task_wd_beat(void)
int lftpd_inet_listen(int port) int lftpd_inet_listen(int port)
{ {
int retry; /* N34 阶段1-6:应用层阻塞重试退役(原 5 轮 500ms/2000ms,最坏 10s 阻塞
* ftpTask)——Trap 09 的启动形态已由就绪屏障消除(FTP_READY 等 NET_READY,
for (retry = 0; retry < 5; retry++) { * lftpd_start 外层本就有监听失败重建循环);芯片忙窗失败属罕见,PASV 数据
lftpd_task_wd_beat(); /* 看门狗:每轮喂心跳(重试间隔 500ms 在 800ms 容差内) */ * 监听交给 FTP 客户端自身的重试语义(主流 FTP 服务器 passive listen 失败
int s = net_socket(NET_AF_INET, NET_SOCK_STREAM); * 亦直接 425,不在服务端叠应用层阻塞重试) */
if (s < 0) { lftpd_task_wd_beat(); /* 看门狗:入口喂心跳 */
if (retry == 0) DBG_ERROR("create socket"); int s = net_socket(NET_AF_INET, NET_SOCK_STREAM);
vTaskDelay(pdMS_TO_TICKS(500)); if (s < 0) {
continue; DBG_ERROR("create socket");
} return -1;
struct net_sockaddr_in addr;
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons((uint16_t)port);
addr.sin_addr.s_addr = 0;
if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
DBG_ERROR("bind port %d", port);
net_close(s);
return -1;
}
if (net_listen(s) >= 0) {
return s;
}
net_close(s);
if (retry < 4) {
vTaskDelay(pdMS_TO_TICKS(2000));
}
} }
DBG_ERROR("listen failed after 5 retries (total 10s wait)"); struct net_sockaddr_in addr;
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons((uint16_t)port);
addr.sin_addr.s_addr = 0;
if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
DBG_ERROR("bind port %d", port);
net_close(s);
return -1;
}
if (net_listen(s) >= 0) {
return s;
}
DBG_ERROR("listen port %d failed", port);
net_close(s);
return -1; return -1;
} }
+54 -170
View File
@@ -542,9 +542,11 @@ int net_poll(void) {
* 函数说明:1. 芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件自愈 * 函数说明:1. 芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件自愈
* 2. 典型:对端连接占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒 * 2. 典型:对端连接占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒
* 3. 应用因未收到 DISCONNECT 仍以为有客户端,永不调用 accept,事件驱动自愈无路可走 * 3. 应用因未收到 DISCONNECT 仍以为有客户端,永不调用 accept,事件驱动自愈无路可走
* 4. 故周期回读芯片状态,修正**两类**失同步: * 4. 故周期回读芯片状态,修正**三类**失同步:
* 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实;重建交 pump) * 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实;重建交 pump)
* 6. ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event) * 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 丢监听)与④(孤儿监听) * 7. ⚠ N-32 P2(2026-10-12):原③(本层 LISTENING 丢监听)与④(孤儿监听)
* 已**整体移交** `net_listener_pump` —— 本函数内不再有任何重建动作 * 已**整体移交** `net_listener_pump` —— 本函数内不再有任何重建动作
* (单一所有者);认领门槛相应收窄为 ESTABLISHED/LISTENING * (单一所有者);认领门槛相应收窄为 ESTABLISHED/LISTENING
@@ -581,7 +583,8 @@ static void net_reconcile_tick(void) {
net_sock_t *p_sock = &s_net_socks[i]; net_sock_t *p_sock = &s_net_socks[i];
/* 芯片侧状态两字节:[0]=socket 状态,[1]=TCP 状态 */ /* 芯片侧状态两字节:[0]=socket 状态,[1]=TCP 状态 */
uint8_t st[2] = {0, 0}; uint8_t st[2] = {0, 0};
/* 失同步类型:1=连接已失 2=握手已成丢中断 3=监听已失 */ /* 失同步类型:1=连接已失 2=握手已成丢中断 5=芯片静默回收回监听
*(原 3/4=监听失位已移交 pump,编号不复用以避与历史口径混淆) */
int bad = 0; int bad = 0;
/* 对账认领范围:**仅 ESTABLISHED / LISTENING**。 /* 对账认领范围:**仅 ESTABLISHED / LISTENING**。
@@ -614,6 +617,15 @@ static void net_reconcile_tick(void) {
if (!((peer != 0U) && (peer == p_sock->last_peer_port))) { if (!((peer != 0U) && (peer == p_sock->last_peer_port))) {
bad = 2; 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 孤儿监听)已移交 /* ③④(本层 LISTENING 丢监听 / 本层 CLOSED 孤儿监听)已移交
* net_listener_pump —— N-32 P2 单一所有者,本函数不再做重建动作 */ * net_listener_pump —— N-32 P2 单一所有者,本函数不再做重建动作 */
@@ -643,6 +655,20 @@ static void net_reconcile_tick(void) {
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发); /* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发);
* 只置事实,重建由 net_listener_pump 统一做(N-32 P2) */ * 只置事实,重建由 net_listener_pump 统一做(N-32 P2) */
handle_disconnect_event(p_sock); 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 { } else {
/* 类型②:补做 standalone accept */ /* 类型②:补做 standalone accept */
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i); 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); msg.result = net_accept_locked(msg.sockfd, p_addr, p_addrlen);
break; break;
} }
case NET_MSG_RESYNC_LISTEN: { /* N34 阶段1(F-NET-3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 两个
/* 取控制块;NULL = fd 越界或已关闭 */ * 手动自愈消息已删——RESYNC 语义并入对账⑤(net_reconcile_tick),
net_sock_t *p_sock = net_get_sock(msg.sockfd); * RELISTEN 与 pump/D10 完全重叠且对健康监听器是 footgun;
if (p_sock == NULL) { * 生产调用者本就为零(仅 test/board Phase 13,已改由自动机制接管) */
/* 描述符无效 → 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;
}
default: default:
/* 未知消息类型(枚举闭合,正常不应触发):置通用错误码与结果 -1, /* 未知消息类型(枚举闭合,正常不应触发):置通用错误码与结果 -1,
* 保证通知值恒为本条消息的确定结果(无上一条残留) */ * 保证通知值恒为本条消息的确定结果(无上一条残留) */
@@ -1429,26 +1418,10 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
return msg.result; return msg.result;
} }
/* /* N34 阶段1(F-NET-4):net_can_accept 已删除——它只认 ESTABLISHED,作为 accept
* 函数功能:查询监听 Socket 是否有可接受的连接(纯 RAM 读,不发起 RPC) * 门控会把"监听器在服役但本轮还没 CONNECT"的窗口全挡掉(:2404 丢 CONNECT 探针
* 入口参数:sockfd - 监听 Socket 描述符 int * 因此从不运行,与已修的 :7003 探针失效同型);唯一生产调用者 iec104 已迁
* 出口参数:无 * net_listener_armed。net_can_read(RECV 门控)不受影响、保留。 */
* 返回值: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;
}
/* /*
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(纯 RAM 读,不发起 RPC) * 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(纯 RAM 读,不发起 RPC)
@@ -1457,8 +1430,9 @@ int net_can_accept(int sockfd) {
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态); * 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);
* 0 - 未就绪(fd 未分配 / 非监听型 / 正处隔离期以外的不就绪态) int * 0 - 未就绪(fd 未分配 / 非监听型 / 正处隔离期以外的不就绪态) int
* 限定条件:任意任务上下文;net_init() 已调用 * 限定条件:任意任务上下文;net_init() 已调用
* 函数说明:1. N-32(P1 不变量 II):取代 net_can_accept()(要求 ESTABLISHED)作为 * 函数说明:1. N-32(P1 不变量 II):取代已退役的 net_can_accept()(要求
* ui_net_sink 的 accept 门控。原门控下 :7003 永不进 net_accept → * ESTABLISHED,N34 阶段1 删除)作为监听 accept 门控。原门控下
* :7003 永不进 net_accept →
* net_lost_connect_probe(丢 CONNECT 中断补 accept)对该端口从不运行 * net_lost_connect_probe(丢 CONNECT 中断补 accept)对该端口从不运行
* (设计文档 §2.3 末"探针失效") * (设计文档 §2.3 末"探针失效")
* 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338) * 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338)
@@ -1469,7 +1443,7 @@ int net_can_accept(int sockfd) {
* 可能已被芯片接住(对账②)→ 放行让 net_accept_locked 的状态门 * 可能已被芯片接住(对账②)→ 放行让 net_accept_locked 的状态门
* 自行裁决(含 RX 有数据的 drain_pending 接受) * 自行裁决(含 RX 有数据的 drain_pending 接受)
* 4. 只读控制块,不入队、不等通知 → uiTask 不被 netTask 拖住 * 4. 只读控制块,不入队、不等通知 → uiTask 不被 netTask 拖住
* 5. 与 net_can_accept 语义差异是**有意的**:本函数表达"这个监听器 * 5. 与已退役 net_can_accept 的语义差异是**有意的**:本函数表达"这个监听器
* 还在服役",而非"这一轮一定有连接" * 还在服役",而非"这一轮一定有连接"
*/ */
int net_listener_armed(int sockfd) { int net_listener_armed(int sockfd) {
@@ -1479,6 +1453,15 @@ int net_listener_armed(int sockfd) {
if (p_sock == NULL) { if (p_sock == NULL) {
return 0; 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)与常规 /* 正常监听态:放行。net_lost_connect_probe(丢 CONNECT 补 accept)与常规
* accept 都发生在 net_accept_locked 内部,门控挡在外面就永远到不了 */ * accept 都发生在 net_accept_locked 内部,门控挡在外面就永远到不了 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) { 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; return (int)s_net_socks[sockfd].state;
} }
/* /* N34 阶段1(F-NET-3):手动自愈 API net_resync_listen / net_relisten 已删除——
* 函数功能:断连重同步(仅 MCU 侧状态,见头文件注释的适用场景) * RESYNC 语义并入对账⑤(net_reconcile_tick 自动认领"芯片静默回收回 LISTEN"形态,
* 入口参数:sockfd - Socket 描述符 int * 200ms×2 去抖 + 1s 冷却沿用);RELISTEN 与 pump/D10 完全重叠且对健康监听器是
* 出口参数:无 * footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。生产调用者本就为零
* 返回值:0 - 已同步/原本即一致;-1 - 失败 * (仅 test/board Phase 13,已改由自动机制接管)。历史实现见本文件 git 历史。 */
* 限定条件:连接已死且芯片已静默回收后调用(应用层判定)
* 函数说明: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;
}
/* /*
+24 -46
View File
@@ -37,8 +37,9 @@ typedef enum {
NET_MSG_CONNECT, NET_MSG_CONNECT,
NET_MSG_LISTEN, NET_MSG_LISTEN,
NET_MSG_ACCEPT, NET_MSG_ACCEPT,
NET_MSG_RESYNC_LISTEN, /* 仅修 MCU 侧状态(ESTABLISHED→LISTENING),不发芯片命令 */ /* N34 阶段1(F-NET-2/3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删——
NET_MSG_RELISTEN, /* 无条件芯片侧重监听(open+listen,含 Trap 09 重试) */ * 静默回收由对账⑤自动认领(net_reconcile_tick),重建由 pump/D10 单一
* 所有者负责;手动自愈 API 面退役(生产调用者本就为零) */
} net_msg_type_t; } net_msg_type_t;
typedef struct { typedef struct {
@@ -150,48 +151,38 @@ int net_listen(int sockfd);
*/ */
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen); int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
/* /* N34 阶段1(F-NET-4):net_can_accept 已退役——只认 ESTABLISHED 的旧门控会把
* 函数功能:查询监听 Socket 当前是否有可接受的连接(**纯 RAM 读,不发起 RPC**) * "监听器在服役、本轮待 CONNECT"窗口全挡掉(丢 CONNECT 探针因此从不运行);
* 入口参数:sockfd - 监听 Socket 描述符 int * 监听 accept 门控统一用 net_listener_armed(),recv 门控 net_can_read() 保留。 */
* 出口参数:无
* 返回值:1 - 有连接可接受;0 - 无(含描述符无效) int
* 限定条件:net_init() 已调用;任意任务上下文(含 uiTask 等实时性敏感任务)
* 函数说明:1. 只读 Socket 控制块状态,**不经消息队列、不等 netTask 回执** →
* 调用方不会被 netTask 的忙碌程度拖住(对比 net_accept:
* osMessageQueuePut(osWaitForever) + xTaskNotifyWait(portMAX_DELAY),
* 两者均**无上界**)
* 2. 判据与 net_accept_locked() 完全一致:单连接模型下监听 Socket 自身
* 在 CONNECT 事件后转 ESTABLISHED,即"有连接可接受"
* 3. 用途 = **门控**:无连接时直接跳过 net_accept(),把"每轮一次无谓
* RPC"降为 0(UI 推流 10 ms 轮询 = 100 msg/s 队列压力的根因)
* 4. 跨任务读单字:netTask 是唯一写者,最坏两种结果都无害——
* 晚一轮发现(netTask 每 1 ms 就 net_poll 一次),或扑空
* (net_accept 返回 -1 + NET_ERR_WOULDBLOCK)
* 5. 丢 CONNECT 中断由 net_reconcile_tick()(200 ms 对账 + 去抖)自愈,
* 本函数**不承担**自愈责任
*/
int net_can_accept(int sockfd);
/* /*
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(**纯 RAM 读,不发起 RPC**) * 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(**纯 RAM 读,不发起 RPC**)
* 入口参数:sockfd - 监听 Socket 描述符 int * 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:无 * 出口参数:无
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);0 - 不就绪 int * 返回值:1 - 就绪(ESTABLISHED 待 accept / LISTENING / CLOSED+auto_relisten
* +残留数据);0 - 不就绪 int
* 限定条件:net_init() 已调用;任意任务上下文 * 限定条件:net_init() 已调用;任意任务上下文
* 函数说明:1. N-32(P1 不变量 II):取代 `net_can_accept()`(只认 ESTABLISHED)作为 * 函数说明:1. N-32(P1 不变量 II,N34 阶段1 补齐 ESTABLISHED 放行):取代已退役的
* ui_net_sink 的 accept 门控——原门控使 :7003 永不进 `net_accept()`, * `net_can_accept()`(只认 ESTABLISHED)作为监听 accept 门控——原门控使
* :7003 永不进 `net_accept()`,
* `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的 * `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的
* "数据被吞/425"主根因修复)对该端口等于从未运行 * "数据被吞/425"主根因修复)对该端口等于从未运行
* 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在 * 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在
* `net_accept_locked` **内部**,门控挡在外面就永远到不了 * `net_accept_locked` **内部**,门控挡在外面就永远到不了
* 2b. 放行 ESTABLISHED(N34 阶段1 台架修正):单连接模型下 CONNECT 处理
* (net_poll 分派 / 探针补 accept / 对账② standalone accept)把监听 fd
* 转 ESTABLISHED,这正是"待 accept"电平——漏放行则任务周期慢的消费者
* (i104 200ms 抢不过对账② ~400ms 去抖)永不 accept、会话整场饿死
* (2026-10-13 实测全 18 项 FAIL)。此态 accept 必返 fd,无风暴风险
* 3. 放行 CLOSED+auto_relisten **且 readable≠0**(有残留数据): * 3. 放行 CLOSED+auto_relisten **且 readable≠0**(有残留数据):
* 保住 N-30 的 "closed-but-readable" 接受;**无残留时必须返回 0**, * 保住 N-30 的 "closed-but-readable" 接受;**无残留时必须返回 0**,
* 否则调用方会在整个 CLOSED 窗口内每轮发起 `net_accept`(RPC 无上界) * 否则调用方会在整个 CLOSED 窗口内每轮发起 `net_accept`(RPC 无上界)
* → 队列积压 → 喂不上狗(2026-10-12 P2a 台架实测 48 条 task stall)。 * → 队列积压 → 喂不上狗(2026-10-12 P2a 台架实测 48 条 task stall)。
* 重建由 `net_listener_pump` 负责,调用方无需催促 * 重建由 `net_listener_pump` 负责,调用方无需催促
* 4. 与 `net_can_accept` 的语义差异**有意为之**:本函数回答"这个监听器 * 4. 与已退役 `net_can_accept` 的关系:本函数 = 其 ESTABLISHED 放行态
* 这一轮**值不值得**发起一次 accept",而非"一定有连接" * ∪ LISTENING ∪ CLOSED+残留;回答"这个监听器这一轮**值不值得**发起
* 5. 跨任务读单字,同 `net_can_accept` 的最坏情形论证 * 一次 accept",而非"一定有连接"
* 5. 跨任务读单字,同 `net_can_read` 的最坏情形论证
*/ */
int net_listener_armed(int sockfd); int net_listener_armed(int sockfd);
@@ -346,23 +337,10 @@ int net_get_errno(void);
*/ */
int net_sock_state(int sockfd); int net_sock_state(int sockfd);
/* /* N34 阶段1(F-NET-3):net_resync_listen / net_relisten 声明已删——
* 函数功能:断连重同步——仅把 MCU 侧状态对齐到 LISTENING,不发任何芯片命令 * 静默回收形态由对账⑤自动认领(≤400ms 去抖 + 1s 冷却,纪律"不发芯片命令"
* 适用场景:芯片"静默回收"(peer 发 FIN 后芯片自行把 Socket 回收回监听 * 原样保留在认领动作内);监听重建由 pump(100ms 周期)/D10(30ms 重试)
* 且不发 DISCONNECT 中断,2026-09-22 台架实证)→ MCU 状态滞留 * 单一所有者。勿再引入"应用层手动自愈"入口。 */
* ESTABLISHED。调用方须确信连接已死/芯片已回收(如会话超时中止后);
* 连接仍存活时严禁调用(会把活连接标记成监听态)。
* 返回值:0 - 已同步(或原本即 LISTENING);-1 - 失败(描述符/队列无效)
*/
int net_resync_listen(int sockfd);
/*
* 函数功能:无条件重新监听(芯片侧 open+listen,含 Trap 09 失败重试)
* 适用场景:断连后需确保监听器确定在位的自愈(监听短暂中断 ~1s,调用方
* 的重连逻辑须容忍)
* 返回值:0 - 成功;-1 - 失败
*/
int net_relisten(int sockfd);
/* /*
* 函数功能:将 IP 地址字符串转为网络字节序 * 函数功能:将 IP 地址字符串转为网络字节序
+2 -2
View File
@@ -1,4 +1,4 @@
/* /*
* 模块名称:UI Network Sink (RemoDispBus 服务端) * 模块名称:UI Network Sink (RemoDispBus 服务端)
* 模块功能:实现 RemoDispBus 协议服务端,接收远端连接与导航按键,回送 1bpp 帧缓冲 * 模块功能:实现 RemoDispBus 协议服务端,接收远端连接与导航按键,回送 1bpp 帧缓冲
* 适用平台:STM32F407ZGTx + CH395F 以太网 * 适用平台:STM32F407ZGTx + CH395F 以太网
@@ -44,7 +44,7 @@ int ui_net_sink_start(uint16_t port);
* 入口参数:无 * 入口参数:无
* 返回值:无 * 返回值:无
* 限定条件:ui_net_sink_start() 已成功调用 * 限定条件:ui_net_sink_start() 已成功调用
* 函数说明:1. 无客户端时先门控(net_can_accept,纯 RAM 读)再尝试接受连接 * 函数说明:1. 无客户端时先门控(net_listener_armed,纯 RAM 读)再尝试接受连接
* 2. 有客户端时先门控(net_can_read)再用 DONTWAIT 收帧并解析 * 2. 有客户端时先门控(net_can_read)再用 DONTWAIT 收帧并解析
* (KEEPLIVE/INIT/KEY/LCDMEM);门控判为无数据时仍每 32 轮兜底收一次 * (KEEPLIVE/INIT/KEY/LCDMEM);门控判为无数据时仍每 32 轮兜底收一次
* 3. 对端关闭或异常时复位客户端,等待下次重监听 * 3. 对端关闭或异常时复位客户端,等待下次重监听
@@ -130,7 +130,9 @@
- **单连接语义**(`AGENTS.md` 模块约定,抄 `ui_net_sink.c` 模板):2404 监听与 - **单连接语义**(`AGENTS.md` 模块约定,抄 `ui_net_sink.c` 模板):2404 监听与
数据通道复用同一 socket(accept 返回即 listen fd);EOF 时 net 层 auto_relisten, 数据通道复用同一 socket(accept 返回即 listen fd);EOF 时 net 层 auto_relisten,
应用层**不得**关 fd(`client == listen` 分支);仅模拟器多连接后端(fd 不同)显式关。 应用层**不得**关 fd(`client == listen` 分支);仅模拟器多连接后端(fd 不同)显式关。
- **门控 + 兜底**:`net_can_accept`/`net_can_read`(纯 RAM 读)省无谓 RPC 往返; - **门控 + 兜底**:`net_listener_armed`/`net_can_read`(纯 RAM 读)省无谓 RPC 往返
(N34 阶段1:门控从只认 ESTABLISHED 的 `net_can_accept` 迁移——旧门控把监听
服役窗口全挡掉,丢 CONNECT 探针从不运行);
每 32 轮无条件 recv 一次防 RECV_OK 中断丢失(ui_net_sink I-1/I-2 先例)。 每 32 轮无条件 recv 一次防 RECV_OK 中断丢失(ui_net_sink I-1/I-2 先例)。
- **网络代次自愈**:`net_get_generation()` 变化 → 关旧 fd 重建监听(netTask - **网络代次自愈**:`net_get_generation()` 变化 → 关旧 fd 重建监听(netTask
NET_RECONFIG 重建路径的既有约定)。 NET_RECONFIG 重建路径的既有约定)。
@@ -420,8 +420,8 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
| `net_accept_locked` | 产出的是**连接**(LISTENING→ESTABLISHED 迁移),属连接层;它只**调用**新模块 | | `net_accept_locked` | 产出的是**连接**(LISTENING→ESTABLISHED 迁移),属连接层;它只**调用**新模块 |
| `net_lost_connect_probe` | 做的是"补 accept"(连接建立),不是重建监听 | | `net_lost_connect_probe` | 做的是"补 accept"(连接建立),不是重建监听 |
| `net_listener_armed` | **双端公开 API**(sim 也实现);纯 RAM 查询,很薄,留在原处避免 sim 缺符号 | | `net_listener_armed` | **双端公开 API**(sim 也实现);纯 RAM 查询,很薄,留在原处避免 sim 缺符号 |
| `net_resync_listen` | 公开 API + 消息分派;它**只改本层状态**,本就不调 relisten | | ~~`net_resync_listen`~~ | **N34 阶段1 已删除**:其"只改本层状态"语义并入对账⑤(静默回收形态自动认领,纪律"不发芯片命令"随动作沉淀进 `net_reconcile_tick`),手动 API 面退役 |
| `net_reconcile_tick` | ①② 是**连接**失同步自愈(③④ 已在 P2a 移交 pump) | | `net_reconcile_tick` | ①②⑤ 是**连接**失同步自愈(③④ 已在 P2a 移交 pump;⑤ 为 N34 阶段1 新增,吸收原 `net_resync_listen` 语义) |
| `net_sock_t` 全部字段 | 数据归 `net_types.h`(双端共享核心),**不随模块走** | | `net_sock_t` 全部字段 | 数据归 `net_types.h`(双端共享核心),**不随模块走** |
> ⚠ `net_sock_t` 的 `last_peer_port` / `lost_conn_seen` 是**跨层字段**:新模块的 > ⚠ `net_sock_t` 的 `last_peer_port` / `lost_conn_seen` 是**跨层字段**:新模块的
+1 -1
View File
@@ -43,7 +43,7 @@
| N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;N-4 二期专门建模 | | N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;N-4 二期专门建模 |
| N-2 | **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) | | N-2 | **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) |
| N-3 | **`net_process_messages` / `net_recv_to` 等缺失** | 未实现(MCU 专属:CH395F 消息队列驱动、带超时接收) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"带超时接收"与"消息泵"的上层逻辑在模拟器上**不可编译/不可运行** | 板端 CH395F / NET 套件 | | N-3 | **`net_process_messages` / `net_recv_to` 等缺失** | 未实现(MCU 专属:CH395F 消息队列驱动、带超时接收) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"带超时接收"与"消息泵"的上层逻辑在模拟器上**不可编译/不可运行** | 板端 CH395F / NET 套件 |
| N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,需 `net_relisten` 显式重建 | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` | | N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten`/pump 自动重建(N34 阶段1 后无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` |
| N-5 | **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 | | N-5 | **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 |
| N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`,使 `DONTWAIT` 语义与 MCU 对齐(设计要点 2) | 中断驱动 + 状态机,天然非阻塞 | ——(此项是**对齐**而非差距,登记以说明"为何模拟器能测非阻塞路径") | —— | | N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`,使 `DONTWAIT` 语义与 MCU 对齐(设计要点 2) | 中断驱动 + 状态机,天然非阻塞 | ——(此项是**对齐**而非差距,登记以说明"为何模拟器能测非阻塞路径") | —— |
@@ -669,7 +669,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
| **开机首个 FTP 会话第 2 个数据连接 425** | **D20**(已定位未修,§8.2) | 复现:`test/host/ftp_regression.py`(复位开边界);现场规避:客户端重试 425(瞬态 4xx);根治方向:数据 Socket 轮换 | | **开机首个 FTP 会话第 2 个数据连接 425** | **D20**(已定位未修,§8.2) | 复现:`test/host/ftp_regression.py`(复位开边界);现场规避:客户端重试 425(瞬态 4xx);根治方向:数据 Socket 轮换 |
| **`[LAT]` 多段数值雷同 / 全 0 却 verdict=OK** | **D18/D19**(测试工装) | 核对 `lat_probe_add_seg` 段名过滤 + `lat_time_init` 调用 + `med==0` 判无效 | | **`[LAT]` 多段数值雷同 / 全 0 却 verdict=OK** | **D18/D19**(测试工装) | 核对 `lat_probe_add_seg` 段名过滤 + `lat_time_init` 调用 + `med==0` 判无效 |
| **连接建立后 15~30s 无流量即被 RST** | **D13**:芯片空闲超时(芯片行为) | 客户端须容忍并重连(peer `--wait` 重试是正常路径) | | **连接建立后 15~30s 无流量即被 RST** | **D13**:芯片空闲超时(芯片行为) | 客户端须容忍并重连(peer `--wait` 重试是正常路径) |
| **peer 重连后 MCU 状态滞留、新连接字节被吞** | **D14**:芯片静默回收 Socket | `net_resync_listen`/`net_relisten` 自愈路径核对 | | **peer 重连后 MCU 状态滞留、新连接字节被吞** | **D14**:芯片静默回收 Socket | 对账⑤自动认领(N34 阶段1:手动 `net_resync_listen`/`net_relisten` 已退役,Phase 13 abort 期自愈亦交 net 层) |
| L8 超而 L7 正常 | 生产路径附加成本(uiTask 节拍/LVGL/日志) | 对照 L10/L11 与 §10.1(渲染已排除;per-recv 日志已删,S8 非阻塞日志仍待办) | | L8 超而 L7 正常 | 生产路径附加成本(uiTask 节拍/LVGL/日志) | 对照 L10/L11 与 §10.1(渲染已排除;per-recv 日志已删,S8 非阻塞日志仍待办) |
| Δ 显著 >80ms | CH395F 栈净开销异常 | 回到 L1~L9 逐段排查 | | Δ 显著 >80ms | CH395F 栈净开销异常 | 回到 L1~L9 逐段排查 |
+22 -29
View File
@@ -60,7 +60,7 @@
| `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) | | `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) |
| `net_types.h` | 类型定义(地址结构、Socket 控制块等) | | `net_types.h` | 类型定义(地址结构、Socket 控制块等) |
| `net_socket.h` | BSD Socket API 头文件,含 `net_msg_t` 消息类型定义 | | `net_socket.h` | BSD Socket API 头文件,含 `net_msg_t` 消息类型定义 |
| `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含 L3 pending 化 recv、Trap 22 周期对账(200ms,去抖+冷却,只剩①②) | | `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含 L3 pending 化 recv、Trap 22 周期对账(200ms,去抖+冷却,①②⑤) |
| `net_listener.c`(**真机专属**) | **监听生命周期**(N-32 §10 模块化):`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick`(D10 重试 30ms/2s)/ `net_listener_pump`(唯一重建入口,100ms)/ `net_listener_settle`(沉降/隔离 100ms/400ms);内部头,模拟器不编译 | | `net_listener.c`(**真机专属**) | **监听生命周期**(N-32 §10 模块化):`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick`(D10 重试 30ms/2s)/ `net_listener_pump`(唯一重建入口,100ms)/ `net_listener_settle`(沉降/隔离 100ms/400ms);内部头,模拟器不编译 |
| `net_select.h` | select/poll API 头文件 | | `net_select.h` | select/poll API 头文件 |
| `net_select.c` | select/poll 实现(仅 MCU 端编译) | | `net_select.c` | select/poll 实现(仅 MCU 端编译) |
@@ -153,7 +153,7 @@ sequenceDiagram
| 规则 | 说明 | | 规则 | 说明 |
|------|------| |------|------|
| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()`,及自愈 `net_relisten()` / `net_resync_listen()`——请求入队列,netTask 串行执行,调用方阻塞等回执 | | **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()`——请求入队列,netTask 串行执行,调用方阻塞等回执。**自愈无显式 API**(N34 阶段1:`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 pump 自动认领,见 §3.6) |
| **`netTask` 内** | 内部 API `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` / `net_listen_locked()` | | **`netTask` 内** | 内部 API `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` / `net_listen_locked()` |
| **`net_socket()` / `net_bind()`** | 直接调用,无需队列;实际在就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)中创建监听 | | **`net_socket()` / `net_bind()`** | 直接调用,无需队列;实际在就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)中创建监听 |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **4 个非阻塞 tick**(D10 重监听重试 → L3 挂起 recv 完成 → Trap 22 对账 200ms → N-32 监听器 pump 100ms),再处理队列 | | **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **4 个非阻塞 tick**(D10 重监听重试 → L3 挂起 recv 完成 → Trap 22 对账 200ms → N-32 监听器 pump 100ms),再处理队列 |
@@ -176,7 +176,7 @@ int net_close_sock(net_sock_t *p_sock);
- **`netTask` 不可向自身发消息**:`netTask` 中直接调 `net_send()` 会死锁(等待自己处理消息),必须在 `netTask` 中使用 `net_send_sock()` 等内部版本 - **`netTask` 不可向自身发消息**:`netTask` 中直接调 `net_send()` 会死锁(等待自己处理消息),必须在 `netTask` 中使用 `net_send_sock()` 等内部版本
- **阻塞 recv 不再独占 netTask(L3 pending 化,2026-09-22)**:无数据时公共 `net_recv()` / `net_recv_to()` **登记挂起请求后返回**(每 fd 至多一个,禁止覆盖——覆盖即丢失前调用方通知 = 永久挂死);完成由 `net_pending_tick()` 按主循环节拍检查(①数据 ②到点 ③断连,看门狗 1s)并经任务通知唤醒调用方——等待期间调用方在自己上下文睡通知,netTask 零占用(消除 E-02 头阻塞)。副作用:`buf` 指针须保持有效至返回;close / 网络重建 / 陈旧重同步会**显式取消并通知**(`NET_ERR_CONNRESET` 等),调用方永不挂死 - **阻塞 recv 不再独占 netTask(L3 pending 化,2026-09-22)**:无数据时公共 `net_recv()` / `net_recv_to()` **登记挂起请求后返回**(每 fd 至多一个,禁止覆盖——覆盖即丢失前调用方通知 = 永久挂死);完成由 `net_pending_tick()` 按主循环节拍检查(①数据 ②到点 ③断连,看门狗 1s)并经任务通知唤醒调用方——等待期间调用方在自己上下文睡通知,netTask 零占用(消除 E-02 头阻塞)。副作用:`buf` 指针须保持有效至返回;close / 网络重建 / 陈旧重同步会**显式取消并通知**(`NET_ERR_CONNRESET` 等),调用方永不挂死
- **队列阻塞语义**:只有 `osMessageQueuePut` 失败(8 长度固定队列,极端情形)时公共 API 阻塞;recv 挂起等待**不计入**队列占用(完成前队列中无消息滞留) - **队列阻塞语义**:只有 `osMessageQueuePut` 失败(8 长度固定队列,极端情形)时公共 API 阻塞;recv 挂起等待**不计入**队列占用(完成前队列中无消息滞留)
- **哪些操作要求 netTask 已运行**:`net_listen()` / `net_accept()` / `net_connect()` / `net_send()` / `net_recv()` / `net_close()` / `net_relisten()` / `net_resync_listen()` 全经消息队列 → 只能在就绪屏障后的任务调用;`net_socket()` / `net_bind()` 为直接调用,但同样建议屏障后;**`net_can_accept()` / `net_can_read()` 是纯 RAM 读控制块**(§10.1),不入队、不等回执,**任意任务可安全周期调用**(用途 = 门控,把"无谓的 RPC"降为 0) - **哪些操作要求 netTask 已运行**:`net_listen()` / `net_accept()` / `net_connect()` / `net_send()` / `net_recv()` / `net_close()` 全经消息队列 → 只能在就绪屏障后的任务调用;`net_socket()` / `net_bind()` 为直接调用,但同样建议屏障后;**`net_listener_armed()` / `net_can_read()` 是纯 RAM 读控制块**(§10.1),不入队、不等回执,**任意任务可安全周期调用**(用途 = 门控,把"无谓的 RPC"降为 0)
### 3.6 芯片/本层状态对账与重监听自愈(Trap 09 / 22 / 23) ### 3.6 芯片/本层状态对账与重监听自愈(Trap 09 / 22 / 23)
@@ -186,7 +186,7 @@ int net_close_sock(net_sock_t *p_sock);
|---|---|---|---| |---|---|---|---|
| **`auto_relisten`** | 数据通道正常 EOF / DISCONNECT | 重开 + 重新 LISTEN,回 LISTENING;应用层无需 close/重 listen | `net_listen()` 置位;`net_close()` 清 | | **`auto_relisten`** | 数据通道正常 EOF / DISCONNECT | 重开 + 重新 LISTEN,回 LISTENING;应用层无需 close/重 listen | `net_listen()` 置位;`net_close()` 清 |
| **D10 非阻塞重监听重试** | 重开失败(Trap 09:芯片 close 后需沉降) | 单次尝试失败即登记待重试,每轮到点重试;超时限放弃并记 `relisten give up` | 重试间隔 30ms / 时限 2s(自首次尝试);同 fd 有在途挂起 recv 时跳过(L3 保险,防毁在途连接) | | **D10 非阻塞重监听重试** | 重开失败(Trap 09:芯片 close 后需沉降) | 单次尝试失败即登记待重试,每轮到点重试;超时限放弃并记 `relisten give up` | 重试间隔 30ms / 时限 2s(自首次尝试);同 fd 有在途挂起 recv 时跳过(L3 保险,防毁在途连接) |
| **Trap 22 周期对账** | 芯片丢 CONNECT/DISCONNECT 中断(快速复连);或静默回收 Socket(peer FIN,无 DISCONNECT 中断) | 每 200ms 对在用 socket 回读芯片状态,修正**两类**失同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理;② 本层 LISTENING 而芯片已握手 → 补 accept(③④ 已移交 pump) | 周期 200ms;**连续 2 次失配**才动手(去抖 ≈400ms)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴) | | **Trap 22 周期对账** | 芯片丢 CONNECT/DISCONNECT 中断(快速复连);或静默回收 Socket(peer FIN,无 DISCONNECT 中断) | 每 200ms 对在用 socket 回读芯片状态,修正**三类**失同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理;② 本层 LISTENING 而芯片已握手 → 补 accept;⑤ 本层 ESTABLISHED 而芯片已静默回收回 LISTEN(0x05/0x01)→ RESYNC 语义只修本层、不发芯片命令(N34 阶段1 新增,原手动 API `net_resync_listen` 的语义并入;③④ 已移交 pump) | 周期 200ms;**连续 2 次失配**才动手(去抖 ≈400ms)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴) |
| **N-32 监听器 pump**(**唯一重建入口**) | 监听型 fd 的芯片状态与本层不一致(会话结束待重建 / 本层状态滞后) | 每 100ms 检查监听型 fd:芯片失位即重建(`net_listener_try`);本层 CLOSED 而芯片**正在** LISTEN 时**只做状态重同步**、不发芯片命令 | 周期 100ms;连续 2 次失配(≈200ms)+ 动手后 1s 冷却;其它位置只置事实、不做重建动作 | | **N-32 监听器 pump**(**唯一重建入口**) | 监听型 fd 的芯片状态与本层不一致(会话结束待重建 / 本层状态滞后) | 每 100ms 检查监听型 fd:芯片失位即重建(`net_listener_try`);本层 CLOSED 而芯片**正在** LISTEN 时**只做状态重同步**、不发芯片命令 | 周期 100ms;连续 2 次失配(≈200ms)+ 动手后 1s 冷却;其它位置只置事实、不做重建动作 |
| **Trap 23 排渣隔离** | 异常释放(芯片 TIMEOUT,如客户端中止上传):在途数据落进 RX 缓冲,直接复监听 → 下条连接把 ~2048B 残渣当自己的数据读走(上传静默截断);正常 FIN 释放亦有残留 DISCONNECT 收尾问题 | 复监听前沉降/隔离:持续排空 RX 残渣 + 主动 close + 排空中断,等芯片回 CLOSED/TCP_CLOSED 才 open+listen;到期强制放行 | 沉降等级(`net_listener_settle`):异常释放 QUARANTINE **400ms** / 正常释放 GRACEFUL **100ms**(2026-09-24 起正常 FIN 亦需沉降);上限 **800ms** 强制放行(**必须 < 2s 时限**,否则监听被放弃) | | **Trap 23 排渣隔离** | 异常释放(芯片 TIMEOUT,如客户端中止上传):在途数据落进 RX 缓冲,直接复监听 → 下条连接把 ~2048B 残渣当自己的数据读走(上传静默截断);正常 FIN 释放亦有残留 DISCONNECT 收尾问题 | 复监听前沉降/隔离:持续排空 RX 残渣 + 主动 close + 排空中断,等芯片回 CLOSED/TCP_CLOSED 才 open+listen;到期强制放行 | 沉降等级(`net_listener_settle`):异常释放 QUARANTINE **400ms** / 正常释放 GRACEFUL **100ms**(2026-09-24 起正常 FIN 亦需沉降);上限 **800ms** 强制放行(**必须 < 2s 时限**,否则监听被放弃) |
@@ -875,20 +875,20 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
> 单连接模式:监听 Socket 在 CONNECT 后**自身即数据通道**(§5.1)。`net_accept` 失败时若状态为 CLOSED 且 `auto_relisten` 开启,内部触发**非阻塞**重监听(失败交重试轮询,§3.6)。中断丢失也不会让监听死亡——对账在 200ms 内修正"本层 LISTENING 而芯片监听消失"等失同步。 > 单连接模式:监听 Socket 在 CONNECT 后**自身即数据通道**(§5.1)。`net_accept` 失败时若状态为 CLOSED 且 `auto_relisten` 开启,内部触发**非阻塞**重监听(失败交重试轮询,§3.6)。中断丢失也不会让监听死亡——对账在 200ms 内修正"本层 LISTENING 而芯片监听消失"等失同步。
#### net_can_accept / net_can_read - 就绪门控(**纯 RAM 读,不走消息队列**) #### net_listener_armed / net_can_read - 就绪门控(**纯 RAM 读,不走消息队列**)
```c ```c
int net_can_accept(int sockfd); /* 1 = 有连接可接受;0 = 无 */ int net_listener_armed(int sockfd); /* 1 = 监听器在服役,值得发起 accept;0 = 跳过本轮 */
int net_can_read(int sockfd); /* 1 = 应发一次 recv;0 = 可跳过本轮 */ int net_can_read(int sockfd); /* 1 = 应发一次 recv;0 = 可跳过本轮 */
``` ```
| 项 | 说明 | | 项 | 说明 |
|------|------| |------|------|
| 代价 | **零 RPC**:只读 Socket 控制块(`net_get_sock()`),不入队、不等 netTask 回执 → 任意任务上下文可安全周期调用,**不被 netTask 忙碌程度拖住** | | 代价 | **零 RPC**:只读 Socket 控制块(`net_get_sock()`),不入队、不等 netTask 回执 → 任意任务上下文可安全周期调用,**不被 netTask 忙碌程度拖住** |
| `net_can_accept` 判据 | `state == ESTABLISHED`,与 `net_accept_locked()` 严格等价(单连接模型:监听 Socket 自身在 CONNECT 后转数据通道) | | `net_listener_armed` 判据 | `ESTABLISHED`(CONNECT 已处理、待应用 accept——单连接模型的"连接已到"电平,漏放行则任务周期慢的消费者永不 accept),或 `LISTENING`,或 `CLOSED + auto_relisten` **且 RX 有残留**(closed-with-data 救回窗)——表达"这个监听器还在服役、这一轮值不值得发起 accept",而非"一定有连接"。(前身 `net_can_accept()` 只认 `ESTABLISHED`,会把监听服役窗口全挡掉、丢 CONNECT 探针从不运行——N34 阶段1 已退役,其 ESTABLISHED 放行态并入本函数,勿再引入) |
| `net_can_read` 判据 | `ESTABLISHED` → 看 `readable`;**非 `ESTABLISHED` 一律返 1**(必须让上层 recv 一次拿到 EOF/错误并复位自身状态,门掉它会把"连接已消失"永久藏住);描述符失效也返 1(让 recv 报 BADF 走既有错误分支) | | `net_can_read` 判据 | `ESTABLISHED` → 看 `readable`;**非 `ESTABLISHED` 一律返 1**(必须让上层 recv 一次拿到 EOF/错误并复位自身状态,门掉它会把"连接已消失"永久藏住);描述符失效也返 1(让 recv 报 BADF 走既有错误分支) |
| 用途 | **门控**:无活时跳过 `net_accept()`/`net_recv()`——两者内部都是 `osMessageQueuePut(osWaitForever)` + `xTaskNotifyWait(portMAX_DELAY)`,**均无上界**(队列深 8 且与 FTP/timeSync 共享) | | 用途 | **门控**:无活时跳过 `net_accept()`/`net_recv()`——两者内部都是 `osMessageQueuePut(osWaitForever)` + `xTaskNotifyWait(portMAX_DELAY)`,**均无上界**(队列深 8 且与 FTP/timeSync 共享) |
| ⚠ 不可作为唯一判据 | `readable` 只由 RECV_OK 中断置位,而 §3.6 的三类对账**不含**"芯片 RX 非空但 `readable=0`" → 中断丢失时 `net_can_read()` 恒返 0。**调用方必须保留低频兜底 recv**(`ui_net_sink.c` 每 32 轮无条件收一次,教训见 `docs/子系统设计/看门狗设计说明.md` §11.5 第 1 条) | | ⚠ 不可作为唯一判据 | `readable` 只由 RECV_OK 中断置位,而 §3.6 的对账**不含**"芯片 RX 非空但 `readable=0`" → 中断丢失时 `net_can_read()` 恒返 0。**调用方必须保留低频兜底 recv**(`ui_net_sink.c` 每 32 轮无条件收一次,教训见 `docs/子系统设计/看门狗设计说明.md` §11.5 第 1 条) |
| 竞态 | netTask 是唯一写者,单字读;最坏"晚一轮发现"(netTask 每 1 ms 就 `net_poll()`)或"扑空"(RPC 返回 `NET_ERR_WOULDBLOCK`),均无副作用 | | 竞态 | netTask 是唯一写者,单字读;最坏"晚一轮发现"(netTask 每 1 ms 就 `net_poll()`)或"扑空"(RPC 返回 `NET_ERR_WOULDBLOCK`),均无副作用 |
| 模拟器端 | `net_socket_sim.c` 同名实现**恒返回 1**——Winsock 本就非阻塞、无 RPC 成本,恒 1 使门控在模拟器上逐位等价于改前行为 | | 模拟器端 | `net_socket_sim.c` 同名实现**恒返回 1**——Winsock 本就非阻塞、无 RPC 成本,恒 1 使门控在模拟器上逐位等价于改前行为 |
@@ -979,21 +979,14 @@ int net_sock_state(int sockfd);
跨任务只读快照(单字节读);描述符无效/未占用返回 -1;值可能滞后 netTask 最新状态。用于**状态轮询**(客户端等 ESTABLISHED,§5.2),不替代 accept/connect。 跨任务只读快照(单字节读);描述符无效/未占用返回 -1;值可能滞后 netTask 最新状态。用于**状态轮询**(客户端等 ESTABLISHED,§5.2),不替代 accept/connect。
**net_resync_listen — 断连重同步(仅修 MCU 侧)** **net_resync_listen / net_relisten — 已退役(N34 阶段1,勿再引入)**
```c 两者曾是"应用层手动自愈"入口:`net_resync_listen()` 只修 MCU 侧状态(D14 静默回收),`net_relisten()` 无条件芯片侧 open+listen。现状:
int net_resync_listen(int sockfd);
```
适用场景(D14):对端 FIN 后芯片**静默回收** Socket 回监听且**不发 DISCONNECT 中断** → 本层滞留 ESTABLISHED,新连接字节被旧会话吞掉。须**确认连接已死**(如控制通道超时中止后)再调;只把本层状态改回 LISTENING 并取消本 fd 挂起 recv,**不发任何芯片命令**(发 close/重监听会摧毁芯片侧健康监听器 → 连接永拒)。返回 0=已同步(或原本即一致),-1=失败。注:Trap 22 周期对账同样能自愈合此失同步(≤200ms),显式 API 用于"不愿等下一 tick"的场景。 - **静默回收**由对账⑤自动认领(§3.6:本层 ESTABLISHED 而芯片 0x05/0x01 → RESYNC 语义,≤400ms 去抖 + 1s 冷却,纪律"不发芯片命令"原样保留在认领动作内)——应用无需判定"连接是否已死",也就消除了"误判活连接调 resync 把活连接标成监听态"的 footgun。
- **监听重建**由 pump(100ms 周期,唯一重建入口)/ D10(30ms 重试)单一所有者负责——`net_relisten()` 对健康监听器反而是 footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。
**net_relisten — 无条件重新监听** 生产调用者本就为零(仅板级测试 Phase 13,已改由自动机制接管)。历史实现见 `net_socket.c` git 历史。
```c
int net_relisten(int sockfd);
```
TCP 监听型 Socket:芯片侧 open+listen(D10 非阻塞重试)。芯片侧已在监听时:首次 open 失败 → close 重建 → 监听短暂中断 ~1s;两种情形都收敛到"监听器确定在位"。用途:代次号变化后显式重建(见 `docs/子系统设计/网络配置说明.md` §4)、应用判定连接已死需确定性监听。正常流无需(auto_relisten + 对账已覆盖)。返回 0 成功 / -1 失败。
**net_getsockname — 获取 Socket 本地地址(IP+端口)** **net_getsockname — 获取 Socket 本地地址(IP+端口)**
@@ -1011,14 +1004,14 @@ Socket 已创建或已绑定时可用;port 为 0(动态分配)时返回已
void net_process_messages(void); void net_process_messages(void);
``` ```
处理其他任务投递的请求(send/recv/connect/accept/close/**resync-listen / relisten**)。每次调用先跑 4 个**非阻塞** tick,再取队列消息(0 超时出队): 处理其他任务投递的请求(send/recv/connect/accept/close)。每次调用先跑 4 个**非阻塞** tick,再取队列消息(0 超时出队):
1. **D10 重监听重试**(`net_listener_retry_tick`):断连后监听器恢复的到点重试(30ms 间隔 / 2s 时限,§3.6) 1. **D10 重监听重试**(`net_listener_retry_tick`):断连后监听器恢复的到点重试(30ms 间隔 / 2s 时限,§3.6)
2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点 ③断连 检查每个挂起请求并通知调用方(看门狗 1s) 2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点 ③断连 检查每个挂起请求并通知调用方(看门狗 1s)
3. **Trap 22 芯片/本层对账**(`net_reconcile_tick`):200ms 周期修正**两类**失同步(①②,§3.6) 3. **Trap 22 芯片/本层对账**(`net_reconcile_tick`):200ms 周期修正**三类**失同步(①②⑤,§3.6)
4. **N-32 监听器 pump**(`net_listener_pump`):100ms 周期,**唯一重建入口**(③④;本层 CLOSED 而芯片在听时只重同步,不发芯片命令,§3.6) 4. **N-32 监听器 pump**(`net_listener_pump`):100ms 周期,**唯一重建入口**(③④;本层 CLOSED 而芯片在听时只重同步,不发芯片命令,§3.6)
> `net_msg_t.timeout_ms`:L3 分片预算(`net_recv_to()`),0=默认 `NET_RECV_TIMEOUT_MS`(30s)。消息类型含 `NET_MSG_RESYNC_LISTEN`(仅 MCU 侧状态)与 `NET_MSG_RELISTEN`(芯片侧重监听)。 > `net_msg_t.timeout_ms`:L3 分片预算(`net_recv_to()`),0=默认 `NET_RECV_TIMEOUT_MS`(30s)。消息类型仅 6 种(send/recv/close/connect/listen/accept)——自愈类消息 `NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 已随 API 退役删除(N34 阶段1)。
#### net_send_sock - 发送数据(内部版本) #### net_send_sock - 发送数据(内部版本)
@@ -1289,10 +1282,9 @@ if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
(2026-09-21 台架实证:`ui_net_sink` 曾在 EOF 时无条件 `net_close`,表现为 7003 答完一帧即断连、 (2026-09-21 台架实证:`ui_net_sink` 曾在 EOF 时无条件 `net_close`,表现为 7003 答完一帧即断连、
端口随后"连接被拒"。) 端口随后"连接被拒"。)
**芯片静默回收(D14,2026-09-22 台架实证)**:对端发 FIN 后,芯片可能**自行把 Socket 回收回 LISTEN 且不发 DISCONNECT 中断** → 本层滞留 ESTABLISHED,应用继续往死连接上读写,新连接的字节被旧会话吞掉。两种处理: **芯片静默回收(D14,2026-09-22 台架实证)**:对端发 FIN 后,芯片可能**自行把 Socket 回收回 LISTEN 且不发 DISCONNECT 中断** → 本层滞留 ESTABLISHED,应用继续往死连接上读写,新连接的字节被旧会话吞掉。
1. **等对账自愈(默认,推荐)**:§3.6 对账 ≤200ms 发现"本层 ESTABLISHED 而芯片已回收" → 按断开处理 → 自动重监听;等待期间该端口实际可接新连接(芯片侧已 LISTEN),新连接经对账补 accept 被本层感知。 **无需应用处理(N34 阶段1 起)**:对账⑤自动认领此形态(§3.6:≤400ms 去抖 + 1s 冷却)→ RESYNC 语义只修本层状态 + 取消本 fd 挂起 recv,**不发任何芯片命令**(发 close/重监听会摧毁芯片侧健康监听器 → 连接永拒,run8 实证 170s 拒绝)。等待期间该端口实际可接新连接(芯片侧已 LISTEN),新连接经对账补 accept 被本层感知。原显式 API `net_resync_listen(fd)` 已退役——"应用判定连接已死"这一步本身就是误判源(对活连接调用会把活连接标成监听态)。
2. **显式重同步(需立即对齐时)**:确认会话已死(如控制通道超时中止)后调 `net_resync_listen(fd)`——只修 MCU 侧状态 + 取消挂起 recv,**不发芯片命令**(发 close/重监听会摧毁芯片侧健康监听器 → 连接永拒)。
⚠ 切勿用**单次**芯片状态读决定重建:单次读可能落在"刚 open 未生效"瞬态,曾致监听反复重建直至 `relisten give up`(2026-09-23 实测风暴,三服务口全死)——任何重建动作必须去抖(连续确认)+ 冷却(§3.6)。 ⚠ 切勿用**单次**芯片状态读决定重建:单次读可能落在"刚 open 未生效"瞬态,曾致监听反复重建直至 `relisten give up`(2026-09-23 实测风暴,三服务口全死)——任何重建动作必须去抖(连续确认)+ 冷却(§3.6)。
@@ -1380,7 +1372,7 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
**问题:哪些操作要求 netTask 已运行?** **问题:哪些操作要求 netTask 已运行?**
`net_listen()` / `net_accept()` / `net_connect()` / `net_send()` / `net_recv()` / `net_close()` / `net_relisten()` / `net_resync_listen()` 全部经消息队列 → 只能在**netTask 已运行的上下文**(就绪屏障后的服务任务)调用;内核启动前的代码禁调任何 FreeRTOS/CMSIS-RTOS API,网络监听建立天然在内核启动后。`net_socket()` / `net_bind()` 为直接调用,但同样建议在屏障后。 `net_listen()` / `net_accept()` / `net_connect()` / `net_send()` / `net_recv()` / `net_close()` 全部经消息队列 → 只能在**netTask 已运行的上下文**(就绪屏障后的服务任务)调用;内核启动前的代码禁调任何 FreeRTOS/CMSIS-RTOS API,网络监听建立天然在内核启动后。`net_socket()` / `net_bind()` 为直接调用,但同样建议在屏障后。
**问题:消息队列由谁创建?在哪里创建?** **问题:消息队列由谁创建?在哪里创建?**
@@ -1403,7 +1395,7 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
| 场景 | 修复前症状 | 机制 | 生产验证(2026-09-23) | | 场景 | 修复前症状 | 机制 | 生产验证(2026-09-23) |
|---|---|---|---| |---|---|---|---|
| **Trap 22** 快速复连丢中断 → 监听静默死亡 | 待机后 UI `:7003` 快速复连 2/10 且**永久**拒连;FTP `:21` 100ms 间隔自第 29 轮死亡;设备日志**无报错**(状态静默滞留) | 周期对账 `net_reconcile_tick()`(200ms,①②)+ 监听器 pump `net_listener_pump()`(100ms,**唯一重建入口**,N-32)+ 去抖(连续 2 次)+ 冷却(1s);`handle_disconnect_event` 只置沉降事实、不再自行重建 | `:7003` 10/10;`:21` 100ms/0ms/600ms 各 50/50;UI 20/20;FTP 正常 10/10(SHA-256);中止 RST/FIN 5/5 + 5/5;零重建风暴 | | **Trap 22** 快速复连丢中断 → 监听静默死亡 | 待机后 UI `:7003` 快速复连 2/10 且**永久**拒连;FTP `:21` 100ms 间隔自第 29 轮死亡;设备日志**无报错**(状态静默滞留) | 周期对账 `net_reconcile_tick()`(200ms,①②⑤)+ 监听器 pump `net_listener_pump()`(100ms,**唯一重建入口**,N-32)+ 去抖(连续 2 次)+ 冷却(1s);`handle_disconnect_event` 只置沉降事实、不再自行重建 | `:7003` 10/10;`:21` 100ms/0ms/600ms 各 50/50;UI 20/20;FTP 正常 10/10(SHA-256);中止 RST/FIN 5/5 + 5/5;零重建风暴 |
| **Trap 23** 中止后首笔复连上传被静默截断 ≤2048B 并误报 226 成功 | RST/FIN 中止上传 → 紧接着的首笔上传"收"到 ~2KB 残留后判结束回 226 → 客户端以为成功 = **静默数据损坏**(第二笔起正常) | 异常释放(TIMEOUT)预重监听隔离:`net_drain_chip_rx()` 分块排空 RX 残渣 + 主动 close + 排空中断 + 等 CLOSED/TCP_CLOSED(400ms 起 / 800ms 上限 < 2s 时限) | RST 10/10、FIN 5/5 全通过;正常路径 30/30 + 20/20,SHA-256 全匹配 | | **Trap 23** 中止后首笔复连上传被静默截断 ≤2048B 并误报 226 成功 | RST/FIN 中止上传 → 紧接着的首笔上传"收"到 ~2KB 残留后判结束回 226 → 客户端以为成功 = **静默数据损坏**(第二笔起正常) | 异常释放(TIMEOUT)预重监听隔离:`net_drain_chip_rx()` 分块排空 RX 残渣 + 主动 close + 排空中断 + 等 CLOSED/TCP_CLOSED(400ms 起 / 800ms 上限 < 2s 时限) | RST 10/10、FIN 5/5 全通过;正常路径 30/30 + 20/20,SHA-256 全匹配 |
| **Trap 21**(TC-NET-604)"接收冻结"虚警 | 旧判读脚本把"对端填满 2KB RX 窗口"误判为 STALL/WEDGE | **结论:非缺陷**(芯片 RX 缓冲即窗口,对端填满是常态);判据改为脚本层 STEP + 客户端内容校验(RET SHA-256),虚警工具归档(`MDK-ARM/retracted_bench_20260922/`,README 禁重跑) | 真实 FTP 路径 227 轮全通过(8KB×10 … 1MB×2);并发 UI 流 600/600 CRC;设备零 FAIL/ERROR/WARN | | **Trap 21**(TC-NET-604)"接收冻结"虚警 | 旧判读脚本把"对端填满 2KB RX 窗口"误判为 STALL/WEDGE | **结论:非缺陷**(芯片 RX 缓冲即窗口,对端填满是常态);判据改为脚本层 STEP + 客户端内容校验(RET SHA-256),虚警工具归档(`MDK-ARM/retracted_bench_20260922/`,README 禁重跑) | 真实 FTP 路径 227 轮全通过(8KB×10 … 1MB×2);并发 UI 流 600/600 CRC;设备零 FAIL/ERROR/WARN |
@@ -1415,6 +1407,7 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
| 日期 | 变更 | | 日期 | 变更 |
|------|------| |------|------|
| 2026-10-13 | **N34 阶段1 API 面收敛**(审计见 `docs/网络/N34-自愈审计/README.md`):删除手动自愈 API `net_relisten()` / `net_resync_listen()`(生产零调用;后者语义并入对账**⑤**——本层 ESTABLISHED 而芯片 0x05/0x01 静默回收 → RESYNC 只修本层,§3.6 对账由两类扩为三类①②⑤;前者与 pump/D10 重叠且对健康监听器是 footgun)与 `net_can_accept()`(只认 ESTABLISHED 的旧门控挡死丢 CONNECT 探针,监听门控统一 `net_listener_armed()`,§10.1 门控节改写);`NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 消息类型删除;timeSync 会话收尾不再 `net_close`(同 fd 续 accept,监听跨会话常驻);lftpd 应用层阻塞重试退役(就绪屏障已消除 Trap 09 启动形态) |
| 2026-10-12 | **N-32 监听生命周期模块化 + 单一所有者**:监听生命周期抽为 `net_listener.c`(§2.2 文件表新增该行,真机专属);主循环自愈 tick 由 3 个增至 **4 个**(新增 `net_listener_pump`,100ms,**唯一重建入口**);`net_reconcile_tick` 对账收窄为 ①②,③④ 移交 pump;`handle_disconnect_event` / `handle_timeout_event` 改为 `net_listener_settle(GRACEFUL 100ms / QUARANTINE 400ms)` **只置事实**、不再自行重建;§3.3 / §3.6(四层自愈 → **五层**)/ §12.10 同步。权威设计见 `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10 | | 2026-10-12 | **N-32 监听生命周期模块化 + 单一所有者**:监听生命周期抽为 `net_listener.c`(§2.2 文件表新增该行,真机专属);主循环自愈 tick 由 3 个增至 **4 个**(新增 `net_listener_pump`,100ms,**唯一重建入口**);`net_reconcile_tick` 对账收窄为 ①②,③④ 移交 pump;`handle_disconnect_event` / `handle_timeout_event` 改为 `net_listener_settle(GRACEFUL 100ms / QUARANTINE 400ms)` **只置事实**、不再自行重建;§3.3 / §3.6(四层自愈 → **五层**)/ §12.10 同步。权威设计见 `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10 |
| 2026-10-10 | 新增 **`net_can_accept()` / `net_can_read()` 就绪门控 API**(§10.1):纯 RAM 读控制块、**不走消息队列、不等 netTask 回执**,供实时性敏感任务在发 RPC 前判断"有没有活"(动因 = `ui_net_sink_poll()` 每 10 ms 一次 `net_accept` 把 uiTask 拖成最坏 ≈50 ms;原拟登记 N-19 / C7,当日结案不入登记簿;设计与实测见 `docs/子系统设计/看门狗设计说明.md` §11(原分析文档已并入该节));§3.5 补"netTask 运行要求"例外条;⚠ 记录两条使用约束——**不可作为唯一判据**(`readable` 只由 RECV_OK 中断置位,须保留低频兜底 recv)与**模拟器后端恒返 1**(`net_socket_sim.c`,Winsock 无 RPC 成本) | | 2026-10-10 | 新增 **`net_can_accept()` / `net_can_read()` 就绪门控 API**(§10.1):纯 RAM 读控制块、**不走消息队列、不等 netTask 回执**,供实时性敏感任务在发 RPC 前判断"有没有活"(动因 = `ui_net_sink_poll()` 每 10 ms 一次 `net_accept` 把 uiTask 拖成最坏 ≈50 ms;原拟登记 N-19 / C7,当日结案不入登记簿;设计与实测见 `docs/子系统设计/看门狗设计说明.md` §11(原分析文档已并入该节));§3.5 补"netTask 运行要求"例外条;⚠ 记录两条使用约束——**不可作为唯一判据**(`readable` 只由 RECV_OK 中断置位,须保留低频兜底 recv)与**模拟器后端恒返 1**(`net_socket_sim.c`,Winsock 无 RPC 成本) |
| 2026-09-23 | 同步 L3 pending 化、D10 非阻塞重监听、Trap 22 对账、Trap 23 排渣隔离后的现状:新增 `net_recv_to` / `net_sock_state` / `net_resync_listen` / `net_relisten`(§10.1)及 `net_getsockname`;§3 新增 3.6(对账与自愈四机制)、3.3/3.5 改写(recv 挂起模型、队列语义、netTask 运行要求);§4/§5 示例更新(监听由就绪屏障后服务任务建立、`net_sock_state` 轮询);§10.2 改述 `net_process_messages()` 每轮节奏(重监听重试 → 挂起 recv 完成 → 200ms 对账 → 队列),补 `timeout_ms`/新消息类型说明;新增 §11.2 分片阻塞读(`net_recv_to`);§12.1/12.3/12.4/12.9 更新,新增 §12.10(自愈行为数据与排障判据,含 Trap 21 结案) | | 2026-09-23 | 同步 L3 pending 化、D10 非阻塞重监听、Trap 22 对账、Trap 23 排渣隔离后的现状:新增 `net_recv_to` / `net_sock_state` / `net_resync_listen` / `net_relisten`(§10.1)及 `net_getsockname`;§3 新增 3.6(对账与自愈四机制)、3.3/3.5 改写(recv 挂起模型、队列语义、netTask 运行要求);§4/§5 示例更新(监听由就绪屏障后服务任务建立、`net_sock_state` 轮询);§10.2 改述 `net_process_messages()` 每轮节奏(重监听重试 → 挂起 recv 完成 → 200ms 对账 → 队列),补 `timeout_ms`/新消息类型说明;新增 §11.2 分片阻塞读(`net_recv_to`);§12.1/12.3/12.4/12.9 更新,新增 §12.10(自愈行为数据与排障判据,含 Trap 21 结案) |
@@ -49,9 +49,9 @@ flowchart TB
NP["netPoll<br/>INT# GPIO 轮询 → 全局中断 → 各 Socket SINT → 每轮一批"] NP["netPoll<br/>INT# GPIO 轮询 → 全局中断 → 各 Socket SINT → 每轮一批"]
T1["① netListenerRetryTick<br/>D10 重监听重试:30ms / 2s 时限"] T1["① netListenerRetryTick<br/>D10 重监听重试:30ms / 2s 时限"]
T2["② netPendingTick<br/>L3 挂起 recv:①数据 ②到点 ③断连 ④看门狗 1s"] T2["② netPendingTick<br/>L3 挂起 recv:①数据 ②到点 ③断连 ④看门狗 1s"]
T3["③ netReconcileTick<br/>Trap 22 对账:200ms / 去抖 / 冷却(只剩①②)"] T3["③ netReconcileTick<br/>Trap 22 对账:200ms / 去抖 / 冷却(①②⑤)"]
T4["④ netListenerPump<br/>N-32 唯一重建入口:100ms / 去抖 / 冷却"] T4["④ netListenerPump<br/>N-32 唯一重建入口:100ms / 去抖 / 冷却"]
DISP["队列分派 switch<br/>SEND · RECV · CLOSE · CONNECT · LISTEN<br/>ACCEPT · RESYNC_LISTEN · RELISTEN"] DISP["队列分派 switch<br/>SEND · RECV · CLOSE<br/>CONNECT · LISTEN · ACCEPT"]
subgraph LK["_locked / 内部分派目标(仅 netTask 上下文)"] subgraph LK["_locked / 内部分派目标(仅 netTask 上下文)"]
LL["netListenLocked<br/>初次监听(3×500ms 阻塞重试)"] LL["netListenLocked<br/>初次监听(3×500ms 阻塞重试)"]
LN["netConnectLocked<br/>3×500ms 重试 + tcpConnect"] LN["netConnectLocked<br/>3×500ms 重试 + tcpConnect"]
@@ -60,7 +60,6 @@ flowchart TB
LA["netAcceptLocked<br/>先 netPoll + 状态判定"] LA["netAcceptLocked<br/>先 netPoll + 状态判定"]
LC["netCloseLocked<br/>了结挂起 + close + 等 CLOSED + 排空中断"] LC["netCloseLocked<br/>了结挂起 + close + 等 CLOSED + 排空中断"]
RLS["netListenerTry → netRelistenLocked<br/>单次尝试 + Trap 23 排渣隔离"] RLS["netListenerTry → netRelistenLocked<br/>单次尝试 + Trap 23 排渣隔离"]
RSY["RESYNC 分支:改状态 + 取消挂起<br/>(不发芯片命令)"]
end end
EV["事件处理器<br/>handleConnect · handleDisconnect<br/>handleRecv · handleSend · handleTimeout"] EV["事件处理器<br/>handleConnect · handleDisconnect<br/>handleRecv · handleSend · handleTimeout"]
ST["netListenerSettle<br/>只置沉降/隔离事实(100ms / 400ms)"] ST["netListenerSettle<br/>只置沉降/隔离事实(100ms / 400ms)"]
@@ -71,11 +70,12 @@ flowchart TB
PUB -->|入队| MQ PUB -->|入队| MQ
MQ --> DISP MQ --> DISP
DISP --> LL & LN & LS & LR & LA & LC & RLS & RSY DISP --> LL & LN & LS & LR & LA & LC & RLS
NPM --> T1 & T2 & T3 & T4 & NP NPM --> T1 & T2 & T3 & T4 & NP
T1 --> RLS T1 --> RLS
T3 -->|"① 已断"| EV T3 -->|"① 已断"| EV
T3 -->|"② 补 accept"| EV T3 -->|"② 补 accept"| EV
T3 -->|"⑤ 静默回收:RESYNC 只修本层 + 取消挂起(不发芯片命令,N34 阶段1 吸收原 net_resync_listen 语义)"| RSY["reconcile⑤ RESYNC 动作"]
T4 -->|"①② 芯片失位即重建"| RLS T4 -->|"①② 芯片失位即重建"| RLS
NP --> EV NP --> EV
EV --> FE EV --> FE
@@ -132,8 +132,8 @@ flowchart TB
| `net_recv` | `NET_MSG_RECV`(timeout=0→默认 30s) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / TIMEDOUT / CONNRESET) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞 → **登记挂起后立即返回**(L3,netTask 零占用);DONTWAIT + CLOSED → EOF + 触发重监听 | | `net_recv` | `NET_MSG_RECV`(timeout=0→默认 30s) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / TIMEDOUT / CONNRESET) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞 → **登记挂起后立即返回**(L3,netTask 零占用);DONTWAIT + CLOSED → EOF + 触发重监听 |
| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套:调用方把长等待切成有界片(如 5ms/剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(会被解释成默认 30s,恰是长阻塞);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) | | `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套:调用方把长等待切成有界片(如 5ms/剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(会被解释成默认 30s,恰是长阻塞);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) |
| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `close_socket` + 等 CLOSED(5ms 节奏 / ≤500ms)+ 排空 Socket/全局中断 | 0 / -1 | **先**了结本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);关后排空残留 DISCONNECT(Trap 14/17:否则下一连接被"刚建立就断开"秒断);控制块整体清零 | | `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `close_socket` + 等 CLOSED(5ms 节奏 / ≤500ms)+ 排空 Socket/全局中断 | 0 / -1 | **先**了结本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);关后排空残留 DISCONNECT(Trap 14/17:否则下一连接被"刚建立就断开"秒断);控制块整体清零 |
| `net_relisten` | `NET_MSG_RELISTEN` | `net_listener_try` → `net_relisten_locked` | `set_proto_type` + `set_sour_port` + `open_socket` + `tcp_listen`(单次尝试;沉降期另有 `close` + `get_socket_status` + 排空) | 0 / -1 | 无条件芯片侧重建:芯片侧已在监听 → 首次 open 失败 → close 重建(短暂中断 ~1s);失败**不阻塞**,登记 D10 重试;用途 = 代次变化后显式重建 / 判死后确定性重建;**-1 = "恢复中"**(重试轮询接管),不是永久失败 | | ~~`net_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **N34 阶段1 已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 |
| `net_resync_listen` | `NET_MSG_RESYNC_LISTEN` | 分派内直接处理(**无芯片命令**) | 无 | 0 / -1 | D14 静默回收场景:芯片已自行把 Socket 回收回监听且**不发 DISCONNECT 中断** → 本层滞留 ESTABLISHED。只修 MCU 侧(state → LISTENING + 取消该 fd 挂起 recv);**严禁**发芯片 close/重监听——会摧毁芯片侧健康监听器 → 连接永拒(run8 实证 170s 拒绝)。非 ESTABLISHED 时视为已一致,返回 0 | | ~~`net_resync_listen`~~ | ~~`NET_MSG_RESYNC_LISTEN`~~ | **N34 阶段1 已删除**(语义并入对账⑤) | — | — | D14 静默回收(芯片自行回监听、不发 DISCONNECT)现由 `net_reconcile_tick` 判据⑤自动认领:本层 ESTABLISHED 且芯片 0x05/0x01 → 只修本层(state→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),**不发芯片命令**(纪律原样保留,run8 实证 170s 拒绝);去抖/冷却沿用对账框架 |
### 4.4 直调 API(不经队列) ### 4.4 直调 API(不经队列)
@@ -193,7 +193,7 @@ flowchart TB
| ② | 本层 CLOSED(会话已结束)而芯片**不在** LISTEN | 重建(`net_listener_try`) | 原对账④ | | ② | 本层 CLOSED(会话已结束)而芯片**不在** LISTEN | 重建(`net_listener_try`) | 原对账④ |
| ③ | 本层 CLOSED 而芯片**正在** LISTEN | **只做状态重同步**(CLOSED→LISTENING),**不发任何芯片命令** | U11 缺口 | | ③ | 本层 CLOSED 而芯片**正在** LISTEN | **只做状态重同步**(CLOSED→LISTENING),**不发任何芯片命令** | U11 缺口 |
情形③的理由:芯片监听器是**健康**的,重下发 open+listen 会因 `USE` 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故);只把本层状态收回 LISTENING 即可(与 `net_resync_listen` 同一纪律)。 情形③的理由:芯片监听器是**健康**的,重下发 open+listen 会因 `USE` 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故);只把本层状态收回 LISTENING 即可(与对账⑤ RESYNC 同一纪律——原 `net_resync_listen`,N34 阶段1 已退役)。
**去抖 + 冷却**(沿用原对账③④ 实测结论,取值不变):同一失配需**连续 2 次**(≈200ms)才动手;同一 Socket 动手后**冷却 1s**(防抖成风暴,2026-09-23 实证)。**周期取 100ms**(对账 200ms 的 1/2):比它快、又不至于像 50ms 那样频繁 close/open 与消费者收发撞车(2026-10-12 台架实测 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms)。 **去抖 + 冷却**(沿用原对账③④ 实测结论,取值不变):同一失配需**连续 2 次**(≈200ms)才动手;同一 Socket 动手后**冷却 1s**(防抖成风暴,2026-09-23 实证)。**周期取 100ms**(对账 200ms 的 1/2):比它快、又不至于像 50ms 那样频繁 close/open 与消费者收发撞车(2026-10-12 台架实测 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms)。
+4 -17
View File
@@ -335,28 +335,15 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
return nidx; return nidx;
} }
/* /* N34 阶段1(F-NET-4):net_can_accept 双端退役——监听 accept 门控统一用
* 函数功能:查询监听 Socket 是否有可接受的连接(与 MCU 端同名 API) * net_listener_armed()(本文件同名实现保留恒 1 语义),recv 门控 net_can_read() 保留。 */
* 入口参数:sockfd - 监听 socket 描述符
* 返回值:1 - 可尝试 accept;0 - 描述符无效
* 限定条件:socket 已创建
* 函数说明:Winsock 后端无"零成本预知"手段(要嘛真调 accept、要嘛 select()),
* 而 accept 本就是非阻塞的、**没有 MCU 端的 RPC 成本** → 恒返回 1,
* 使上层门控在模拟器上等价于"改前行为"(逐位一致,不引入新语义)
*/
int net_can_accept(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
return 0;
}
return 1;
}
/* /*
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(与 MCU 端同名 API) * 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(与 MCU 端同名 API)
* 入口参数:sockfd - 监听 socket 描述符 * 入口参数:sockfd - 监听 socket 描述符
* 返回值:1 - 可尝试 accept;0 - 描述符无效 * 返回值:1 - 可尝试 accept;0 - 描述符无效
* 限定条件:socket 已创建 * 限定条件:socket 已创建
* 函数说明:同 net_can_accept——Winsock accept 本无 RPC 成本 → 恒返回 1, * 函数说明:Winsock accept 本无 RPC 成本 → 恒返回 1,
* 使上层门控在模拟器上等价于"改前行为"。 * 使上层门控在模拟器上等价于"改前行为"。
* ⚠ MCU 端该函数额外放行 CLOSED+auto_relisten(单连接复用的断连待重建态), * ⚠ MCU 端该函数额外放行 CLOSED+auto_relisten(单连接复用的断连待重建态),
* 那是芯片 fd 别名模型的产物;Winsock 后端 fd 与监听器分离 * 那是芯片 fd 别名模型的产物;Winsock 后端 fd 与监听器分离
@@ -374,7 +361,7 @@ int net_listener_armed(int sockfd) {
* 入口参数:sockfd - socket 描述符 * 入口参数:sockfd - socket 描述符
* 返回值:1 - 可尝试 recv;0 - 描述符无效 * 返回值:1 - 可尝试 recv;0 - 描述符无效
* 限定条件:socket 已创建 * 限定条件:socket 已创建
* 函数说明:同 net_can_accept——Winsock recv 非阻塞、无 RPC 成本 → 恒返回 1, * 函数说明:同 net_listener_armed 的双端口径——Winsock recv 非阻塞、无 RPC 成本 → 恒返回 1,
* 上层门控在模拟器上不改变既有行为(含"每轮都 recv"的自愈特性) * 上层门控在模拟器上不改变既有行为(含"每轮都 recv"的自愈特性)
*/ */
int net_can_read(int sockfd) { int net_can_read(int sockfd) {
+5 -44
View File
@@ -585,9 +585,6 @@ enum {
static uint8_t s_p13_state = P13_ST_HELLO; static uint8_t s_p13_state = P13_ST_HELLO;
static uint8_t s_p13_done = 0U; /* 会话完成/中止:后续调用空操作 */ static uint8_t s_p13_done = 0U; /* 会话完成/中止:后续调用空操作 */
static uint8_t s_p13_resync_done = 0U; /* abort 后首次重同步只执行一次 */
static uint8_t s_p13_clean_done = 0U; /* 会话干净完成(全段 OK + 已发 0xFF) */
static uint32_t s_p13_resync_tick = 0U; /* 最近一次(重)监听的时刻(周期自愈用) */
static uint8_t s_p13_hello_n = 0U; static uint8_t s_p13_hello_n = 0U;
static int s_p13_verdict_all = LAT_VERDICT_OK; static int s_p13_verdict_all = LAT_VERDICT_OK;
static int s_p13_seg_force_fail = 0; /* 当前段出现短写/错误 → 强制 FAIL */ static int s_p13_seg_force_fail = 0; /* 当前段出现短写/错误 → 强制 FAIL */
@@ -732,9 +729,6 @@ static void p13_reinit(void)
s_p13_seg_force_fail = 0; s_p13_seg_force_fail = 0;
s_p13_verdict_all = LAT_VERDICT_OK; s_p13_verdict_all = LAT_VERDICT_OK;
s_p13_done = 0U; s_p13_done = 0U;
s_p13_resync_done = 0U;
s_p13_clean_done = 0U;
s_p13_resync_tick = HAL_GetTick();
s_p13_hello_n = 0U; s_p13_hello_n = 0U;
lat_stat_reset(&s_p13_st); lat_stat_reset(&s_p13_st);
} }
@@ -763,43 +757,11 @@ void net_latency_phase13_run(int32_t sockfd)
if (s_p13_state != P13_ST_ABORT) { if (s_p13_state != P13_ST_ABORT) {
return; /* 正常完成/DONE:会话终态,不再处理 */ return; /* 正常完成/DONE:会话终态,不再处理 */
} }
/* 重同步(run7~run9 实证):peer 断连后芯片 Socket 去向不确定 /* N34 阶段1(F-NET-3):原"进 abort 分状态手动重同步 + 10s 周期 relisten"
* (静默回收回监听 / RST 关闭 / 监听器丢失)→ 进 abort 时做一次 * 已删——三种去向全部由 net 层自动机制认领(run8 静默回收滞留 ESTABLISHED
* 分状态重同步 + 等待期周期性自愈: * → 对账⑤ RESYNC;CLOSED 失位 → pump 100ms 认领重建;LISTENING 静默丢
* ESTABLISHED(陈旧)→ 先只修 MCU 侧状态(run8:close 会摧毁芯片 * 监听 → pump"芯片不在听"判据),手动 API net_resync_listen/net_relisten
* 侧健康监听器 → 连接永拒),再无条件芯片侧重监听(open+listen * 已退役。此处只保留收包与 peer re-HELLO 探测。 */
* 含 Trap 09 重试;run9:一次性 net_listen 落 Trap 09 窗口失败
* 且被忽略 → 监听器永久丢失);
* CLOSED → 芯片命令重新 LISTEN;
* LISTENING → 10s 无新连接则再重监听一次(自愈静默丢失的监听器;
* 重监听监听窗口中断 ~1s,peer 重连重试可吸收)。 */
if (s_p13_resync_done == 0U) {
int st = net_sock_state(sockfd);
s_p13_resync_done = 1U;
s_p13_resync_tick = HAL_GetTick();
if (st == NET_SOCK_STATE_ESTABLISHED) {
DBG_INFO("[LAT] note: stale ESTABLISHED on abort -> resync + relisten");
(void)net_resync_listen(sockfd);
(void)net_relisten(sockfd);
} else if (st == NET_SOCK_STATE_CLOSED) {
DBG_INFO("[LAT] note: CLOSED on abort -> re-listen via chip");
(void)net_relisten(sockfd);
}
} else if (s_p13_clean_done == 0U) {
/* 周期自愈:10s 无新连接(状态仍 LISTENING)→ 再重监听一次;
* 新连接到来时状态已转 ESTABLISHED,本分支自然不再触发。
* 干净完成(已发 0xFF、peer 正常退出)后不再自愈——否则每 10s
* 拆建一次监听器(无谓 SPI 开销 + 日志噪声 + 台架永不判空闲,
* run12 实证),此时候只需静候下一次 re-HELLO。 */
uint32_t now = HAL_GetTick();
if (net_sock_state(sockfd) == NET_SOCK_STATE_LISTENING &&
(uint32_t)(now - s_p13_resync_tick) >= 10000U) {
s_p13_resync_tick = now;
DBG_INFO("[LAT] note: no peer in 10s -> relisten (self-heal)");
(void)net_relisten(sockfd);
}
}
n = net_recv(sockfd, s_p13_rx, (int)sizeof(s_p13_rx), NET_MSG_DONTWAIT); n = net_recv(sockfd, s_p13_rx, (int)sizeof(s_p13_rx), NET_MSG_DONTWAIT);
if (n > 0) { if (n > 0) {
for (i = 0U; i + 4U <= (uint32_t)n; i++) { for (i = 0U; i + 4U <= (uint32_t)n; i++) {
@@ -1142,7 +1104,6 @@ void net_latency_phase13_run(int32_t sockfd)
"P13 latency baseline (verdict=%s)", p_name); "P13 latency baseline (verdict=%s)", p_name);
DBG_INFO("[LAT] report complete"); DBG_INFO("[LAT] report complete");
TEST_REPORT("Phase 13"); TEST_REPORT("Phase 13");
s_p13_clean_done = 1U; /* 已下发 0xFF:peer 正常收尾,停止周期自愈 */
s_p13_done = 1U; s_p13_done = 1U;
s_p13_state = P13_ST_ABORT; s_p13_state = P13_ST_ABORT;
break; break;
+4 -2
View File
@@ -54,10 +54,12 @@ def main():
with socket.create_connection((mcu_ip, port), timeout=5) as sock: with socket.create_connection((mcu_ip, port), timeout=5) as sock:
sock.sendall(pkt) sock.sendall(pkt)
# 关键:发送后保持连接打开,等板子读完再关。 # 关键:发送后保持连接打开,给板子留出读完 8 字节的窗口。
# 若立即 close,CH395F 收到 FIN 会丢弃接收缓冲里尚未被板子读出的 # 若立即 close,CH395F 收到 FIN 会丢弃接收缓冲里尚未被板子读出的
# 8 字节时间包,导致板子 net_recv 看到 CLOSED 而无数据、时间同步失败。 # 8 字节时间包,导致板子 net_recv 看到 CLOSED 而无数据、时间同步失败。
# 这里 recv 直到板子主动关闭(EOF)或 2s 超时,确保板子先读到数据。 # N34 阶段1-5 起板子会话收尾**不再 close**(单连接监听跨会话常驻,
# EOF 时 net_close 会连监听一起毁掉)——这里 recv 等 2s 宽限后由
# 本端关闭;板子 net 层 auto_relisten 随即恢复监听,下一轮照常连接。
sock.settimeout(2.0) sock.settimeout(2.0)
try: try:
sock.recv(64) sock.recv(64)