diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 8a177571..e111007c 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -959,6 +959,9 @@ int net_socket(int domain, int type) { p_sock->readable = 0; /* 新 fd 不得继承上一条连接的释放原因(Trap 23 待办 #3) */ p_sock->released_abnormal = 0; + /* N-32(P1):新 fd 不得继承"上一条会话已结束"电平(否则新建即 EOF, + * 消费侧连数据都收不到)——与 released_abnormal 同理,显式清零 */ + p_sock->session_dead = 0; /* 新 fd 不得继承上一次的待重试状态(D10) */ net_relisten_clear(sockfd); /* 新 fd 不得继承上一次挂起的 recv 请求(L3) */ @@ -1453,6 +1456,49 @@ int net_can_accept(int sockfd) { return (p_sock->state == NET_SOCK_STATE_ESTABLISHED) ? 1 : 0; } +/* + * 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(纯 RAM 读,不发起 RPC) + * 入口参数:sockfd - 监听 Socket 描述符 int + * 出口参数:无 + * 返回值: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 → + * net_lost_connect_probe(丢 CONNECT 中断补 accept)对该端口从不运行 + * (设计文档 §2.3 末"探针失效") + * 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338) + * 只在 net_accept_locked 内部发生,门控挡在外面就永远到不了; + * LISTENING 态 accept 由探测补 accept 或返 WOULDBLOCK,均安全 + * 3. 为什么放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在 + * CLOSED 等 relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN + * 可能已被芯片接住(对账②)→ 放行让 net_accept_locked 的状态门 + * 自行裁决(含 RX 有数据的 drain_pending 接受) + * 4. 只读控制块,不入队、不等通知 → uiTask 不被 netTask 拖住 + * 5. 与 net_can_accept 语义差异是**有意的**:本函数表达"这个监听器 + * 还在服役",而非"这一轮一定有连接" + */ +int net_listener_armed(int sockfd) { + /* Socket 控制块(纯 RAM 读,可能为 NULL = 描述符越界或未分配) */ + const net_sock_t *p_sock = net_get_sock(sockfd); + + if (p_sock == NULL) { + return 0; + } + /* 正常监听态 */ + if (p_sock->state == NET_SOCK_STATE_LISTENING) { + return 1; + } + /* 断连待重建态:监听器仍在服役(auto_relisten + 已绑端口), + * 芯片侧交给 relisten / 对账恢复;同 net_reconcile_tick ④ 的认领口径 */ + if ((p_sock->state == NET_SOCK_STATE_CLOSED) && + (p_sock->auto_relisten != 0U) && + (p_sock->local_port != 0U)) { + return 1; + } + return 0; +} + /* * 函数功能:查询 Socket 是否"值得发一次 recv"(纯 RAM 读,不发起 RPC) * 入口参数:sockfd - Socket 描述符 int @@ -1682,6 +1728,10 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a if ((status != CH395F_ERR_SUCCESS) && (status != CH395F_ERR_BUSY)) { DBG_ERROR("[CONN] sock%d TCP_CONNECT rejected by chip: 0x%02X", sockfd, status); (void)ch395f_close_socket((uint8_t)sockfd); + /* N-32(P1 不变量 I):命令级失败同样是"本会话已结束"——此前这里只置 + * CLOSED,而 CONNECTING 态从未置 session_dead,挂起的 recv 只能干等 + * deadline(B2 审计发现的"停在 CONNECTING 干等 30s 超时才暴露"同源) */ + p_sock->session_dead = 1U; p_sock->state = NET_SOCK_STATE_CLOSED; return NET_ERR_RETURN_1(NET_ERR_INVAL); } @@ -1876,10 +1926,20 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, if (flags & NET_MSG_DONTWAIT) { /* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听, * 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */ - /* 连接已断开(CLOSED):按 EOF 处理 */ - if (p_sock->state == NET_SOCK_STATE_CLOSED) { - /* 启用自动重监听:先把监听器重新置回 LISTEN */ - if (p_sock->auto_relisten) { + /* 连接已断开(CLOSED)**或**本会话已结束(session_dead):按 EOF 处理。 + * 判据从纯 `state == CLOSED` 放宽为 `|| session_dead`(N-32 P1 不变量 I): + * 抢先 relisten 后 state 已是 LISTENING 而会话确实已结束,旧判据在此 + * 恒返 WOULDBLOCK → EOF 永久失踪(新连接被旧会话静默吸收)。 + * 有数据时走上方快路径(recv_len>0 优先),本判据只在"无数据"时生效, + * 故不会把新连接的起始数据误判为 EOF。 */ + if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) { + /* 启用自动重监听:先把监听器重新置回 LISTEN。 + * ⚠ 内层 `state == CLOSED` 门是**必需的**,不是冗余: + * `net_relisten_try` 无状态门,对已 LISTENING 的 fd 再 open+listen + * 会让 ch395f_open_socket 返 USE,其失败分支会 ch395f_close_socket + * —— 把正听着新 SYN 的 Socket 当场关掉(U12 同源事故)。 + * session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。 */ + if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) { /* 非阻塞:失败由 D10 重试轮询接管 */ (void)net_relisten_try(p_sock); } @@ -2073,6 +2133,11 @@ static int net_relisten_locked(net_sock_t *p_sock) { /* 状态置回 LISTENING(fd 保留) */ 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)清零。 */ /* 新监听不得继承上一条连接的释放原因(Trap 23 待办 #3) */ p_sock->released_abnormal = 0U; /* N-30(Trap 25):清零前把对端端口转登为幽灵甄别基准 */ @@ -2428,8 +2493,12 @@ static void net_pending_tick(void) { continue; } - /* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听 */ - if (p_sock->state == NET_SOCK_STATE_CLOSED) { + /* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。 + * 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED || + * session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED) + * 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时 + * (lftpd 阻塞 recv 的"消费者卡死"原型)。 */ + if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) { /* 查询残留字节数 */ int rem = ch395f_get_recv_len((uint8_t)i); /* 有残留数据:按数据完成 */ @@ -2445,8 +2514,12 @@ static void net_pending_tick(void) { /* 了结:以字节数通知 */ net_pending_complete(i, rd); } else { - /* 启用自动重监听:先把监听器重新置回 LISTEN */ - if (p_sock->auto_relisten != 0U) { + /* 启用自动重监听:先把监听器重新置回 LISTEN。 + * ⚠ 内层 `state == CLOSED` 门同 net_recv_locked:session_dead + * 但已回 LISTENING 时重建已由别处完成,再 open+listen 会因 + * USE 错误走进关闭分支、关掉正听着的 Socket(U12 同源) */ + if ((p_sock->state == NET_SOCK_STATE_CLOSED) && + (p_sock->auto_relisten != 0U)) { /* 非阻塞:失败由 D10 重试轮询接管 */ (void)net_relisten_try(p_sock); } @@ -3575,6 +3648,10 @@ static void handle_connect_event(net_sock_t *p_sock) { p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); /* 状态 → ESTABLISHED(监听自身转数据通道) */ p_sock->state = NET_SOCK_STATE_ESTABLISHED; + /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置 + * ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内 + * recv 仍会误得 EOF(把新客户端踢开) */ + p_sock->session_dead = 0U; /* 新开 Socket 发送缓冲按空闲计 */ p_sock->send_ready = 1; @@ -3609,6 +3686,9 @@ static void handle_connect_event(net_sock_t *p_sock) { p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); /* 状态 → ESTABLISHED */ p_sock->state = NET_SOCK_STATE_ESTABLISHED; + /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING + * 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */ + p_sock->session_dead = 0U; /* 使能 KeepAlive(客户端连接保活) */ ch395f_set_keepalive_enable(p_sock->ch395_sock, 1); @@ -3695,6 +3775,11 @@ static void handle_disconnect_event(net_sock_t *p_sock) { } /* 正常释放(对端 FIN):断连了结回 EOF(0) */ p_sock->released_abnormal = 0U; + /* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**, + * 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间 + * recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据 + * 落到本标志 → 仍得 EOF,不因排空慢而漏报 */ + p_sock->session_dead = 1U; /* N-30(Trap 25):登记本连接对端端口(幽灵态甄别基准,须在状态 * 迁移前记录——remote_port 此刻仍有效,relisten 清零) */ p_sock->last_peer_port = p_sock->remote_port; @@ -3787,6 +3872,10 @@ static void handle_timeout_event(net_sock_t *p_sock) { p_sock->released_abnormal = 1U; /* N-30(Trap 25):异常释放同样登记对端端口(幽灵甄别基准) */ p_sock->last_peer_port = p_sock->remote_port; + /* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST + * 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与 + * released_abnormal(结束得好不好看)正交) */ + p_sock->session_dead = 1U; /* 状态 → CLOSED */ p_sock->state = NET_SOCK_STATE_CLOSED; } else if (p_sock->state == NET_SOCK_STATE_LISTENING) { diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h index b9ca27a2..df88d1b3 100644 --- a/Drivers/BSP/NET/net_socket.h +++ b/Drivers/BSP/NET/net_socket.h @@ -172,6 +172,28 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen); */ int net_can_accept(int sockfd); +/* + * 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(**纯 RAM 读,不发起 RPC**) + * 入口参数:sockfd - 监听 Socket 描述符 int + * 出口参数:无 + * 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);0 - 不就绪 int + * 限定条件:net_init() 已调用;任意任务上下文 + * 函数说明:1. N-32(P1 不变量 II):取代 `net_can_accept()`(只认 ESTABLISHED)作为 + * ui_net_sink 的 accept 门控——原门控使 :7003 永不进 `net_accept()`, + * `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的 + * "数据被吞/425"主根因修复)对该端口等于从未运行 + * 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在 + * `net_accept_locked` **内部**,门控挡在外面就永远到不了;LISTENING 态 + * accept 要么被探针补中、要么返 WOULDBLOCK,两者均安全 + * 3. 放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在 CLOSED 等 + * relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN 可能已被芯片 + * 接住 → 放行让 `net_accept_locked` 的状态门自行裁决 + * 4. 与 `net_can_accept` 的语义差异**有意为之**:本函数回答"这个监听器 + * 还在服役吗",而非"这一轮一定有连接" + * 5. 跨任务读单字,同 `net_can_accept` 的最坏情形论证 + */ +int net_listener_armed(int sockfd); + /* * 函数功能:查询 Socket 是否"值得发一次 recv"(**纯 RAM 读,不发起 RPC**) * 入口参数:sockfd - Socket 描述符 int diff --git a/Drivers/BSP/NET/net_types.h b/Drivers/BSP/NET/net_types.h index 4cf2f7a9..3a218ed1 100644 --- a/Drivers/BSP/NET/net_types.h +++ b/Drivers/BSP/NET/net_types.h @@ -189,6 +189,22 @@ typedef struct { * 节拍 = accept 轮询 100ms;新监听/补 accept 后清零) */ uint8_t lost_conn_seen; + /* N-32 / B10 根治 P1(2026-10-12,设计文档 §2.3 不变量 I):本会话"已结束"的 + * **电平**(粘滞),取代 `state == CLOSED` 这一**边沿**。 + * 为什么必须电平化:单连接复用模型下同一个 fd 既是监听器又是连接(handle + * aliasing),"关掉一条连接"与"毁掉一个监听器"在类型上无法区分;而"会话结束" + * 原先只能靠 `state == CLOSED` 这一刻被观察到——一旦别的路径抢先 relisten + * 回 LISTENING(对账④/`net_accept_locked` 的 CLOSED 分支),消费者再 recv + * 就恒得 WOULDBLOCK,EOF 永远失踪(B10"消费者卡死"与新连接被旧会话静默吸收)。 + * session_dead 让"会话已结束"可重复查询、幂等:谁先看到不重要,谁都饿不着。 + * 置位:handle_disconnect_event 释放分支 / handle_timeout_event ESTABLISHED + * 分支 / net_connect_locked 命令被拒 / 对账①(复用前者,:654)。 + * 清零:新 CONNECT 成功(handle_connect_event 两支)、net_close/net_socket + * (memset 整块清零)、net_relisten_locked 的 peer 簿记**不清**—— + * 这是关键:重建监听与"应用是否已确认会话结束"必须解耦(design §5 P1.1)。 + * 跨 fd 保留语义同 released_abnormal(幽灵/陈旧事件属芯片 Socket 级)。 */ + uint8_t session_dead; + /* 统计信息 */ uint32_t recv_bytes; /* 累计接收字节数 */ uint32_t send_bytes; /* 累计发送字节数 */ diff --git a/Drivers/BSP/UI/ui_net_sink.c b/Drivers/BSP/UI/ui_net_sink.c index 74d22bcb..ac59a58b 100644 --- a/Drivers/BSP/UI/ui_net_sink.c +++ b/Drivers/BSP/UI/ui_net_sink.c @@ -23,6 +23,13 @@ * "RX 非空但 readable=0",故每 32 轮无条件 recv 一次(≈320 ms); * 门控放行时若监听控制块已消失则置 g_listen=-1 触发重建, * 避免"门控把端口永久不再接受新客户端"这一新卡死路径 + * 2026-10-12 王建锋 N-32(B10 根治 P1,设计文档 §2.3 不变量 II):accept 门控 + * 由 net_can_accept()(只认 ESTABLISHED)改判 + * net_listener_armed()(LISTENING 或 CLOSED+auto_relisten)—— + * 旧门控在断连待重建窗口恒 0,:7003 永不进 net_accept, + * net_lost_connect_probe 对该端口等于从未运行;同时 EOF / + * 真错误两条路径不再自行 bind_listen(重建交回 net 层, + * 消灭第三/第四个重建所有者),仅 fd 真消失时置 -1 */ #include "ui_net_sink.h" @@ -469,11 +476,15 @@ int ui_net_sink_start(uint16_t port) { * 限定条件:ui_net_sink_start() 已成功调用 * 函数说明: 1. 网络代次变化(重建)时关闭旧监听/客户端并重建 * 2. 无监听(启动失败)时重试启动 - * 3. 无客户端时**先门控**(net_can_accept,纯 RAM 读)再 accept,成功则复位状态机 + * 3. 无客户端时**先门控**(net_listener_armed,纯 RAM 读)再 accept,成功则复位状态机。 + * 门控判据 = "监听器还在服役"(LISTENING 或 CLOSED+auto_relisten)而非 + * "本轮必然有连接"——后者会让断连待重建窗口内永不进 net_accept, + * 把丢 CONNECT 中断的补 accept 探测永久挡在门外(N-32) * 4. 有客户端时**先门控**(net_can_read)再非阻塞 recv 逐字节喂入状态机; * 门控判为"无数据"时仍每 RM_LIVENESS_DIV 轮无条件收一次(兜底) - * 5. 对端关闭/真错误时关闭客户端;若监听 Socket 随客户端失效 - * (MCU 单连接模型)则重建监听,等待重连 + * 5. 对端关闭/真错误时关闭客户端。**不再自行重建监听**(N-32 不变量 II): + * recv 的 EOF/错误分支已触发 net 层 auto_relisten,另有对账④兜底; + * 仅 g_listen 的 fd 真消失(net_get_sock 返 NULL)才置 -1 交回 2 重启 * ⚠ **门控只消除"无谓的往返",不消除"往返本身无上界"**:一旦真有活要干 * (accept / recv / 3204 B 的 LCDMEM 应答),仍是"入队 + 等 netTask 回执", * 最坏 ≈4 s。宿主 = uiTask 主循环;"搬出 uiTask"已完整评估并**决定不做** @@ -507,10 +518,15 @@ void ui_net_sink_poll(void) { return; } - /* 无客户端时尝试接受连接(I-1 门控:无连接可接受 → 0 次 RPC) */ + /* 无客户端时尝试接受连接(N-32 P1 不变量 II:门控从 net_can_accept〔只认 + * ESTABLISHED〕换成 net_listener_armed〔LISTENING 或 CLOSED+auto_relisten〕) + * 原因:MCU 单连接模型下断连后 fd 会停在 CLOSED 等 relisten,旧门控在该窗口 + * 内恒 0 → :7003 永不进 net_accept → net_lost_connect_probe 对该端口等于 + * 从未运行(丢 CONNECT 中断无第二信源);armed 放行后由 net_accept_locked + * 的状态门自行裁决(含 RX 有数据的 closed-but-readable 接受) */ if (g_client < 0) { - if (net_can_accept(g_listen) == 0) { - /* 监听控制块已消失(网络重建 / 异常释放)→ 置 -1 交回上方 + if (net_listener_armed(g_listen) == 0) { + /* 监听器已不在服役(fd 消失 / 非监听型)→ 置 -1 交回上方 * g_listen < 0 分支重建监听。改前靠"每轮都 accept"撞出 BADF 来暴露, * 加了门控就必须显式兜这一条,否则端口永久不再接受新客户端 */ if (net_get_sock(g_listen) == NULL) { @@ -547,24 +563,40 @@ void ui_net_sink_poll(void) { } } else if (n == 0) { /* 对端关闭:单连接模型下监听通道与数据通道是同一个 Socket,net 层已在 - * net_recv_locked 的 CLOSED 分支自动把该 Socket 放回 LISTEN;此处绝不能再 + * net_recv_locked 的 EOF 分支自动把该 Socket 放回 LISTEN;此处绝不能再 * net_close 它,否则监听一并被毁(后续连接被拒),且重建监听受 Trap 09 时序限制 */ if (g_client != g_listen) { net_close(g_client); /* 多连接后端(PC 模拟器):数据通道独立,需显式关闭 */ } g_client = -1; rm_parser_reset(); - if (net_get_sock(g_listen) == NULL && bind_listen() < 0) { - g_listen = -1; /* 重建失败:交回主循环重试,避免拿失效 fd 反复 accept */ + /* N-32(P1 不变量 II):重建**不再**由本处自行 bind_listen。 + * ① net_recv_locked 的 EOF 分支已触发 auto_relisten(D10 非阻塞重试); + * ② 对账④还会兜底补一次 relisten(Trap 22 实证事件会丢,两层不可互替)。 + * 本处再做一遍 = 第三个重建所有者,正是 §2.3 那 12 个所有者的同类。 + * 仅当 fd 真消失(net_get_sock 返 NULL = 网络重建)才置 -1,交回上方 + * 分支重新走 start → bind_listen */ + if (net_get_sock(g_listen) == NULL) { + g_listen = -1; /* fd 真消失:交回主循环重试 */ } } else { /* n < 0:区分"暂无数据"(继续等待)与"真错误"(断开并视情况重建监听) */ if (net_get_errno() != NET_ERR_WOULDBLOCK) { - net_close(g_client); + /* N-32(P1):加 `g_client != g_listen` 护栏,与上面 n==0 分支 + * 的 :552 一致——MCU 单连接模型下 g_client 可能就是 g_listen 本身, + * 无护栏的 net_close 会直接关掉监听 fd(:563,与 :134 注释"严禁" + * 自相矛盾),端口随 Trap 09 沉降窗短死。PC 模拟器多连接后端下 + * 两者恒不等,护栏不改变其行为 */ + if (g_client != g_listen) { + net_close(g_client); /* 多连接后端(PC 模拟器):数据通道独立,需显式关闭 */ + } g_client = -1; rm_parser_reset(); - if (net_get_sock(g_listen) == NULL && bind_listen() < 0) { - g_listen = -1; /* 重建失败:交回主循环重试 */ + /* N-32(P1 不变量 II):同 EOF 分支——不再自行 bind_listen, + * 重建交回 net 层(recv 的错误分支同样会走 relisten)。仅 fd 真消失 + * 时置 -1 交回主循环走 start */ + if (net_get_sock(g_listen) == NULL) { + g_listen = -1; /* fd 真消失:交回主循环重试 */ } } } diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 7e392177..b6e39fb1 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 214,088 B (209.07 KB) | 1,048,576 B (1024 KB) | 20.4% | +| ER_IROM1 | 214,100 B (209.08 KB) | 1,048,576 B (1024 KB) | 20.4% | | RW_IRAM1 | 94,768 B (92.55 KB) | 131,072 B (128 KB) | 72.3% | | RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% | -| **Flash (ROM 合计)** | **215,056 B (210.0 KB)** | 1,048,576 B (1024 KB) | **20.5%** | +| **Flash (ROM 合计)** | **215,068 B (210.0 KB)** | 1,048,576 B (1024 KB) | **20.5%** | 各区的容量与常量来源: diff --git a/docs/子系统设计/网络Socket监听生命周期设计说明.md b/docs/子系统设计/网络Socket监听生命周期设计说明.md index 41f33c51..6327a5f9 100644 --- a/docs/子系统设计/网络Socket监听生命周期设计说明.md +++ b/docs/子系统设计/网络Socket监听生命周期设计说明.md @@ -260,14 +260,23 @@ DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-56 - [ ] 在 B10 行确认已记「④ 扩大 EOF 吞没窗口」(已记,执行时复核) **P1(边沿→电平)** -- [ ] `net_types.h` 的 `net_sock_t` 增 `uint8_t session_dead;` -- [ ] 置位:`handle_disconnect_event` 释放分支、`handle_timeout_event` ESTABLISHED 分支、对账①(复用前者)、`net_connect_locked:1685` -- [ ] 清零:`handle_connect_event`(新 CONNECT)、`net_close`/`net_socket` 的 memset;**`net_relisten_locked` 不清**(关键) -- [ ] `net_recv_locked` DONTWAIT 判据改 `state==CLOSED || session_dead` -- [ ] `ui_net_sink.c:563` 加 `g_client != g_listen` 护栏;EOF/错误后不再自行 `bind_listen` -- [ ] 新增 `net_listener_armed(fd)`(LISTENING 或 CLOSED+auto_relisten),替换 `ui_net_sink.c:512` 的 `net_can_accept` 门控 +- [x] `net_types.h` 的 `net_sock_t` 增 `uint8_t session_dead;` +- [x] 置位:`handle_disconnect_event` 释放分支、`handle_timeout_event` ESTABLISHED 分支、对账①(复用前者)、`net_connect_locked:1685` +- [x] 清零:`handle_connect_event`(新 CONNECT,两支)、`net_close`/`net_socket` 的 memset 与显式清零;**`net_relisten_locked` 不清**(关键,已加注释钉住契约) +- [x] `net_recv_locked` DONTWAIT 判据改 `state==CLOSED || session_dead` +- [x] `ui_net_sink.c:563` 加 `g_client != g_listen` 护栏;EOF/错误后不再自行 `bind_listen` +- [x] 新增 `net_listener_armed(fd)`(LISTENING 或 CLOSED+auto_relisten),替换 `ui_net_sink.c:512` 的 `net_can_accept` 门控(MCU + `net_socket_sim.c` 双端实现) +- [x] **超出清单的必要扩展(设计时未预见)**:`net_pending_tick` ③ 断连判据同步改 `CLOSED || session_dead`。不改则 lftpd 的**阻塞** recv 仍会被抢先 relisten 饿死——①要求 ESTABLISHED、③要求 CLOSED,抢先 relisten 后两头落空,只能干等 deadline 超时。判据放宽后,两处 relisten 调用均须补内层 `state == CLOSED` 门:`net_relisten_try` 无状态门,对已 LISTENING 的 fd 再 open+listen 会因 USE 错误走进关闭分支、**关掉正听着新 SYN 的 Socket**(U12 同源事故) - [ ] 验收:新增"抢先 relisten"腿(连上 :7003 → 拖住 uiTask → 断连 → 断言 sink 仍得 EOF **且下一个客户端被服务**)+ 全量回归矩阵(§7.1 那一套) -- [ ] `@build` 0/0 → `@test` PASS → 回读校验 → 停下等提交指令 +- [ ] `@test` PASS → 回读校验 → 停下等提交指令 + +**P1 落成状态(2026-10-12)**:代码已实现、`@build` 0 错 0 警(`Code=197096`,较基线 +12 B)、 +`mem-numbers` 快照已 `--write` 刷新、除下述基线问题外静态门禁全绿。 +⚠ **已知基线问题(非本改动引入)**:`doc-links` 在**任何从当前 `v2-on-ui` 切出的工作树**上都会报 +2 处失效引用——`RAM使用分析与优化方案.md:277/:585` → `scripts/ram_baseline.tsv`、 +`docs/内存管理/RAM预算台账.md`。这两个文件属另一在途任务、尚未提交,主工作树磁盘上有故主线 +门禁呈绿(见 `docs/环境与部署/并行任务工作树方案.md` §10.2)。待该任务合入后自动消解, +**不应**由本分支代为提交他人半成品。 **P2(单一所有者)** - [ ] 新增 `net_listener_pump()`,与 `net_reconcile_tick` 同节拍,成为**唯一**调 `net_relisten_try` 的监听重建入口 diff --git a/simulator/sim/net_socket_sim.c b/simulator/sim/net_socket_sim.c index 8c339e22..47ce668f 100644 --- a/simulator/sim/net_socket_sim.c +++ b/simulator/sim/net_socket_sim.c @@ -351,6 +351,24 @@ int net_can_accept(int sockfd) { return 1; } +/* + * 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(与 MCU 端同名 API) + * 入口参数:sockfd - 监听 socket 描述符 + * 返回值:1 - 可尝试 accept;0 - 描述符无效 + * 限定条件:socket 已创建 + * 函数说明:同 net_can_accept——Winsock accept 本无 RPC 成本 → 恒返回 1, + * 使上层门控在模拟器上等价于"改前行为"。 + * ⚠ MCU 端该函数额外放行 CLOSED+auto_relisten(单连接复用的断连待重建态), + * 那是芯片 fd 别名模型的产物;Winsock 后端 fd 与监听器分离 + * (设计文档 D6:双模型分裂暂不消除),无此中间态,恒 1 即为正确语义。 + */ +int net_listener_armed(int sockfd) { + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) { + return 0; + } + return 1; +} + /* * 函数功能:查询 Socket 是否"值得发一次 recv"(与 MCU 端同名 API) * 入口参数:sockfd - socket 描述符