net/ui: UI 推流加就绪门控,刷新脱离 netTask 往返(销项 N-10)

新增 net_can_accept() / net_can_read():纯 RAM 读 Socket 控制块,不入队、
不等 netTask 回执,供实时性敏感任务在发 RPC 前判断"有没有活"。
ui_net_sink_poll() 改为先门控再 RPC;无客户端时 RPC 从 100 msg/s 降到 0。

两条兜底(门控引入的新卡死路径,设计阶段未预见):
- I-2 每 32 轮无条件 recv:readable 仅由 RECV_OK 中断置位,Trap 22 三类
  对账不含"RX 非空但 readable=0",纯门控会把已到达的数据永久藏住;
- I-3 门控返 0 时查监听控制块,消失即 g_listen=-1 重建(改前靠每轮
  accept 撞 BADF 暴露,门控会连这条暴露路径一起消掉)。
net_can_read() 对非 ESTABLISHED / 非法 fd 故意返 1,让上层必得 EOF/BADF
走既有错误分支——返 0 会把死连接永久藏住。
模拟器后端恒返 1(Winsock 无 RPC 成本,行为逐位不变)。

实测:uis 50ms→1µs、uiw 50ms→11.4ms(只剩 LVGL);@build 0 错 0 警、
@test 10/10 PASS(uis≤1µs、uisn≡0、0 task stall、0 [ERR])、
check_remo_sink.py e2e 0/25600 位差异、内容页文本断言 50/50。
uip 最坏 61ms 是 LVGL 帧周期地板、与网络无关,判据已按实测修订。

方案 B(把推流搬出 uiTask + 帧 seqlock)已评估并决定不做:搬任务不会
让刷新变快,收益只剩极端并发下的观感隔离,成本含必改 .ioc 的 CubeMX
重生成。残留暴露与观测手段(uis/uisn)如实记录在 §9.4。
N-19 / C7 不入登记簿(当日结案),结论沉淀进问题记录 §9。
This commit is contained in:
2026-10-10 20:47:59 +08:00
parent 35b5b78209
commit 1d1a9d4656
10 changed files with 382 additions and 11 deletions
+41
View File
@@ -150,6 +150,47 @@ int net_listen(int sockfd);
*/
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
/*
* 函数功能:查询监听 Socket 当前是否有可接受的连接(**纯 RAM 读,不发起 RPC**)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:无
* 返回值: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 是否"值得发一次 recv"(**纯 RAM 读,不发起 RPC**)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:1 - 应发 recv(有数据可读,或连接已非 ESTABLISHED 需了结);0 - 不必发 int
* 限定条件:net_init() 已调用;任意任务上下文
* 函数说明:1. ESTABLISHED → 看 readable(net_poll 在 RECV_OK 置位、读空清零)
* 2. **非 ESTABLISHED 一律返回 1**:连接已消失时必须让上层真的 recv 一次
* 拿到 EOF/错误并自行复位;若把它门掉,"连接已不存在"会被永久藏住
* (推流侧 g_client 永不复位 → 再也不接受新客户端)
* 3. 描述符失效(net_get_sock 返回 NULL)同样返回 1 → 让 recv 报 BADF,
* 走调用方既有的错误分支重建,而不是静默卡死
* 4. ⚠ **不可作为唯一判据**:readable 只由 RECV_OK 中断置位,而
* net_reconcile_tick 的三类对账**不含**"芯片 RX 非空但 readable=0"
* → 中断丢失时本函数会恒返 0。调用方须保留**低频兜底 recv**
* (ui_net_sink 每 32 轮无条件收一次,最坏延迟 ≈320 ms)
*/
int net_can_read(int sockfd);
/*
* 函数功能:TCP Client 发起连接
* 入口参数:sockfd - socket 描述符