feat(NET): N34 阶段 1.5 recv 语义主流化——无限默认+具名非阻塞+模式属性
核心语义(实施方案 §3.5): - net_recv 默认无限阻塞:删除 NET_RECV_TIMEOUT_MS(30s 隐藏默认预算), 超时归调用方策略;唤醒源=数据/EOF/close/网络重建(CONNRESET), 会话死亡由对账① ~400ms 判死兜底,无"对端消失永久挂死"窗口 - 新增 net_recv_nb(NET_MSG_DONTWAIT 路径转正具名)与 net_setblocking(fd,on)(阻塞模式挂控制块属性,对齐 O_NONBLOCK/ Python setblocking;直写单字节,net_set_event_cb 先例) - net_types.h net_sock_t 新增 blocking 属性;net_socket() 默认置 1 (close memset 清零后显式重置,同 released_abnormal 纪律) - 原 1s 强制了结看门狗降级为 60s tripwire:只 [ERR] 留痕一次, 绝不伪造 TIMEDOUT 返回值(旧实现掩盖完成路径缺陷) - net_recvfrom 删 30s 自旋超时、同判据对齐(零生产调用者,注释警示) - 文档契约:IWDG required_mask 内任务禁止无限阻塞 recv(BSD 指南 §3.5/§10.1 + net_socket.h 注释) 调用点审计(§3.5.5):零生产调用点采用无限默认——iec104_serv/ ui_net_sink 转 net_recv_nb(受监控任务逐位等价);lftpd/time_sync 已是显式预算 net_recv_to 不动;32 轮盲收兜底未动(阶段 3 E6 门禁) 模拟器双端镜像 net_recv_nb/net_recv_to/net_setblocking(包装层 Sleep(1) 轮询模拟阻塞);差距清单 N-3/N-6 登记义务已履行 门禁与台架: - @build 0/0:Code 209580→209780(+200B) ZI +32B;RAM 受管区官方 脚本同步;ci_local L0 32/32 + L0.5 951/951 - 台架全回归:relisten :7003 50/50、iec104 18/18、对时正常、 ftp_regression 11/11、Trap23 RST 5/5+FIN 5/5、ftp_integrity 20/20 字节一致、n30 0/10、@test PASS - 模拟器 build_sim OK + check_remo_sink 黄金参考 0/25600 位差 队友 p15-recv-semantics 实施,Lead 全量审阅 + 独立复跑门禁与回归
This commit is contained in:
@@ -258,7 +258,10 @@ static void s_serv_poll(void) {
|
||||
s_liveness_ctr = 0u;
|
||||
}
|
||||
|
||||
n = net_recv(s_client, s_rx, (int)sizeof(s_rx), NET_MSG_DONTWAIT);
|
||||
/* N34 阶段 1.5:DONTWAIT 转正为具名 API net_recv_nb——本任务在 IWDG
|
||||
* required_mask 内且有 100ms 周期职责(喂狗/链路节拍/上送),禁止无限
|
||||
* 阻塞 recv(文档契约见 net_socket.h net_recv 注释),非阻塞轮询是义务 */
|
||||
n = net_recv_nb(s_client, s_rx, (int)sizeof(s_rx));
|
||||
if (n > 0) {
|
||||
iec104_link_on_rx(&s_link, s_rx, (uint16_t)n, now);
|
||||
} else if (n == 0) {
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
* 实际生效值 60s/5s/3 并在 net_socket.c 引用(原宏为无效遗留值)
|
||||
* 2026-09-22 新增 NET_RETRANS_COUNT/NET_RETRANS_PERIOD_MS(P0-a:重传周期
|
||||
* 500→200ms 治 D1 症状),net_socket.c 的字面量 8/500 移入本文件
|
||||
* 2026-10-13 N34 阶段 1.5:删除 NET_RECV_TIMEOUT_MS(30s 默认预算)——
|
||||
* recv 超时改为调用方策略(net_recv 无限阻塞 /
|
||||
* net_recv_to 显式预算 / net_select 首选),API 不再强加默认值
|
||||
*/
|
||||
|
||||
#ifndef NET_CONFIG_H
|
||||
@@ -30,15 +33,14 @@ extern "C" {
|
||||
/*
|
||||
* 超时时间配置(毫秒)
|
||||
* 0 = 无限等待(阻塞模式)
|
||||
* 注:recv 侧无编译期默认预算(N34 阶段 1.5 删除 NET_RECV_TIMEOUT_MS)——
|
||||
* 超时是调用方策略:net_recv() 无限阻塞、net_recv_to() 显式预算、
|
||||
* net_select() 首选机制。
|
||||
*/
|
||||
#ifndef NET_SEND_TIMEOUT_MS
|
||||
#define NET_SEND_TIMEOUT_MS 1000 /* 发送超时 */
|
||||
#endif
|
||||
|
||||
#ifndef NET_RECV_TIMEOUT_MS
|
||||
#define NET_RECV_TIMEOUT_MS 30000 /* 接收超时(30秒,0=无限等待) */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* TCP 重传配置(由 net_socket.c 的 net_init() 在 ch395f_init() 之前下发)
|
||||
* P0-a(2026-09-22):周期 500→200ms——单次丢包代价 500→200ms(治 D1 症状),
|
||||
|
||||
+161
-60
@@ -174,19 +174,23 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
|
||||
/* L3 pending 化(一期,E-02 结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成
|
||||
* "无数据 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回
|
||||
* net_pending_tick(与 D10 重监听重试同节奏,net_process_messages 开头每轮调用)
|
||||
* 按 ①数据 ②到点 ③断连 顺序检查完成条件并通知调用方(语义逐项对齐旧自旋循环)
|
||||
* 按 ①数据 ②到点(仅显式预算) ③断连 顺序检查完成条件并通知调用方(语义逐项对齐旧自旋循环)
|
||||
* 关闭/网络重建/陈旧重同步走显式 settle/cancel 路径(取消必通知,调用方永不挂死)
|
||||
* 公开 API 返回契约不变:>0 字节数 / 0 EOF / -1 错误(错误码经通知值回传)
|
||||
* N34 阶段 1.5:预算归调用方——timeout_ms=0 即无限等待(30s 默认预算已删),
|
||||
* 原"到点 +1s 强制了结"的看门狗降级为 tripwire(只留痕、绝不伪造返回值)
|
||||
*/
|
||||
#define NET_PENDING_REGISTERED 0x70000000 /* 内部哨兵:pending 已登记(分派跳过即时通知;取值不与字节数空间冲突) */
|
||||
#define NET_PENDING_WATCHDOG_MS 1000U /* 看门狗:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + 错误日志 */
|
||||
#define NET_RECV_TRIPWIRE_MS 60000U /* tripwire:挂起超 60s = 完成逻辑缺位探针 → 只 [ERR] 留痕一次,绝不强制了结 */
|
||||
|
||||
typedef struct {
|
||||
uint8_t active; /* 1 = 本 fd 有挂起 recv */
|
||||
uint8_t infinite; /* 1 = 无限等待(无 deadline,N34 阶段 1.5:0 预算 = 永等) */
|
||||
uint8_t tripwire_logged; /* 1 = 本轮挂起已打过 tripwire(保证只留痕一条) */
|
||||
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
|
||||
int len; /* 缓冲容量 */
|
||||
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算 */
|
||||
uint32_t registered; /* 登记时刻(tick;看门狗/日志) */
|
||||
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算(infinite=1 时无意义) */
|
||||
uint32_t registered; /* 登记时刻(tick;tripwire/日志基准) */
|
||||
TaskHandle_t caller; /* 调用方任务(完成通知目标) */
|
||||
} net_pending_t;
|
||||
|
||||
@@ -694,8 +698,8 @@ static void net_reconcile_tick(void) {
|
||||
* (NET_RELISTEN_RETRY_MS)节拍重试 open+listen;2s(NET_RELISTEN_DEADLINE_MS)
|
||||
* 总时限到期放弃留痕
|
||||
* 3. D10:本 fd 有在途挂起 recv 时跳过(L3 保险)
|
||||
* 4. net_pending_tick(L3):按节拍了结挂起 recv(数据/到点/断连)
|
||||
* 5. L3 看门狗:到点后 1s(NET_PENDING_WATCHDOG_MS)未了结 → 强制了结 + DBG_ERROR 留痕
|
||||
* 4. net_pending_tick(L3):按节拍了结挂起 recv(数据/到点[仅显式预算]/断连)
|
||||
* 5. L3 tripwire(N34 阶段 1.5):挂起超 60s 只 DBG_ERROR 留痕一次,不再强制了结/伪造返回值
|
||||
* 6. net_reconcile_tick(Trap 22):每 200ms(NET_RECONCILE_PERIOD_MS)对账①②
|
||||
* 7. 对账以连续 2 次失配去抖 + 1s 冷却,自愈两类失同步
|
||||
* 8. net_listener_pump(N-32 P2):每 100ms 监听器 pump,本层**唯一**重建入口
|
||||
@@ -933,6 +937,10 @@ int net_socket(int domain, int type) {
|
||||
p_sock->send_ready = 1;
|
||||
/* 无可读数据 */
|
||||
p_sock->readable = 0;
|
||||
/* N34 阶段 1.5:阻塞模式默认 = 阻塞(BSD/Python 语义;非阻塞用
|
||||
* net_setblocking(fd, 0) 显式设置)。close 的 memset 会清零本字段,
|
||||
* 新 fd 必须在此显式置 1(同 released_abnormal 的"不得继承"纪律) */
|
||||
p_sock->blocking = 1;
|
||||
/* 新 fd 不得继承上一条连接的释放原因(Trap 23 待办 #3) */
|
||||
p_sock->released_abnormal = 0;
|
||||
/* N-32(P1):新 fd 不得继承"上一条会话已结束"电平(否则新建即 EOF,
|
||||
@@ -1865,16 +1873,17 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
* buf - 接收缓冲区指针 void *
|
||||
* len - 缓冲区大小 int > 0
|
||||
* flags - 标志位,NET_MSG_DONTWAIT 表示非阻塞 int
|
||||
* timeout_ms - 本次等待预算(0 = 默认 NET_RECV_TIMEOUT_MS) uint32_t
|
||||
* timeout_ms - 本次等待预算(0 = 无限等待,N34 阶段 1.5 起无默认值)uint32_t
|
||||
* caller - 调用方任务句柄(挂起请求的完成通知目标) TaskHandle_t
|
||||
* 出口参数:buf - 填充接收到的数据
|
||||
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
|
||||
* NET_PENDING_REGISTERED 表示已登记挂起请求(分派据此跳过即时通知)
|
||||
* 限定条件:p_sock 非空;netTask 上下文
|
||||
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回,行为与旧实现一致
|
||||
* 2. 非阻塞模式无数据立即返回 -1(NET_ERR_WOULDBLOCK)
|
||||
* 2. 非阻塞(NET_MSG_DONTWAIT 单次覆盖 **或** socket blocking 属性为 0,
|
||||
* N34 阶段 1.5 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK)
|
||||
* 3. 无数据 + 阻塞等待 → 登记挂起请求(L3 pending 化,E-02 结构演进)
|
||||
* 4. netTask 零占用立即返回;完成条件(数据/到点/断连)由 net_pending_tick 按主循环节拍检查
|
||||
* 4. netTask 零占用立即返回;完成条件(数据/到点[仅显式预算]/断连)由 net_pending_tick 按主循环节拍检查
|
||||
* 5. 并通知 caller;关闭/网络重建/陈旧重同步由对应路径显式了结(取消必通知)
|
||||
* 6. CLOSED 状态语义(排空 + EOF + 自动重监听)逐项保留在 tick 内
|
||||
*/
|
||||
@@ -1888,8 +1897,9 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
return NET_ERR_RETURN_1(NET_ERR_INVAL);
|
||||
}
|
||||
|
||||
/* 超时来源:调用方指定(单次预算)优先,否则用默认值(均作为挂起截止) */
|
||||
uint32_t to_ms = (timeout_ms != 0U) ? timeout_ms : (uint32_t)NET_RECV_TIMEOUT_MS;
|
||||
/* 超时来源:只有调用方显式预算(net_recv_to);0 = 无限等待
|
||||
* (N34 阶段 1.5:NET_RECV_TIMEOUT_MS 30s 默认预算已删,API 不强加隐藏值) */
|
||||
uint32_t to_ms = timeout_ms;
|
||||
|
||||
/* 先轮询一次:刷新状态机与可读标志 */
|
||||
net_poll();
|
||||
@@ -1911,8 +1921,9 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
return read_len;
|
||||
}
|
||||
|
||||
/* 非阻塞模式(DONTWAIT) */
|
||||
if (flags & NET_MSG_DONTWAIT) {
|
||||
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket 的 blocking 属性为 0
|
||||
* (net_setblocking(fd, 0),N34 阶段 1.5 模式挂控制块属性) */
|
||||
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
|
||||
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
|
||||
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
|
||||
/* 连接已断开(CLOSED)**或**本会话已结束(session_dead):按 EOF 处理。
|
||||
@@ -1961,16 +1972,18 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
* 2. 清空整个控制块(memset 0),释放该 Socket 资源
|
||||
*/
|
||||
|
||||
/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22) =====
|
||||
* 完成条件按 ①数据 ②到点 ③断连 顺序检查(逐项对齐旧自旋循环语义)
|
||||
/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22;N34 阶段 1.5 语义主流化) =====
|
||||
* 完成条件按 ①数据 ②到点(仅显式预算) ③断连 顺序检查(逐项对齐旧自旋循环语义)
|
||||
* ① 数据通道(ESTABLISHED/UDP_OPEN)内 readable 门控 SPI 读——常见无数据路径零额外 SPI 事务
|
||||
* (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位)
|
||||
* ② 到点(与状态无关,旧自旋亦在任何状态下检查超时)→ NET_ERR_TIMEDOUT
|
||||
* ② 到点(仅当登记时带显式预算 net_recv_to;无限等待登记无 deadline)→ NET_ERR_TIMEDOUT
|
||||
* ——这是调用方策略的正常兑现,不是伪造
|
||||
* ③ CLOSED:先排空(bit3=1 芯片断连后残数据留缓冲待 MCU 读——Trap 25 排空契约;
|
||||
* 旧注归 Trap 13 已随其复核注降级)→ 有数据按数据完成,否则 EOF(0) + 自动重监听
|
||||
* 其他状态(CONNECTING/LISTENING/…)保持等待状态迁移(CONNECT→ESTABLISHED 是合法路径;
|
||||
* 陈旧 resync 由显式取消处理)——不做状态推断取消,避免误杀正在建链的等待
|
||||
* 看门狗兜底:到点后仍未了结 = 完成逻辑缺位 → 强制了结留痕
|
||||
* tripwire(N34 阶段 1.5 降级):挂起超 60s = "完成逻辑缺位"探针 → 只 [ERR] 留痕一次,
|
||||
* **绝不强制了结、绝不伪造 TIMEDOUT 返回值**(唤醒责任在 ①②③ 与显式 cancel 路径)
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -2012,7 +2025,7 @@ static void net_pending_complete(int sockfd, int result) {
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* buf - 调用方接收缓冲(须保持有效至完成) void *
|
||||
* len - 缓冲容量 int
|
||||
* to_ms - 本次等待预算(截止 = 登记时刻 + to_ms) uint32_t
|
||||
* to_ms - 本次等待预算(0 = 无限等待,N34 阶段 1.5) uint32_t
|
||||
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
|
||||
* 出口参数:无
|
||||
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
|
||||
@@ -2045,17 +2058,21 @@ static int net_pending_register(int sockfd, void *buf, int len,
|
||||
return NET_ERR_RETURN_1(NET_ERR_BUSY);
|
||||
}
|
||||
|
||||
/* 当前 tick(截止/看门狗基准) */
|
||||
/* 当前 tick(截止/tripwire 基准) */
|
||||
now = HAL_GetTick();
|
||||
/* 标记挂起在位 */
|
||||
p->active = 1U;
|
||||
/* 预算形态:0 = 无限等待(无 deadline,tick ② 跳过;tripwire 以登记时刻计) */
|
||||
p->infinite = (to_ms == 0U) ? 1U : 0U;
|
||||
/* tripwire 留痕标记清零(每轮挂起只留痕一条) */
|
||||
p->tripwire_logged = 0U;
|
||||
/* 调用方接收缓冲(须保持有效至完成) */
|
||||
p->buf = buf;
|
||||
/* 缓冲容量 */
|
||||
p->len = len;
|
||||
/* 截止时刻 = 登记时刻 + 预算 */
|
||||
p->deadline = now + to_ms;
|
||||
/* 登记时刻(看门狗基准) */
|
||||
/* 截止时刻 = 登记时刻 + 预算(无限等待时不启用,置 0 仅为可观测性) */
|
||||
p->deadline = (to_ms == 0U) ? 0U : (now + to_ms);
|
||||
/* 登记时刻(tripwire 基准) */
|
||||
p->registered = now;
|
||||
/* 完成通知目标 */
|
||||
p->caller = caller;
|
||||
@@ -2069,8 +2086,10 @@ static int net_pending_register(int sockfd, void *buf, int len,
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(deadline)/ 断连
|
||||
* 2. 完成即经 net_pending_complete 通知并清除;看门狗(NET_PENDING_WATCHDOG_MS)超时强制了结
|
||||
* 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(仅显式预算)/ 断连
|
||||
* 2. 完成即经 net_pending_complete 通知并清除
|
||||
* 3. tripwire(N34 阶段 1.5):挂起超 NET_RECV_TRIPWIRE_MS 只 [ERR] 留痕
|
||||
* 一次("完成逻辑缺位"探针),绝不强制了结、绝不伪造返回值
|
||||
*/
|
||||
static void net_pending_tick(void) {
|
||||
/* 当前 tick */
|
||||
@@ -2122,8 +2141,10 @@ static void net_pending_tick(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ② 到点(tick 差值按有符号比较,抗回绕) */
|
||||
if ((int32_t)(now - p->deadline) >= 0) {
|
||||
/* ② 到点(仅显式预算登记;无限等待登记无 deadline——N34 阶段 1.5:
|
||||
* 超时是调用方策略,net_recv_to 的预算在此兑现,不是伪造。
|
||||
* tick 差值按有符号比较,抗回绕) */
|
||||
if ((p->infinite == 0U) && ((int32_t)(now - p->deadline) >= 0)) {
|
||||
/* 了结:超时(错误码由 net_pending_complete 记入本任务槽位) */
|
||||
net_pending_complete(i, NET_ERR_TIMEDOUT);
|
||||
continue;
|
||||
@@ -2133,7 +2154,9 @@ static void net_pending_tick(void) {
|
||||
* 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED ||
|
||||
* session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED)
|
||||
* 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时
|
||||
* (lftpd 阻塞 recv 的"消费者卡死"原型)。 */
|
||||
* (lftpd 阻塞 recv 的"消费者卡死"原型)。N34 阶段 1.5 后该形态更致命:
|
||||
* 无限等待登记没有 ②,漏 ③ = 永久挂死(只剩 tripwire 留痕)——
|
||||
* ③ 与显式 cancel 是无限 recv 的全部唤醒责任方,勿动判据。 */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
|
||||
/* 查询残留字节数 */
|
||||
int rem = ch395f_get_recv_len((uint8_t)i);
|
||||
@@ -2179,13 +2202,23 @@ static void net_pending_tick(void) {
|
||||
|
||||
/* 其他状态:保持等待(见本区块头注释) */
|
||||
|
||||
/* ④ 看门狗:到点 + 裕量仍未了结 = 完成逻辑缺位 → 强制了结并留痕 */
|
||||
if ((int32_t)(now - p->deadline) >= (int32_t)NET_PENDING_WATCHDOG_MS) {
|
||||
/* 强制了结留痕(超截止时刻的毫秒数) */
|
||||
DBG_ERROR("sock%d pending recv stuck %ums past deadline (force settle)",
|
||||
i, (unsigned)((int32_t)now - (int32_t)p->deadline));
|
||||
/* 强制了结:超时(错误码由 net_pending_complete 记入本任务槽位) */
|
||||
net_pending_complete(i, NET_ERR_TIMEDOUT);
|
||||
/* ④ tripwire(N34 阶段 1.5 降级,原"看门狗强制了结"):挂起超 60s =
|
||||
* "完成逻辑缺位"探针 → 只 [ERR] 留痕一次,**绝不强制了结、绝不伪造
|
||||
* TIMEDOUT 返回值**(旧实现到点 +1s 兜底伪造 TIMEDOUT,掩盖完成路径
|
||||
* 缺陷;新语义下无限等待本就无预算,伪造返回值反而违背调用方意图)。
|
||||
* 判据:无限登记按登记时刻计;显式登记按 deadline 计(② 正常不会让它
|
||||
* 越过 60s,能到这里本身就是完成逻辑缺位)。 */
|
||||
if (p->tripwire_logged == 0U) {
|
||||
/* 探针基准时刻:无限 = 登记时刻;显式 = 截止时刻 */
|
||||
uint32_t base = (p->infinite != 0U) ? p->registered : p->deadline;
|
||||
if ((int32_t)(now - base) >= (int32_t)NET_RECV_TRIPWIRE_MS) {
|
||||
/* 留痕(挂起已持续毫秒数,自基准起算) */
|
||||
DBG_ERROR("sock%d recv pending %ums (base=%s): completion source missing",
|
||||
i, (unsigned)((int32_t)now - (int32_t)base),
|
||||
(p->infinite != 0U) ? "infinite" : "deadline");
|
||||
/* 只留痕一次(每轮挂起一条,防日志风暴) */
|
||||
p->tripwire_logged = 1U;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2437,17 +2470,26 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask)
|
||||
* 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask;
|
||||
* **默认无限阻塞**——N34 阶段 1.5 语义主流化,30s 默认预算已删)
|
||||
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
|
||||
* buf - 接收缓冲区指针 void *
|
||||
* len - 缓冲区大小 int > 0
|
||||
* flags - 标志位,0=阻塞,NET_MSG_DONTWAIT=非阻塞 int
|
||||
* flags - 标志位,0=按 socket 阻塞属性等待,NET_MSG_DONTWAIT=单次非阻塞 int
|
||||
* 出口参数:无
|
||||
* 返回值:实际接收字节数 int >= 0 || 0 || -1
|
||||
* 0 - 对端关闭连接 int
|
||||
* -1 - 接收失败(参数无效、队列满等) int
|
||||
* -1 - 接收失败(参数无效、非阻塞无数据=WOULDBLOCK、
|
||||
* close/网络重建/陈旧重同步取消=CONNRESET 等,errno 可查) int
|
||||
* 限定条件:Socket 处于 ESTABLISHED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
|
||||
* 函数说明:将接收请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_recv_sock()
|
||||
* 函数说明:1. 将接收请求放入消息队列,等待 netTask 处理完毕并返回结果。netTask 内请使用 net_recv_sock()
|
||||
* 2. 无数据 + 阻塞(blocking 属性为默认值 1 且未置 DONTWAIT)→ 登记挂起
|
||||
* **无限等待**;唤醒源 = 数据 / EOF / close / 网络重建(CONNRESET);
|
||||
* 会话死亡由对账① ~400ms 判死兜底,不存在"对端消失后永久挂死"窗口
|
||||
* 3. ⚠ 文档契约(嵌入式特有):IWDG required_mask 内任务禁止无限阻塞
|
||||
* recv——必须 net_select 超时或 net_recv_to 显式预算(超时归使用者
|
||||
* 对受监控任务是义务不是选项),否则"对端连上不发数据"= IWDG 复位
|
||||
* 4. 超时首选 net_select(...);net_recv_to(...) 为显式预算便捷入口
|
||||
*/
|
||||
int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
/* 一次性消息结构体(队列按值存结构副本) */
|
||||
@@ -2471,9 +2513,9 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
msg.buf = buf;
|
||||
/* 缓冲容量 */
|
||||
msg.len = len;
|
||||
/* 标志:0=阻塞,NET_MSG_DONTWAIT=非阻塞 */
|
||||
/* 标志:0=按 socket 阻塞属性,NET_MSG_DONTWAIT=单次非阻塞 */
|
||||
msg.flags = flags;
|
||||
/* 0 = 默认超时预算(netTask 侧用 NET_RECV_TIMEOUT_MS) */
|
||||
/* 0 = 无限等待(N34 阶段 1.5:无默认预算;显式预算走 net_recv_to) */
|
||||
msg.timeout_ms = 0U;
|
||||
/* 调用方任务句柄(netTask 凭它回传回执) */
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
@@ -2499,7 +2541,28 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(带调用方指定超时的阻塞版,用于分片有界读)
|
||||
* 函数功能:接收数据(非阻塞具名入口——N34 阶段 1.5 把 NET_MSG_DONTWAIT 路径转正,
|
||||
* 对齐 BSD recv(MSG_DONTWAIT) / Python recv 非阻塞模式的"名如其义")
|
||||
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
|
||||
* buf - 接收缓冲区指针 void *
|
||||
* len - 缓冲区大小 int > 0
|
||||
* 出口参数:buf - 填充接收到的数据
|
||||
* 返回值:实际接收字节数(> 0);0 = 对端关闭(net 层已自动重监听); int >= 0 || -1
|
||||
* -1 = 错误(无数据 = NET_ERR_WOULDBLOCK,其余 errno 可查) int
|
||||
* 限定条件:非 netTask 上下文;net 层已初始化
|
||||
* 函数说明:1. 等价 net_recv(sockfd, buf, len, NET_MSG_DONTWAIT):单次强制非阻塞,
|
||||
* 不改变 socket 的 blocking 属性(模式用 net_setblocking)
|
||||
* 2. 受 IWDG 监控 / 有周期职责的任务(netTask 除外)的推荐默认入口;
|
||||
* 需要"等但不无限等"用 net_select / net_recv_to
|
||||
*/
|
||||
int net_recv_nb(int sockfd, void *buf, int len) {
|
||||
return net_recv(sockfd, buf, len, NET_MSG_DONTWAIT);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(带调用方指定超时的阻塞版,用于分片有界读;
|
||||
* N34 阶段 1.5 起为**显式预算**便捷入口,等价 Python settimeout,
|
||||
* 不再有隐藏默认值)
|
||||
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
|
||||
* buf / len - 接收缓冲与容量 void * / int > 0
|
||||
* timeout_ms - 单次占用 netTask 的最长等待(毫秒,>0) uint32_t
|
||||
@@ -2507,10 +2570,11 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
* 返回值:>0 字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据**
|
||||
* 限定条件:非 netTask 上下文;net 层已初始化
|
||||
* 函数说明:1. E-02 修复配套接口:lftpd 等"等对端下一行/下一块数据"的调用方
|
||||
* 若用默认 30s 阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期占住
|
||||
* 若无限阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期占住
|
||||
* 2. 导致其余任务的网络操作整体冻结(M3 实测一次空闲 FTP 登录冻结远端显示 17.7s)
|
||||
* 3. 用本接口把等待切成 ≤timeout_ms 的片,调用方自行累计总预算:总超时语义不变、单次占用有界
|
||||
* 4. 超时用独立返回码 -2(而非靠 errno 区分)→ 无跨任务 errno 竞态
|
||||
* 5. ⚠ IWDG required_mask 内任务必须走本接口或 net_select(net_recv 契约)
|
||||
*/
|
||||
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
|
||||
/* 一次性消息结构体:队列按值存结构副本(仅指针、不拷数据),入队后由 netTask 取出处理 */
|
||||
@@ -2528,8 +2592,9 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
|
||||
return NET_ERR_RETURN_1(NET_ERR);
|
||||
}
|
||||
|
||||
/* ---- 3. 分片长度检查:timeout_ms = 0 在 netTask 侧意味着"改用默认
|
||||
* NET_RECV_TIMEOUT_MS(30 秒)",恰是本接口要规避的长阻塞,直接拒绝 ---- */
|
||||
/* ---- 3. 分片长度检查:timeout_ms = 0 在 netTask 侧意味着"**无限等待**"
|
||||
* (N34 阶段 1.5 删除 30s 默认预算),恰与本接口"有界等待"的存在
|
||||
* 理由相反,直接拒绝 ---- */
|
||||
if (timeout_ms == 0U) {
|
||||
/* 分片长度必须 > 0 */
|
||||
return NET_ERR_RETURN_1(NET_ERR_INVAL);
|
||||
@@ -2780,14 +2845,14 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
* 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用;有数据可用
|
||||
* 函数说明:1. 轮询检查芯片接收缓冲(net_poll + get_recv_len),有数据即返回
|
||||
* 2. UDP 数据报前 8 字节为报文头(源端口+源 IP),据此填充 src_addr
|
||||
* 3. 无数据时:DONTWAIT 立即返回 -1(WOULDBLOCK);默认等待 NET_RECV_TIMEOUT_MS 后返回 TIMEDOUT
|
||||
* 3. 无数据时(N34 阶段 1.5,与 net_recv 同构):DONTWAIT / 非阻塞属性
|
||||
* 立即返回 -1(WOULDBLOCK);阻塞模式**无限等待**(30s 默认预算已删;
|
||||
* 本接口为轮询自旋实现且无生产调用者,受 IWDG 监控的任务勿用无限形态)
|
||||
*/
|
||||
int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
struct net_sockaddr *src_addr, int *addrlen) {
|
||||
/* Socket 控制块指针 */
|
||||
net_sock_t *p_sock;
|
||||
/* 起始时刻(超时判定基准,tick) */
|
||||
uint32_t tick_start;
|
||||
|
||||
/* 参数检查 */
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
@@ -2807,10 +2872,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
return NET_ERR_RETURN_1(NET_ERR_INVAL);
|
||||
}
|
||||
|
||||
/* 记录起始时刻(超时基准) */
|
||||
tick_start = HAL_GetTick();
|
||||
|
||||
/* 轮询直到:有数据 / 非阻塞 / 超时 */
|
||||
/* 轮询直到:有数据 / 非阻塞(N34 阶段 1.5:阻塞 = 无限等待,无默认预算) */
|
||||
while (1) {
|
||||
/* 轮询一次:更新状态机与可读标志 */
|
||||
net_poll();
|
||||
@@ -2862,18 +2924,16 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
}
|
||||
}
|
||||
|
||||
/* 非阻塞模式:无数据立即返回 */
|
||||
if (flags & NET_MSG_DONTWAIT) {
|
||||
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket blocking 属性为 0
|
||||
* (N34 阶段 1.5,与 net_recv_locked 同判据):无数据立即返回 */
|
||||
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
|
||||
/* 置 WOULDBLOCK */
|
||||
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
|
||||
}
|
||||
|
||||
/* 超时检查 */
|
||||
if (NET_RECV_TIMEOUT_MS > 0 &&
|
||||
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
|
||||
/* 置 TIMEDOUT */
|
||||
return NET_ERR_RETURN_1(NET_ERR_TIMEDOUT);
|
||||
}
|
||||
/* 阻塞模式:无数据继续轮询等待(N34 阶段 1.5:无限等待,无默认预算——
|
||||
* 旧 NET_RECV_TIMEOUT_MS 30s 兜底已删;唤醒源 = 数据,与 net_recv 的
|
||||
* 差异是本接口为自旋实现,勿在受 IWDG 监控任务中使用无限形态) */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2916,7 +2976,8 @@ int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BADF);
|
||||
}
|
||||
/* 直接委托锁定实现(netTask 上下文,无队列往返);
|
||||
* 0 = 默认 NET_RECV_TIMEOUT_MS;caller = netTask 自身(自调用) */
|
||||
* timeout_ms = 0(无限等待语义,但 netTask 自调用会被挂起登记拒绝 →
|
||||
* 本接口实际仅 DONTWAIT / 非阻塞属性可用);caller = netTask 自身 */
|
||||
return net_recv_locked(p_sock, buf, len, flags, 0U,
|
||||
xTaskGetCurrentTaskHandle());
|
||||
}
|
||||
@@ -2975,6 +3036,46 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
|
||||
/* 注册成功 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(N34 阶段 1.5,模式挂控制块属性,
|
||||
* 对齐 BSD O_NONBLOCK / Python setblocking)
|
||||
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
|
||||
* on - 1 = 阻塞(net_socket 默认);0 = 非阻塞 int
|
||||
* 出口参数:无
|
||||
* 返回值:0 - 设置成功 int
|
||||
* -1 - 失败(描述符越界 / 未占用) int
|
||||
* 限定条件:socket 已创建(in_use == 1);任意上下文(同 net_set_event_cb 先例)
|
||||
* 函数说明:1. 属性对 net_recv / net_recv_sock 生效:非阻塞下无数据立即
|
||||
* NET_ERR_WOULDBLOCK;EOF 判定语义不变(CLOSED/session_dead 无数据
|
||||
* 仍返回 0 并触发自动重监听)
|
||||
* 2. 直写控制块单字节(ARM 原子写,先例 net_set_event_cb):与 netTask
|
||||
* 竞态的最坏结果 = 一次在途 recv 用旧模式,无撕裂、无需过消息队列
|
||||
* 3. 与超时预算正交:模式是粘滞属性;单次预算用 net_recv_to / net_select
|
||||
*/
|
||||
int net_setblocking(int sockfd, int on) {
|
||||
/* Socket 控制块指针 */
|
||||
net_sock_t *p_sock;
|
||||
|
||||
/* 参数检查:描述符范围 */
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
/* 描述符越界 → BADF */
|
||||
return NET_ERR_RETURN_1(NET_ERR_BADF);
|
||||
}
|
||||
|
||||
/* 取控制块 */
|
||||
p_sock = &s_net_socks[sockfd];
|
||||
/* 控制块未在用 → BADF */
|
||||
if (!p_sock->in_use) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BADF);
|
||||
}
|
||||
|
||||
/* 写模式属性:非 0 = 阻塞,0 = 非阻塞(单字节原子写) */
|
||||
p_sock->blocking = (on != 0) ? 1U : 0U;
|
||||
|
||||
/* 设置成功 */
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* 函数功能:获取 **本任务** 最后一次 net_*() 调用失败时的精确错误码
|
||||
* 入口参数:无
|
||||
|
||||
@@ -48,7 +48,8 @@ typedef struct {
|
||||
void *buf; /* 数据缓冲区(send/recv) */
|
||||
int len; /* 数据长度/缓冲区大小 */
|
||||
int flags; /* 接收标志 */
|
||||
uint32_t timeout_ms; /* 接收超时(入队方向;0 = 用默认 NET_RECV_TIMEOUT_MS) */
|
||||
uint32_t timeout_ms; /* 接收超时(入队方向;0 = 无限等待,无默认预算——
|
||||
* N34 阶段 1.5 删除 NET_RECV_TIMEOUT_MS 30s 默认) */
|
||||
TaskHandle_t caller; /* 调用方任务句柄 */
|
||||
int result; /* 操作结果 */
|
||||
/* 连接参数 */
|
||||
@@ -230,19 +231,55 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen);
|
||||
int net_send(int sockfd, const void *buf, int len, int flags);
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(TCP)
|
||||
* 函数功能:接收数据(TCP,**默认无限阻塞**——N34 阶段 1.5 语义主流化)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* len - 缓冲区大小
|
||||
* flags - 通常为 0,可设置 NET_MSG_DONTWAIT 非阻塞
|
||||
* flags - 通常为 0;NET_MSG_DONTWAIT 单次强制非阻塞
|
||||
* 返回值:实际接收字节数,0=对端关闭,-1=错误
|
||||
* 限定条件:TCP 连接已建立
|
||||
* 函数说明:在阻塞模式下,如果没有数据,函数会等待;
|
||||
* 在非阻塞模式下,如果没有数据,返回 -1 并设置
|
||||
* net_errno 为 NET_ERR_WOULDBLOCK
|
||||
* 函数说明:1. 无数据时**无限等待**(无默认超时预算,对齐 POSIX/Python:模式归
|
||||
* 使用者、超时归调用方);唤醒源 = 数据 / EOF / close / 网络重建
|
||||
* (NET_ERR_CONNRESET)——会话死亡由对账①在 ~400ms 内判死兜底,
|
||||
* 不存在"对端消失后永久挂死"的窗口
|
||||
* 2. 模式属性:net_setblocking(fd, 0) 后本接口无数据立即返回
|
||||
* NET_ERR_WOULDBLOCK;NET_MSG_DONTWAIT 仅单次覆盖该属性
|
||||
* 3. ⚠ 文档契约(嵌入式特有):IWDG required_mask 内任务**禁止无限
|
||||
* 阻塞 recv**——必须 net_select 超时或 net_recv_to 显式预算,
|
||||
* 否则"对端连上不发数据"= IWDG 复位(超时归使用者对受监控任务是义务)
|
||||
* 4. 超时首选 net_select(r,w,e,timeout_ms);net_recv_to 为显式预算便捷入口
|
||||
*/
|
||||
int net_recv(int sockfd, void *buf, int len, int flags);
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(非阻塞具名入口,原 NET_MSG_DONTWAIT 路径转正——N34 阶段 1.5)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* len - 缓冲区大小
|
||||
* 返回值:实际接收字节数;0 = 对端关闭(net 层已自动重监听);
|
||||
* -1 = 错误(无数据 = NET_ERR_WOULDBLOCK,其余 errno 可查)
|
||||
* 限定条件:非 netTask 上下文(netTask 内用 net_recv_sock + DONTWAIT)
|
||||
* 函数说明:等价 net_recv(sockfd, buf, len, NET_MSG_DONTWAIT);
|
||||
* 不改变 socket 的 blocking 属性(模式用 net_setblocking 设置)
|
||||
*/
|
||||
int net_recv_nb(int sockfd, void *buf, int len);
|
||||
|
||||
/*
|
||||
* 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK / Python
|
||||
* setblocking——N34 阶段 1.5,模式挂 socket 控制块属性)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* on - 1 = 阻塞(net_socket 默认);0 = 非阻塞
|
||||
* 返回值:0 成功;-1 失败(描述符无效/未占用)
|
||||
* 限定条件:socket 已创建
|
||||
* 函数说明:1. 属性对 net_recv / net_recv_sock 生效:非阻塞模式下无数据立即
|
||||
* 返回 NET_ERR_WOULDBLOCK(EOF 判定语义不变:CLOSED/session_dead
|
||||
* 无数据仍返回 0 并触发自动重监听)
|
||||
* 2. 直写控制块单字节(先例 net_set_event_cb),任意上下文可调;
|
||||
* 最坏一次在途 recv 用旧模式,无撕裂风险
|
||||
* 3. 与超时预算正交:模式是粘滞属性,单次预算用 net_recv_to/net_select
|
||||
*/
|
||||
int net_setblocking(int sockfd, int on);
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(**带调用方指定超时**的阻塞版,用于分片有界读)
|
||||
* 入口参数:sockfd - Socket 描述符
|
||||
@@ -250,10 +287,14 @@ int net_recv(int sockfd, void *buf, int len, int flags);
|
||||
* timeout_ms - 单次调用在 netTask 内的最长等待(毫秒,>0)
|
||||
* 返回值:>0 实际字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据**
|
||||
* 限定条件:非 netTask 上下文;Socket 已连接
|
||||
* 函数说明:为"不能长时间占用 netTask"的调用方(如 lftpd 控制行/数据读)提供
|
||||
* **分片**阻塞:每次调用最多占用 netTask timeout_ms,返回 -2 表示这段
|
||||
* 时间内无数据,调用方自行累计总预算并决定是否继续(E-02 头阻塞修复)。
|
||||
* 超时用独立返回码而非 errno → 无跨任务 errno 竞态。
|
||||
* 函数说明:1. **显式预算**便捷入口(等价 Python settimeout,N34 阶段 1.5 起
|
||||
* 不再有隐藏默认值):net_recv 的无限阻塞 + 本接口的显式预算构成
|
||||
* 超时语义的全部来源;超时首选仍是 net_select
|
||||
* 2. E-02 修复配套:lftpd 等"等对端下一行/下一块数据"的调用方
|
||||
* 若无限阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期占住
|
||||
* 3. 用本接口把等待切成 ≤timeout_ms 的片,调用方自行累计总预算:总超时语义不变、单次占用有界
|
||||
* 4. 超时用独立返回码 -2(而非靠 errno 区分)→ 无跨任务 errno 竞态
|
||||
* 5. ⚠ IWDG required_mask 内任务必须走本接口或 net_select(见 net_recv 契约)
|
||||
*/
|
||||
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
|
||||
|
||||
@@ -277,12 +318,16 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* len - 缓冲区大小
|
||||
* flags - 通常为 0
|
||||
* flags - 通常为 0;NET_MSG_DONTWAIT 单次强制非阻塞
|
||||
* src_addr - 输出:发送方地址
|
||||
* addrlen - 输入输出:地址长度
|
||||
* 返回值:实际接收字节数,失败返回 -1
|
||||
* 限定条件:socket 已创建,指针非空
|
||||
* 函数说明:读取 CH395F 接收缓冲区,获取对端地址
|
||||
* 函数说明:1. 读取 CH395F 接收缓冲区,获取对端地址
|
||||
* 2. 阻塞语义与 net_recv 同(N34 阶段 1.5):blocking 属性下无数据
|
||||
* 无限等待,DONTWAIT / 非阻塞属性立即 NET_ERR_WOULDBLOCK;
|
||||
* 本接口为轮询自旋实现(历史遗留,UDP 专用),受 IWDG 监控的
|
||||
* 任务同样禁止对其无限等待
|
||||
*/
|
||||
int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
struct net_sockaddr *src_addr, int *addrlen);
|
||||
|
||||
@@ -47,7 +47,9 @@ extern "C" {
|
||||
/*
|
||||
* 接收标志定义
|
||||
*/
|
||||
#define NET_MSG_DONTWAIT 0x01 /* 非阻塞模式 */
|
||||
#define NET_MSG_DONTWAIT 0x01 /* 非阻塞模式(单次覆盖 socket 的 blocking 属性;
|
||||
* 具名入口见 net_recv_nb() / net_setblocking(),
|
||||
* N34 阶段 1.5) */
|
||||
|
||||
/*
|
||||
* 网络字节序转换宏
|
||||
@@ -171,6 +173,15 @@ typedef struct {
|
||||
/* 单连接模式自动重监听:连接断开后由 net 层自动回到 LISTEN(BSD 风格监听常驻) */
|
||||
uint8_t auto_relisten;
|
||||
|
||||
/* N34 阶段 1.5(2026-10-13):阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK /
|
||||
* Python setblocking 的"模式归使用者"模型)。
|
||||
* 1 = 阻塞(net_socket() 默认);0 = 非阻塞(net_setblocking(fd, 0))。
|
||||
* net_recv() 无 NET_MSG_DONTWAIT 时按本属性决定"无数据 → 登记挂起等待"
|
||||
* 还是"立即 WOULDBLOCK"。单字节写(ARM 原子),写入先例同 event_cb
|
||||
* (net_setblocking 直写控制块,最坏一次 recv 用旧模式,无撕裂风险)。
|
||||
* 注意与"超时预算"正交:模式是属性(粘滞),预算是单次参数(net_recv_to)。 */
|
||||
uint8_t blocking;
|
||||
|
||||
/* 释放来源标记(Trap 23 待办 #3):1 = 异常释放(芯片 TIMEOUT,对端 RST/掉链),
|
||||
* 0 = 正常释放(芯片 DISCONNECT,对端 FIN)或新连接。L3 挂起 recv 的了结断连
|
||||
* 分支据此决定回 EOF(0) 还是 NET_ERR_CONNRESET:异常结束上报应用(lftpd STOR)
|
||||
|
||||
@@ -545,8 +545,10 @@ void ui_net_sink_poll(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 有客户端时非阻塞收帧(I-1 门控 + I-2 兜底):
|
||||
* 无数据可读 → 多数轮次直接返回,省掉一次 net_recv 往返(该往返内部
|
||||
/* 有客户端时非阻塞收帧(I-1 门控 + I-2 兜底;N34 阶段 1.5:DONTWAIT 转正
|
||||
* 为具名 API net_recv_nb——uiTask 在 IWDG required_mask 内且有 10ms UI 推流
|
||||
* 周期职责,禁止无限阻塞 recv,非阻塞轮询是义务):
|
||||
* 无数据可读 → 多数轮次直接返回,省掉一次 recv 往返(该往返内部
|
||||
* osMessageQueuePut(osWaitForever) + xTaskNotifyWait(portMAX_DELAY),无上界);
|
||||
* 但每 RM_LIVENESS_DIV 轮无条件收一次,防 RECV_OK 中断丢失把数据永久藏住 */
|
||||
if (net_can_read(g_client) == 0) {
|
||||
@@ -556,7 +558,7 @@ void ui_net_sink_poll(void) {
|
||||
} else {
|
||||
s_liveness_ctr = 0U;
|
||||
}
|
||||
n = net_recv(g_client, g_rx, (int)sizeof(g_rx), NET_MSG_DONTWAIT);
|
||||
n = net_recv_nb(g_client, g_rx, (int)sizeof(g_rx));
|
||||
if (n > 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
rm_parser_feed((uint8_t)g_rx[i]);
|
||||
|
||||
@@ -45,8 +45,10 @@ int ui_net_sink_start(uint16_t port);
|
||||
* 返回值:无
|
||||
* 限定条件:ui_net_sink_start() 已成功调用
|
||||
* 函数说明:1. 无客户端时先门控(net_listener_armed,纯 RAM 读)再尝试接受连接
|
||||
* 2. 有客户端时先门控(net_can_read)再用 DONTWAIT 收帧并解析
|
||||
* 2. 有客户端时先门控(net_can_read)再用 net_recv_nb 非阻塞收帧并解析
|
||||
* (KEEPLIVE/INIT/KEY/LCDMEM);门控判为无数据时仍每 32 轮兜底收一次
|
||||
* (N34 阶段 1.5:DONTWAIT 转正为具名 API;uiTask 受 IWDG 监控,
|
||||
* 非阻塞是契约义务——见 net_socket.h net_recv 文档契约)
|
||||
* 3. 对端关闭或异常时复位客户端,等待下次重监听
|
||||
* ⚠ **不是"零成本非阻塞"**:门控只消除无谓往返;真有活时仍是
|
||||
* "入队 + 等 netTask 回执"(osMessageQueuePut(osWaitForever) +
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 228,172 B (222.82 KB) | 1,048,576 B (1024 KB) | 21.8% |
|
||||
| RW_IRAM1 | 97,864 B (95.57 KB) | 131,072 B (128 KB) | 74.7% |
|
||||
| ER_IROM1 | 228,372 B (223.02 KB) | 1,048,576 B (1024 KB) | 21.8% |
|
||||
| RW_IRAM1 | 97,896 B (95.60 KB) | 131,072 B (128 KB) | 74.7% |
|
||||
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
|
||||
| **Flash (ROM 合计)** | **229,276 B (223.9 KB)** | 1,048,576 B (1024 KB) | **21.9%** |
|
||||
| **Flash (ROM 合计)** | **229,476 B (224.1 KB)** | 1,048,576 B (1024 KB) | **21.9%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|
||||
|---|---|---|---|---|
|
||||
| ER_IROM1 (Flash) | 228,172 B | 1,048,576 B | 21.8% | 参考 |
|
||||
| ER_IROM1 (Flash) | 228,372 B | 1,048,576 B | 21.8% | 参考 |
|
||||
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
|
||||
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
|
||||
| RW_IRAM1 | 97,864 B | 131,072 B | 74.7% | PASS |
|
||||
| RW_IRAM1 | 97,896 B | 131,072 B | 74.7% | PASS |
|
||||
|
||||
| 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 |
|
||||
|---|---|---|---|
|
||||
| 网络 | 13,410 B | 12 KB | ⚠ 超 1.1 KB |
|
||||
| 网络 | 13,442 B | 12 KB | ⚠ 超 1.1 KB |
|
||||
| 存储 | 7,056 B | 12 KB | OK |
|
||||
| UI | 54,988 B | 16 KB | ⚠ 超 37.7 KB |
|
||||
| RTOS 堆 | 57,376 B | 56 KB | 堆=配额 |
|
||||
|
||||
@@ -116,6 +116,14 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
|
||||
| 关闭姿势必须正确 | Trap 14/17:陈旧 DISCONNECT 锁存秒断下一条连接 | `net_close_locked` 既有「close → 等 `CLOSED` → 排空本 Socket 中断」,是 bit3=1 下唯一正确的关闭序列 |
|
||||
| 异常释放须先排空残渣再关 | Trap 23:~2KB RX 残渣被下条连接当自己的数据读走 | `net_relisten_locked` 隔离期(400 ms)持续排空 RX + 主动 close + 等 `CLOSED/TCP_CLOSED` |
|
||||
|
||||
> **命名注(N34 阶段 1.5,2026-10-13)**:上表与下文所说的「DONTWAIT」机制本体不变
|
||||
> (`NET_MSG_DONTWAIT` 仍是 `net_recv_locked` 的非阻塞路径与 EOF 判据载体),但对外
|
||||
> 入口已主流化:具名 API `net_recv_nb()`(单次非阻塞)+ `net_setblocking(fd, on)`
|
||||
> (模式挂控制块 `blocking` 属性);`net_recv()` 默认改为**无限阻塞**(30s 默认预算
|
||||
> 删除),受 IWDG 监控任务的有界等待契约见 `docs/网络/BSD_Socket_API_使用指南.md`
|
||||
> §10.1 与 `docs/网络/N34-自愈审计/实施方案.md` §3.5。本文所有「DONTWAIT 判据 /
|
||||
> 返 WOULDBLOCK」表述按新命名逐字仍成立,不逐处改写。
|
||||
|
||||
**⑥ 本次(P0)已落地的口径修正**:`net_socket.c:3627` / `:3641` 原注释写「DISCONNECT 后
|
||||
芯片自动关闭 Socket(FUN_PARA 默认)」——与实际 `0x08` **相反**。这类注释正是 U12 这类
|
||||
「跳过显式 close」缺陷的观念源头,已改正并留痕。
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
|---|---|---|---|---|---|
|
||||
| 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-3 | **`net_process_messages` / `net_recv_to` 等缺失** | 未实现(MCU 专属:CH395F 消息队列驱动、带超时接收) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"带超时接收"与"消息泵"的上层逻辑在模拟器上**不可编译/不可运行** | 板端 CH395F / NET 套件 |
|
||||
| N-3 | ~~`net_recv_to` 缺失~~(**2026-10-13 N34 阶段 1.5 已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 |
|
||||
| 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-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`,使 `DONTWAIT` 语义与 MCU 对齐(设计要点 2) | 中断驱动 + 状态机,天然非阻塞 | ——(此项是**对齐**而非差距,登记以说明"为何模拟器能测非阻塞路径") | —— |
|
||||
| N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(N34 阶段 1.5:`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + L3 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(N-1)仍不建模 | —— |
|
||||
|
||||
### 2.2 日志与配置
|
||||
|
||||
@@ -109,4 +109,5 @@
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-13 | **N34 阶段 1.5 recv 语义主流化**:`net_socket_sim.c` 镜像 `net_recv_nb` / `net_recv_to` / `net_setblocking`,阻塞语义改按控制块 `blocking` 属性在包装层模拟(Winsock 句柄仍恒 FIONBIO)——N-3 的 `net_recv_to` 缺失部分**已消除**(划线留档),N-6 改写为"模式属性 + 包装层模拟";内部挂起模型(N-1)不变 |
|
||||
| 2026-10-02 | 初版(S13):收录 6 条网络差距(N-1~N-6)、4 条日志/配置差距(L-1~L-4)、7 条硬件专属差距(H-1~H-7)、4 条"有意对齐"项。证据来源:`net_socket_sim.c` 文件头设计要点 + 双端导出函数逐一对比(MCU 有而 sim 缺 9 个)+ 各子系统文档既有记载汇总。§3 给出 6 条"必须回真机"决策规则 |
|
||||
|
||||
@@ -91,7 +91,7 @@ sequenceDiagram
|
||||
| TCP 重传 | **8 次 × 200ms = 1.6s 窗口**(固定周期,非 RTT 自适应;P0-a 2026-09-22 实装,原 8×500ms=4s) | `NET_RETRANS_COUNT/PERIOD_MS`(`net_config.h`) |
|
||||
| SPI2 速率 | APB1 42MHz ÷ 8 ≈ **5.25 Mbit/s** → 1KB ≈ **1.6ms**(分频 8 为 PCB 约束,勿"优化") | `Src/spi.c` |
|
||||
| 发送等待超时 | 1s(`NET_SEND_TIMEOUT_MS`) | `net_config.h` |
|
||||
| 接收超时 | 30s(`NET_RECV_TIMEOUT_MS`) | `net_config.h` |
|
||||
| 接收超时 | **无默认预算**(N34 阶段 1.5:`net_recv` 无限阻塞,超时归调用方——`net_select` / `net_recv_to`;原 30s `NET_RECV_TIMEOUT_MS` 已删) | `net_config.h`(宏已移除) |
|
||||
| 调试串口 | USART1 115200,**逐字节阻塞发送**(`fputc` = `HAL_UART_Transmit` 单字节) | `Src/usart.c` |
|
||||
| LVGL 刷新周期 / 色深 | 20ms / 1bpp(`RENDER_MODE_FULL`) | `lv_conf.h`、`ui_port.c` |
|
||||
|
||||
|
||||
@@ -41,9 +41,11 @@
|
||||
| 特性 | Linux Socket | 本实现 |
|
||||
|------|--------------|--------|
|
||||
| Socket 数量 | 无限制 | 最多 8 个 |
|
||||
| 非阻塞模式 | 支持 | 通过 `net_poll()` 实现 |
|
||||
| 阻塞默认 | 默认阻塞(无隐藏超时) | **同**(N34 阶段 1.5:`net_recv()` 无限阻塞,超时归调用方——`net_select` / `net_recv_to`) |
|
||||
| 非阻塞模式 | `O_NONBLOCK` / `MSG_DONTWAIT` | `net_setblocking(fd, 0)`(模式属性)/ `net_recv_nb()` / `NET_MSG_DONTWAIT`(单次) |
|
||||
| 错误码 | errno | `net_get_errno()` |
|
||||
| 头文件 | `<sys/socket.h>` | `"net_socket.h"` |
|
||||
| 无限阻塞约束 | 无 | ⚠ IWDG `required_mask` 内任务**禁止**(嵌入式特有契约,§10.1) |
|
||||
|
||||
---
|
||||
|
||||
@@ -79,7 +81,7 @@ PC 模拟器(`simulator/`,CMake + SDL2)与 MCU 端共用同一份上层网
|
||||
| 底层后端 | CH395F(SPI2) | Winsock2(链接 `ws2_32`) |
|
||||
| `net_init()` | 芯片初始化 / 复位 / 配置 IP·MAC / 分配 Socket 缓冲 | 仅确保 `WSAStartup`(幂等);ip/mask/gw/mac 四参数全部忽略 |
|
||||
| fd 模型 | 控制块索引 0~7 | 相同(并行数组 `s_ws[]` 存 Winsock 句柄,不侵入共享的 `net_sock_t`) |
|
||||
| 并发 / 阻塞 | 消息队列 + `netTask` 串行化 | 单线程直调;所有 Socket 创建即非阻塞(FIONBIO),主循环直接轮询 |
|
||||
| 并发 / 阻塞 | 消息队列 + `netTask` 串行化 | 单线程直调;Winsock 句柄创建即非阻塞(FIONBIO),阻塞语义按控制块 `blocking` 属性在包装层模拟(`net_recv` 轮询等待 / `net_recv_nb` 立即返回,N34 阶段 1.5 与 MCU 端对齐) |
|
||||
| `net_poll()` | CH395F 中断轮询 + 状态机更新 | 空操作(返回 0,非阻塞 I/O 直接反映事件) |
|
||||
| `net_accept()` | 单连接:监听 Socket 自身即数据通道(返回监听 Socket 自身) | Winsock 语义:返回独立于监听 Socket 的新 fd(监听 Socket 保持 `LISTENING`) |
|
||||
| 事件回调 | `net_poll` 检测后主动触发 | 仅存储,不主动触发(上层自行非阻塞轮询感知) |
|
||||
@@ -174,7 +176,8 @@ int net_close_sock(net_sock_t *p_sock);
|
||||
- **消息队列由 CubeMX 创建**:在 `MX_FREERTOS_Init()`(`osKernelInitialize()` 之后)中调用 `osMessageQueueNew(8, sizeof(net_msg_t), NULL)` 创建
|
||||
- **队列句柄全局可见**:通过 `extern osMessageQueueId_t netMsgQueueHandle` 引用
|
||||
- **`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()` 按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方——等待期间调用方在自己上下文睡通知,netTask 零占用(消除 E-02 头阻塞)。副作用:`buf` 指针须保持有效至返回;close / 网络重建 / 陈旧重同步会**显式取消并通知**(`NET_ERR_CONNRESET` 等),调用方永不挂死
|
||||
- **recv 语义主流化(N34 阶段 1.5,2026-10-13)**:对齐 POSIX/Python——**模式归使用者、超时归调用方、API 不强加默认预算**。`net_recv()` 默认**无限阻塞**(`NET_RECV_TIMEOUT_MS` 30s 默认已删),唤醒源 = 数据 / EOF / close / 网络重建(CONNRESET),会话死亡由对账① ~400ms 判死兜底;非阻塞具名入口 `net_recv_nb()`(原 `NET_MSG_DONTWAIT` 路径转正),模式属性 `net_setblocking(fd, on)` 挂 socket 控制块;超时首选 `net_select(...)`,`net_recv_to(...)` 保留为**显式预算**便捷入口。内部看门狗降级为 **tripwire**:挂起超 60s 只 `[ERR]` 留痕一条(完成逻辑缺位探针),不再伪造 TIMEDOUT 返回值。⚠ **文档契约(嵌入式特有)**:IWDG `required_mask` 内任务**禁止无限阻塞 recv**——必须 `net_select` 超时或 `net_recv_to` 显式预算("超时归使用者"对受监控任务是义务不是选项)
|
||||
- **队列阻塞语义**:只有 `osMessageQueuePut` 失败(8 长度固定队列,极端情形)时公共 API 阻塞;recv 挂起等待**不计入**队列占用(完成前队列中无消息滞留)
|
||||
- **哪些操作要求 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)
|
||||
|
||||
@@ -919,7 +922,7 @@ int net_send(int sockfd, const void *buf, int len, int flags);
|
||||
| `flags` | 通常为 0 |
|
||||
| 返回值 | 实际发送字节数,失败返回 -1 |
|
||||
|
||||
#### net_recv - 接收数据
|
||||
#### net_recv - 接收数据(默认无限阻塞)
|
||||
|
||||
```c
|
||||
int net_recv(int sockfd, void *buf, int len, int flags);
|
||||
@@ -930,12 +933,43 @@ int net_recv(int sockfd, void *buf, int len, int flags);
|
||||
| `sockfd` | socket 描述符 |
|
||||
| `buf` | 接收缓冲区(须保持有效至调用返回) |
|
||||
| `len` | 缓冲区大小 |
|
||||
| `flags` | 0=阻塞,`NET_MSG_DONTWAIT`=非阻塞 |
|
||||
| 返回值 | 实际接收字节数 (>0);`0`=对端关闭(net 层随即自动重监听);`-1`=错误(非阻塞无数据=`NET_ERR_WOULDBLOCK`;阻塞预算内无数据=`NET_ERR_TIMEDOUT`,默认 30s `NET_RECV_TIMEOUT_MS`;close/网络重建/陈旧重同步取消=`NET_ERR_CONNRESET`) |
|
||||
| `flags` | 0=按 socket 阻塞属性等待,`NET_MSG_DONTWAIT`=单次强制非阻塞 |
|
||||
| 返回值 | 实际接收字节数 (>0);`0`=对端关闭(net 层随即自动重监听);`-1`=错误(非阻塞无数据=`NET_ERR_WOULDBLOCK`;close/网络重建/陈旧重同步取消=`NET_ERR_CONNRESET`;**阻塞形态无"预算内无数据"一档——默认无限等待,超时是调用方策略**) |
|
||||
|
||||
> **L3 pending 化(2026-09-22)**:无数据时阻塞接收**不占 netTask**——登记挂起请求(每 fd 至多一个,禁止覆盖:覆盖会丢失前调用方通知 = 永久挂死)后**立即返回**;完成按主循环节拍检查(数据/到点/断连,看门狗 1s)并经任务通知唤醒调用方。等待期间调用方在自己上下文睡通知,netTask 全程零占用(消除 E-02 头阻塞)。
|
||||
> **语义主流化(N34 阶段 1.5,2026-10-13)**:对齐 POSIX/Python——`net_recv()` 默认**无限阻塞**(旧 `NET_RECV_TIMEOUT_MS` 30s 默认预算已删,API 不再强加隐藏值)。唤醒源 = 数据 / EOF / close / 网络重建(`NET_ERR_CONNRESET`);会话死亡由对账①在 ~400ms 内判死兜底(§3.6),不存在"对端消失后永久挂死"窗口。需要"等但不无限等":首选 `net_select(r,w,e,timeout_ms)`(§7.1),或 `net_recv_to()` 显式预算。
|
||||
>
|
||||
> **L3 pending 化(2026-09-22)**:无数据时阻塞接收**不占 netTask**——登记挂起请求(每 fd 至多一个,禁止覆盖:覆盖会丢失前调用方通知 = 永久挂死)后**立即返回**;完成按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方。等待期间调用方在自己上下文睡通知,netTask 全程零占用(消除 E-02 头阻塞)。内部 tripwire(原看门狗降级):挂起超 60s 只 `[ERR]` 留痕一条("完成逻辑缺位"探针),**绝不伪造 TIMEDOUT 返回值**。
|
||||
>
|
||||
> ⚠ **文档契约(嵌入式特有,Python 没有)**:IWDG `required_mask` 内任务(当前产品掩码 `0x1F7`:default/net/ftp/ui/timeSync/adc/iec104/protect)**禁止无限阻塞 recv**——必须 `net_select` 超时或 `net_recv_to` 显式预算并周期喂心跳,否则"对端连上不发数据"这种完全正常的场景 = IWDG 复位。
|
||||
|
||||
#### net_recv_to - 接收数据(调用方指定超时,分片阻塞版)
|
||||
#### net_recv_nb - 接收数据(非阻塞具名入口)
|
||||
|
||||
```c
|
||||
int net_recv_nb(int sockfd, void *buf, int len);
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `sockfd` / `buf` / `len` | 同 `net_recv` |
|
||||
| 返回值 | >0 字节数;`0`=对端关闭;`-1`=无数据(`NET_ERR_WOULDBLOCK`)或真错误(`net_get_errno` 可查) |
|
||||
|
||||
原 `NET_MSG_DONTWAIT` 路径转正(N34 阶段 1.5):等价 `net_recv(sockfd, buf, len, NET_MSG_DONTWAIT)`,单次强制非阻塞、不改变 socket 的 blocking 属性。受 IWDG 监控 / 有周期职责的任务的推荐默认入口(生产用户:`ui_net_sink` :7003、`iec104_serv` :2404)。
|
||||
|
||||
#### net_setblocking - 设置阻塞模式属性
|
||||
|
||||
```c
|
||||
int net_setblocking(int sockfd, int on);
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `sockfd` | socket 描述符 |
|
||||
| `on` | 1=阻塞(`net_socket()` 默认);0=非阻塞 |
|
||||
| 返回值 | 0 成功;-1 失败(描述符无效/未占用) |
|
||||
|
||||
模式挂 socket 控制块属性(对齐 BSD `O_NONBLOCK` / Python `setblocking`):非阻塞模式下 `net_recv()` 无数据立即返回 `NET_ERR_WOULDBLOCK`(EOF 判定不变:CLOSED/session_dead 无数据仍返回 0 并触发自动重监听)。与超时预算**正交**——模式是粘滞属性,单次预算用 `net_recv_to` / `net_select`。直写控制块单字节(先例 `net_set_event_cb`),任意上下文可调。
|
||||
|
||||
#### net_recv_to - 接收数据(调用方指定超时,显式预算分片版)
|
||||
|
||||
```c
|
||||
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
|
||||
@@ -944,10 +978,10 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `sockfd` / `buf` / `len` | 同 `net_recv` |
|
||||
| `timeout_ms` | 本片等待预算(ms,**必须 >0**;0 会被解释为"默认 30s",恰是本接口要规避的长阻塞,直接拒 `NET_ERR_INVAL`) |
|
||||
| `timeout_ms` | 本片等待预算(ms,**必须 >0**;0 = `net_recv` 的无限等待语义,恰与本接口"有界"存在理由相反,直接拒 `NET_ERR_INVAL`——N34 阶段 1.5 起本接口是**显式预算**入口,不再有"默认 30s"隐藏值) |
|
||||
| 返回值 | >0=字节数(允许部分读,TCP 分段);`0`=对端关闭(排空残留后);`-1`=真错误(`net_get_errno` 可查);**-2=本片预算内无数据**(**不是 errno**,不得依赖 errno 判超时——`s_net_errno` 是跨任务共享全局量) |
|
||||
|
||||
调用方累计自身总预算:见到 -2 发起下一片,直到收齐 / EOF / 总预算耗尽。生产用途:lftpd 数据行读、timeSync 帧读(E-02 头阻塞修复,完整例 §11.2)。
|
||||
调用方累计自身总预算:见到 -2 发起下一片,直到收齐 / EOF / 总预算耗尽。生产用途:lftpd 数据行读、timeSync 帧读(E-02 头阻塞修复,完整例 §11.2);受 IWDG 监控任务的长等待必须走本接口(或 `net_select`)+ 每片喂心跳。
|
||||
|
||||
#### net_sendto - 发送 UDP 数据
|
||||
|
||||
@@ -1007,11 +1041,11 @@ void net_process_messages(void);
|
||||
处理其他任务投递的请求(send/recv/connect/accept/close)。每次调用先跑 4 个**非阻塞** tick,再取队列消息(0 超时出队):
|
||||
|
||||
1. **D10 重监听重试**(`net_listener_retry_tick`):断连后监听器恢复的到点重试(30ms 间隔 / 2s 时限,§3.6)
|
||||
2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点 ③断连 检查每个挂起请求并通知调用方(看门狗 1s)
|
||||
2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点(仅显式预算) ③断连 检查每个挂起请求并通知调用方;④ tripwire:挂起超 60s 只 `[ERR]` 留痕一次,不强制了结(N34 阶段 1.5)
|
||||
3. **Trap 22 芯片/本层对账**(`net_reconcile_tick`):200ms 周期修正**三类**失同步(①②⑤,§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)。消息类型仅 6 种(send/recv/close/connect/listen/accept)——自愈类消息 `NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 已随 API 退役删除(N34 阶段1)。
|
||||
> `net_msg_t.timeout_ms`:L3 显式预算(`net_recv_to()`),0=**无限等待**(N34 阶段 1.5 删除 `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 - 发送数据(内部版本)
|
||||
|
||||
@@ -1155,9 +1189,9 @@ void tcp_client_reconnect(void)
|
||||
net_send(sock, "heartbeat", 9, 0);
|
||||
}
|
||||
|
||||
/* 检查接收 */
|
||||
/* 检查接收(N34 阶段 1.5:非阻塞具名入口 net_recv_nb) */
|
||||
char buf[128];
|
||||
int len = net_recv(sock, buf, sizeof(buf) - 1, NET_MSG_DONTWAIT);
|
||||
int len = net_recv_nb(sock, buf, sizeof(buf) - 1);
|
||||
if (len > 0) {
|
||||
buf[len] = '\0';
|
||||
printf("收到: %s\r\n", buf);
|
||||
@@ -1246,18 +1280,18 @@ static int read_time_frame(int conn_sock, uint8_t buf[FRAME_LEN]) {
|
||||
|
||||
### 12.4 如何处理连接断开?
|
||||
|
||||
**方式一:阻塞接收**
|
||||
**方式一:阻塞接收(无限等待——⚠ 仅限不受 IWDG 监控且无周期职责的任务,§10.1 文档契约)**
|
||||
```c
|
||||
int len = net_recv(sock, buf, sizeof(buf), 0);
|
||||
if (len <= 0) {
|
||||
/* 连接断开或错误 */
|
||||
/* 连接断开或错误(EOF / CONNRESET / 真错误,net_get_errno 可查) */
|
||||
net_close(sock);
|
||||
}
|
||||
```
|
||||
|
||||
**方式二:非阻塞接收**
|
||||
**方式二:非阻塞接收(`net_recv_nb`,N34 阶段 1.5 转正)**
|
||||
```c
|
||||
int len = net_recv(sock, buf, sizeof(buf), NET_MSG_DONTWAIT);
|
||||
int len = net_recv_nb(sock, buf, sizeof(buf));
|
||||
if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
|
||||
/* 没有数据 */
|
||||
} else if (len <= 0) {
|
||||
@@ -1407,6 +1441,7 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
|
||||
|
||||
| 日期 | 变更 |
|
||||
|------|------|
|
||||
| 2026-10-13 | **N34 阶段 1.5 recv 语义主流化**(规格 `docs/网络/N34-自愈审计/实施方案.md` §3.5):`net_recv()` 默认**无限阻塞**(删 `NET_RECV_TIMEOUT_MS` 30s 默认预算,唤醒源=数据/EOF/close/网络重建 CONNRESET);新增 `net_recv_nb()`(`NET_MSG_DONTWAIT` 路径转正)与 `net_setblocking(fd, on)`(模式挂 socket 控制块属性,`net_sock_t.blocking`,默认阻塞);`net_recv_to()` 保留为**显式预算**便捷入口(`timeout_ms=0` 语义从"默认 30s"改为"无限"故仍拒 INVAL);内部 recv 看门狗降级为 **tripwire**(挂起超 60s 只 `[ERR]` 留痕一次,不再"到点 +1s 强制了结"伪造 TIMEDOUT);⚠ 新增**文档契约**:IWDG `required_mask` 内任务禁止无限阻塞 recv(必须 `net_select` 超时或 `net_recv_to` 显式预算);`ui_net_sink` / `iec104_serv` 迁移 `net_recv_nb`,lftpd / timeSync 维持显式预算;模拟器 `net_socket_sim.c` 镜像三 API(blocking 属性包装层模拟) |
|
||||
| 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-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 成本) |
|
||||
|
||||
@@ -48,7 +48,7 @@ flowchart TB
|
||||
NPM["netProcessMessages<br/>先跑 4 个 tick,再排空队列分派"]
|
||||
NP["netPoll<br/>INT# GPIO 轮询 → 全局中断 → 各 Socket SINT → 每轮一批"]
|
||||
T1["① netListenerRetryTick<br/>D10 重监听重试:30ms / 2s 时限"]
|
||||
T2["② netPendingTick<br/>L3 挂起 recv:①数据 ②到点 ③断连 ④看门狗 1s"]
|
||||
T2["② netPendingTick<br/>L3 挂起 recv:①数据 ②到点(仅显式预算) ③断连 ④tripwire 60s 只留痕"]
|
||||
T3["③ netReconcileTick<br/>Trap 22 对账:200ms / 去抖 / 冷却(①②⑤)"]
|
||||
T4["④ netListenerPump<br/>N-32 唯一重建入口:100ms / 去抖 / 冷却"]
|
||||
DISP["队列分派 switch<br/>SEND · RECV · CLOSE<br/>CONNECT · LISTEN · ACCEPT"]
|
||||
@@ -129,8 +129,9 @@ flowchart TB
|
||||
| `net_accept` | `NET_MSG_ACCEPT` | `net_accept_locked` | 无(先 `net_poll` 泵一次 + 状态判定) | 监听 fd 自身(单连接)/ -1(WOULDBLOCK) | 单连接:监听 Socket 在 CONNECT 后**自身即数据通道**,返回的就是它;CLOSED + auto_relisten 时触发**非阻塞** `net_listener_try`;**accept 路径不做自愈**——单次状态读会误判"刚 listen 未生效"致重建风暴(2026-09-23 实测),统一交给 `net_listener_pump`(N-32 单一重建入口) |
|
||||
| `net_connect` | `NET_MSG_CONNECT` | `net_connect_locked` | `set_proto_type` + `open_socket`(3×500ms 重试)+ 清残留中断 + `set_sour_port` / `set_des_ip`(大端数组)/ `set_des_port` + `tcp_connect` | 0 = 请求已发出(**异步**)/ -1 | open **先于**寄存器写入(open 会复位寄存器);open 后清残留 DISCONNECT,防新连接秒断(Trap 14/17);目的 IP 字节序写反 = CONNECT TIMEOUT(Trap 16);返回 0 ≠ 已建立——调用方以 `net_sock_state` 轮询等 ESTABLISHED |
|
||||
| `net_send` | `NET_MSG_SEND` | `net_send_locked` | `ch395f_write_send_buf`(分块)+ 等 SENBUF_FREE(`net_poll` 轮询) | 实际字节数 / -1 | 分块 ≤ `NET_SEND_CHUNK_MAX` = 1KB(**硬件发送 FIFO 硬上限**,Trap 20:写穿 FIFO = 数据错位);`send_ready` 门控用 `net_poll` 轮询而非 osDelay;短写**必须**告警——静默截断会让对端永久等待剩余字节(2026-09-21 实证:UI 推流应答截断 → 客户端失联) |
|
||||
| `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` | `NET_MSG_RECV`(timeout=0→**无限等待**,N34 阶段 1.5 删 30s 默认) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / CONNRESET;阻塞形态无 TIMEDOUT 一档) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞(`blocking` 属性=1 且未 DONTWAIT)→ **登记挂起后立即返回**(L3,netTask 零占用),唤醒源=数据/EOF/close/网络重建;DONTWAIT / `blocking=0` + CLOSED/session_dead → EOF + 触发重监听;⚠ IWDG 受监控任务禁用无限形态(`net_socket.h` 文档契约) |
|
||||
| `net_recv_nb` | `NET_MSG_RECV`(flags=DONTWAIT) | `net_recv_locked` | 同上 | >0 / 0 / -1(WOULDBLOCK 等) | 原 `NET_MSG_DONTWAIT` 路径转正的具名入口(N34 阶段 1.5);不改变 `blocking` 属性;受 IWDG 监控 / 有周期职责任务的推荐默认(ui_net_sink / iec104_serv 已迁) |
|
||||
| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套 + **显式预算**入口(N34 阶段 1.5,等价 Python settimeout,无隐藏默认值):调用方把长等待切成有界片(如剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(0 = 无限等待,与本接口"有界"存在理由相反);超时**只凭 -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_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **N34 阶段1 已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 |
|
||||
| ~~`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 拒绝);去抖/冷却沿用对账框架 |
|
||||
@@ -141,7 +142,8 @@ flowchart TB
|
||||
|---|---|---|---|
|
||||
| `net_socket(domain,type)` | 分配空闲控制块(`alloc_socket` 线性扫描 `in_use=0`)→ CREATED | `alloc_socket` + `net_listener_clear` + `net_pending_clear` | 新 fd 不得继承上一次的待重试/挂起状态(复用描述符的经典坑);domain 仅 `NET_AF_INET` |
|
||||
| `net_bind(sockfd,addr,addrlen)` | TCP:仅记录本地端口(port=0 → 动态端口 49152 起递增)→ BOUND,**无芯片命令**;UDP:完整开 Socket → UDP_OPEN | UDP 路径:`set_proto_type(UDP)` + `set_sour_port` + `set_des_ip(0xFFFFFFFF)` + `set_des_port(0)` + `open_socket`(Socket 4~7 兜底重设缓冲) | TCP 的 bind 只是"记账",真正的芯片动作在 listen/connect;UDP 直碰 SPI(§2 三例外之一);动态端口回绕 65535→49152 |
|
||||
| `net_sendto` / `net_recvfrom` | UDP 收发(直碰 SPI,不经队列) | `set_des_ip` / `set_des_port` + `write_send_buf`(分块 1KB + `net_poll` 门控);`net_poll` + `get_recv_len` + `read_recv_buf` | 同 §2 上下文约束;recv 时 UDP Server 模式前 8 字节是固定头(源 IP + 端口 + 长度),先剥头填 `src_addr` 再读数据 |
|
||||
| `net_sendto` / `net_recvfrom` | UDP 收发(直碰 SPI,不经队列) | `set_des_ip` / `set_des_port` + `write_send_buf`(分块 1KB + `net_poll` 门控);`net_poll` + `get_recv_len` + `read_recv_buf` | 同 §2 上下文约束;recv 时 UDP Server 模式前 8 字节是固定头(源 IP + 端口 + 长度),先剥头填 `src_addr` 再读数据;recvfrom 阻塞语义与 net_recv 同构(N34 阶段 1.5:blocking 属性 / DONTWAIT,无默认预算;轮询自旋实现,生产零调用) |
|
||||
| `net_setblocking(sockfd,on)` | 设置阻塞**模式属性**(N34 阶段 1.5,直写控制块,不经队列) | 写 `s_net_socks[fd].blocking` | 对齐 BSD `O_NONBLOCK` / Python `setblocking`:"模式归使用者、超时归调用方";单字节原子写(先例 `net_set_event_cb`),最坏一次在途 recv 用旧模式;`net_socket()` 默认置 1(close 的 memset 清零后新 fd 必须显式重置) |
|
||||
| `net_wait_result(kind,sockfd)` | 调用侧两段式等待:**零等待拾取** → 阻塞等回执 | `xTaskNotifyWait` ×2 | ① 零等待段兜住"入队→等待入口"间隙落地的通知(2026-09-22 台架实证丢通知);② **清除掩码必须全 1**(传 0 = 不清 → 上次结果残留被重读,run3~11 挂死/错位的真根因);③ 必须判 **pdTRUE** 而非 `v!=0`(合法结果 0 = EOF 会被误判"无通知"→ 永久挂死);④ 32 位整体作结果(32768 大字节数不截断);⑤ 本路径**禁止**加逐调用日志(曾占日志 98.6%、冲掉 [LAT] 证据行,run14 实证) |
|
||||
| `net_sock_state(sockfd)` | 跨任务只读状态快照(单字节读,无锁) | 无 | 描述符无效/未占用 → -1;值可能滞后 netTask 最新状态——用于**轮询**(客户端等 ESTABLISHED),不替代 accept/connect |
|
||||
| `net_get_sock(sockfd)` | 返回控制块指针 | 无 | netTask 上下文内用;跨任务只可读单字节 `state`(即 `net_sock_state`),不得读结构体其他成员 |
|
||||
@@ -157,7 +159,7 @@ flowchart TB
|
||||
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|
||||
|---|---|---|---|
|
||||
| `net_pending_register` | 登记挂起(`net_recv_locked` 的"无数据 + 阻塞"路径) | 写 `s_net_pending[fd]` | 每 fd 至多一个:**覆盖会丢失前调用方通知 = 永久挂死**;执行任务自身(netTask)发起的等待直接拒绝(自等自通知 = 死锁,netTask 内须用 DONTWAIT) |
|
||||
| `net_pending_tick` | 完成检查(`net_process_messages` 每轮调用,非阻塞) | ① `get_recv_len` + `read_recv_buf` ② 到点 ③ `get_recv_len` 排空 + `net_listener_try` ④ `net_pending_complete` | 按 **①数据 ②到点 ③断连** 顺序检查(逐项对齐旧自旋语义);CONNECTING/LISTENING 等状态**保持等待**(CONNECT→ESTABLISHED 是合法迁移,不做状态推断取消,防误杀正在建链的等待);**看门狗**:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + 错误日志 |
|
||||
| `net_pending_tick` | 完成检查(`net_process_messages` 每轮调用,非阻塞) | ① `get_recv_len` + `read_recv_buf` ② 到点(仅显式预算) ③ `get_recv_len` 排空 + `net_listener_try` ④ `net_pending_complete` | 按 **①数据 ②到点(仅 `net_recv_to` 显式预算;无限登记无 deadline)③断连** 顺序检查(逐项对齐旧自旋语义);CONNECTING/LISTENING 等状态**保持等待**(CONNECT→ESTABLISHED 是合法迁移,不做状态推断取消,防误杀正在建链的等待);**tripwire**(N34 阶段 1.5,原看门狗降级):挂起超 60s = 完成逻辑缺位 → 只 `[ERR]` 留痕一次,**绝不强制了结、绝不伪造 TIMEDOUT**——③ 与显式 cancel 是无限 recv 的全部唤醒责任方 |
|
||||
| `net_pending_complete` | 所有完成/取消路径的公共出口 | `xTaskNotify(caller, result)` + 清 active | 通知值 = 结果原值(>0 字节 / 0 EOF / 负错误码,与消息分派同一回传通道);失败结果同步 `s_net_errno`(netTask 上下文一致性);无在位挂起时静默返回(防重复通知) |
|
||||
| `net_pending_settle_close` | close 前**了结**本 fd 挂起 | `get_recv_len` + `read_recv_buf` + `net_pending_complete` | 关闭不得丢已到达数据(芯片缓冲 close 前可读):有数据 → 按字节数通知;无数据 → 取消(CONNRESET);先于 `ch395f_close_socket` 执行 |
|
||||
| `net_pending_cancel` / `cancel_all` | 显式取消(**必通知**) | `net_pending_complete` | 取消路径:陈旧 resync(单 fd)/ 网络重建(全部,`net_init` 开头,早于控制块清零)——"取消不通知 = 调用方永久挂死" |
|
||||
|
||||
+15
-8
@@ -44,7 +44,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|
||||
|---|---|
|
||||
| **左栏 · 调用方任务** | `net_recv` → 判"数据已在?" → **是**走快速路径;**否**(`timeout > 0`)登记 pending 后在自己上下文睡任务通知 |
|
||||
| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable)→ 4 个非阻塞 tick(`net_listener_retry_tick` D10 → `net_pending_tick` 了结挂起 → `net_reconcile_tick` Trap 22 对账 → `net_listener_pump` 监听重建唯一入口)→ 排空消息队列 |
|
||||
| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点 / EOF / 同 fd 后续操作 / 网络重建;含 1s 看门狗 |
|
||||
| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建;tripwire 60s 只留痕(N34 阶段 1.5 由 1s 看门狗降级) |
|
||||
| **回环** | 判定完成经 `xTaskNotify` 唤醒调用方(结果 = 字节数 / 负错误码) |
|
||||
|
||||
**关键性质**:
|
||||
@@ -54,6 +54,10 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|
||||
- 调用方**在自己的上下文睡眠**(任务通知),与 LwIP 的消费者线程模型同构;
|
||||
- 公开 API 与调用契约**不变**——`net_recv` / `net_recv_to` 的签名、返回值、
|
||||
超时语义均与改造前一致,既有调用方、测试与模拟器零改动。
|
||||
(**N34 阶段 1.5 更新**:本条描述 L3 改造当时口径;2026-10-13 起 `net_recv`
|
||||
默认预算 30s 删除、改为无限阻塞,`net_recv_to` 成为唯一显式预算入口——
|
||||
挂起模型与"等待不占 netTask"性质不变,变的是超时归属:见
|
||||
`docs/网络/N34-自愈审计/实施方案.md` §3.5 与 BSD 指南 §3.5。)
|
||||
|
||||
> 图形由 `tools/arch_diagram/gen_net_pending_svg.py` 生成(改流程后重跑;见
|
||||
> `docs/架构设计/图形绘制流程.md`)。
|
||||
@@ -67,7 +71,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|
||||
| 项 | 设计 |
|
||||
|---|---|
|
||||
| 存储 | **独立静态数组** `s_net_pending[NET_MAX_SOCKETS]`(不置于 `net_sock_t` 内) |
|
||||
| 字段 | `active` / `buf` / `len` / `deadline` / `registered` / `caller` |
|
||||
| 字段 | `active` / `infinite` / `tripwire_logged` / `buf` / `len` / `deadline` / `registered` / `caller`(`infinite`/`tripwire_logged` 为 N34 阶段 1.5 新增:0 预算 = 无限等待、tripwire 只留痕一次) |
|
||||
| 容量 | 每 fd **至多一个**挂起请求 |
|
||||
| 占用 | ≈24B/fd × 8 fd = **192B**(+4B 任务句柄),实测 SRAM +200B |
|
||||
|
||||
@@ -93,7 +97,7 @@ socket 生命周期解耦,与 `net_listener_retry_tick` 的重试表同构。
|
||||
| # | 条件 | 动作 | 设计要点 |
|
||||
|---|---|---|---|
|
||||
| 1 | **数据到达** | SPI 读出 + 通知(结果 = 字节数) | 由 `net_poll` 维护的 `readable` 门控,**零额外 SPI**;仅 ESTABLISHED / UDP_OPEN 态可读;spurious readable 需清除 |
|
||||
| 2 | **到点** `now >= deadline` | 通知 `NET_ERR_TIMEDOUT`(对外 -2 映射保持) | 状态无关;用有符号回绕安全差值,防 tick 回绕 |
|
||||
| 2 | **到点** `now >= deadline`(仅显式预算登记;无限等待登记无 deadline——N34 阶段 1.5) | 通知 `NET_ERR_TIMEDOUT`(对外 -2 映射保持) | 状态无关;用有符号回绕安全差值,防 tick 回绕;这是 `net_recv_to` 调用方策略的正常兑现,不是伪造 |
|
||||
| 3 | **CLOSED**(EOF/DISCONNECT) | 排空:有数据则读出;否则按 0(EOF) 完成 + `auto_relisten` + 通知 | CLOSE 先行完成挂起 = `net_close` 的 settle 钩子(排空→数据否则 CONNRESET) |
|
||||
| 4 | **同 fd 后续操作** | 先结算挂起,再执行新操作 | per-fd 串行——冻结面从"全网"缩到"本 fd" |
|
||||
| 5 | **网络重建**(`net_stack_bring_up`,芯片复位) | **取消 + 必须通知** | `net_init` 顶部 `net_pending_cancel_all(CONNRESET)`,先于芯片复位;**取消不通知 = 调用方永久挂死** |
|
||||
@@ -101,10 +105,13 @@ socket 生命周期解耦,与 `net_listener_retry_tick` 的重试表同构。
|
||||
**状态推断的克制**:CONNECTING / LISTENING 等中间态**保持等待**,**不做**状态推断式取消——
|
||||
CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的等待。
|
||||
|
||||
### 4.1 看门狗
|
||||
### 4.1 tripwire(原"看门狗",N34 阶段 1.5 降级)
|
||||
|
||||
到点后 1s 仍未了结 = 完成逻辑缺位 → **强制了结 + `DBG_ERROR`**。
|
||||
定位:防"挂起登记后永不完成"导致调用方无限期睡眠。
|
||||
挂起超 60s(`NET_RECV_TRIPWIRE_MS`)= "完成逻辑缺位"探针 → **只 `DBG_ERROR` 留痕一次,
|
||||
绝不强制了结、绝不伪造 TIMEDOUT 返回值**(旧设计"到点 +1s 强制了结"会掩盖完成路径
|
||||
缺陷,且与"无限等待"的新语义矛盾——伪造返回值违背调用方意图)。
|
||||
定位:留痕成本极低的开发期探针;唤醒责任在 ①数据 ②到点(仅显式预算)③断连 与
|
||||
显式 cancel 路径(close / 网络重建 / 陈旧重同步),见 §4 五路。
|
||||
|
||||
---
|
||||
|
||||
@@ -143,8 +150,8 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
|
||||
| **SEND 路径未覆盖** | 发送仍为分块阻塞(1s/chunk stall) | 该期间队列仍被冻结;二期 pending 化解决 |
|
||||
| **ACCEPT 路径未覆盖** | accept 仍用轮询计时 | 需 pending 化(二期) |
|
||||
| **超时精度** | 随主循环节拍(~1ms),被长操作占用时顺延 | 一期文档化、不修;二期 SEND 化后该顺延面**收窄** |
|
||||
| **看门狗未接自检** | 强结算 + `DBG_ERROR`,未写 selftest | 二期补齐 |
|
||||
| **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现挂起模型 | 该路径在模拟器上零覆盖,只能硬件验证;**监听生命周期 `net_listener.c` 亦为真机专属**(内部头只被 `net_socket.c` include,CMakeLists 不编译) |
|
||||
| **tripwire 未接自检** | 只 `DBG_ERROR` 留痕(N34 阶段 1.5 起不再强制了结),未写 selftest | 二期补齐 |
|
||||
| **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现**挂起模型内部机制**(外部语义已由包装层按 blocking 属性/预算模拟,N34 阶段 1.5) | 挂起表/tick/通知路径在模拟器上零覆盖,只能硬件验证;**监听生命周期 `net_listener.c` 亦为真机专属**(内部头只被 `net_socket.c` include,CMakeLists 不编译) |
|
||||
|
||||
**二期扩展设计**(ACCEPT / SEND 的挂起化、门禁清单、改动量)见
|
||||
`docs/任务规划/基线与待确认.md` §2.1。
|
||||
|
||||
+143
-24
@@ -10,8 +10,10 @@
|
||||
*
|
||||
* 设计要点:
|
||||
* 1. fd 采用与 MCU 端一致的"控制块索引"模型(0 ~ NET_MAX_SOCKETS-1)
|
||||
* 2. 所有 Socket 创建即置为非阻塞(ioctlsocket FIONBIO),使 net_recv /
|
||||
* net_accept 的 DONTWAIT 语义与 MCU 端一致(无数据返回 -1 + NET_ERR_WOULDBLOCK)
|
||||
* 2. Winsock 句柄创建即置非阻塞(ioctlsocket FIONBIO),阻塞语义在包装层
|
||||
* 按控制块 blocking 属性模拟(N34 阶段 1.5:模式归使用者——blocking=1
|
||||
* 时 recv 轮询等待(Sleep(1)),=0 或 NET_MSG_DONTWAIT 立即 WOULDBLOCK),
|
||||
* 使 net_recv / net_recv_nb / net_recv_to / net_accept 的语义与 MCU 端一致
|
||||
* 3. 代次号在模拟器上恒为 0(无"网络重建"概念),net_get_generation 恒返回 0
|
||||
* 4. net_get_sock 以 in_use 判活,供上层判断"监听 Socket 是否随客户端一同失效"
|
||||
* 5. Winsock 句柄存放于并行数组 s_ws[],不侵入共享的 net_sock_t 结构体
|
||||
@@ -213,6 +215,9 @@ int net_socket(int domain, int type) {
|
||||
s_socks[idx].type = (uint8_t)type;
|
||||
s_socks[idx].state = NET_SOCK_STATE_CREATED;
|
||||
s_socks[idx].ch395_sock = (uint8_t)idx;
|
||||
/* N34 阶段 1.5:阻塞模式默认 = 阻塞(与 MCU 端 net_socket 一致;
|
||||
* Winsock 句柄仍恒非阻塞,阻塞语义由包装层按本属性模拟,见文件头要点 2) */
|
||||
s_socks[idx].blocking = 1;
|
||||
s_ws[idx] = ws;
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
@@ -329,6 +334,8 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
|
||||
s_socks[nidx].type = (uint8_t)NET_SOCK_STREAM;
|
||||
s_socks[nidx].state = NET_SOCK_STATE_ESTABLISHED;
|
||||
s_socks[nidx].ch395_sock = (uint8_t)nidx;
|
||||
/* N34 阶段 1.5:accept 出的新连接同样默认阻塞模式(与 MCU 端一致) */
|
||||
s_socks[nidx].blocking = 1;
|
||||
s_ws[nidx] = cws;
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
@@ -451,32 +458,18 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(TCP)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* 函数功能:单次非阻塞 recv(内部公共体:立即返回,不等待)
|
||||
* 入口参数:sockfd - socket 描述符(已校验)
|
||||
* buf - 接收缓冲区(已校验)
|
||||
* len - 缓冲区大小
|
||||
* flags - 通常为 0,可设 NET_MSG_DONTWAIT(Socket 本就非阻塞)
|
||||
* 出口参数:buf - 填充接收数据
|
||||
* 返回值:实际接收字节数;0 = 对端关闭;-1 = 无数据(NET_ERR_WOULDBLOCK)或错误
|
||||
* 限定条件:socket 已建立
|
||||
* 函数说明:与 MCU 端语义一致——>0 有数据,0 对端关闭,<0 且 errno 区分
|
||||
* "暂无数据"与"真错误"
|
||||
* 返回值:>0 字节数;0 = 对端关闭;-1 = 无数据(WOULDBLOCK) 或错误
|
||||
* 限定条件:Winsock 句柄为非阻塞(FIONBIO)
|
||||
* 函数说明:net_recv 的非阻塞路径与 net_recv_nb 共用;错误码映射与旧实现一致
|
||||
*/
|
||||
int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
int r;
|
||||
static int recv_once(int sockfd, void *buf, int len) {
|
||||
int r = recv(s_ws[sockfd], (char *)buf, len, 0);
|
||||
|
||||
(void)flags;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
r = recv(s_ws[sockfd], (char *)buf, len, 0);
|
||||
if (r > 0) {
|
||||
s_socks[sockfd].recv_bytes += (uint32_t)r;
|
||||
s_net_errno = NET_OK;
|
||||
@@ -494,6 +487,110 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:阻塞语义模拟(内部公共体:轮询等待数据/EOF/错误)
|
||||
* 入口参数:sockfd - socket 描述符(已校验)
|
||||
* buf / len - 接收缓冲与容量(已校验)
|
||||
* has_budget - 1 = 有预算(net_recv_to);0 = 无限等待(net_recv 阻塞)
|
||||
* budget_ms - 预算毫秒(has_budget=0 时忽略)
|
||||
* 出口参数:buf - 填充接收数据
|
||||
* 返回值:>0 字节数;0 = 对端关闭;-1 = 错误;-2 = 预算耗尽无数据(仅 has_budget)
|
||||
* 限定条件:Winsock 句柄为非阻塞;调用方为 UI/服务线程(Sleep(1) 粒度可接受)
|
||||
* 函数说明:与 MCU 端挂起 recv 的对外契约对齐——MCU 端等待发生在调用方任务
|
||||
* (睡任务通知),模拟器端等价地睡 Sleep(1) 轮询;EOF/错误立即返回
|
||||
*/
|
||||
static int recv_wait(int sockfd, void *buf, int len, int has_budget, unsigned long long budget_ms) {
|
||||
unsigned long long t0 = GetTickCount64();
|
||||
|
||||
for (;;) {
|
||||
int r = recv_once(sockfd, buf, len);
|
||||
|
||||
if (r != -1 || s_net_errno != NET_ERR_WOULDBLOCK) {
|
||||
return r; /* 数据 / EOF / 真错误:立即返回 */
|
||||
}
|
||||
if (has_budget && (GetTickCount64() - t0) >= budget_ms) {
|
||||
s_net_errno = NET_ERR_TIMEDOUT;
|
||||
return -2; /* 预算耗尽(对齐 MCU net_recv_to 的 -2) */
|
||||
}
|
||||
Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(TCP,**默认无限阻塞**——N34 阶段 1.5 与 MCU 端语义对齐)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* len - 缓冲区大小
|
||||
* flags - 通常为 0;NET_MSG_DONTWAIT 单次强制非阻塞
|
||||
* 出口参数:buf - 填充接收数据
|
||||
* 返回值:实际接收字节数;0 = 对端关闭;-1 = 无数据(NET_ERR_WOULDBLOCK,仅非阻塞)或错误
|
||||
* 限定条件:socket 已建立
|
||||
* 函数说明:与 MCU 端语义一致——blocking 属性(net_setblocking)决定无数据时
|
||||
* 无限等待还是立即 WOULDBLOCK;DONTWAIT 单次覆盖属性
|
||||
*/
|
||||
int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 非阻塞:DONTWAIT 单次覆盖,或 socket 的 blocking 属性为 0 */
|
||||
if ((flags & NET_MSG_DONTWAIT) || s_socks[sockfd].blocking == 0U) {
|
||||
return recv_once(sockfd, buf, len);
|
||||
}
|
||||
/* 阻塞:无限等待(无默认预算,N34 阶段 1.5) */
|
||||
return recv_wait(sockfd, buf, len, 0, 0ULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(非阻塞具名入口,与 MCU 端同名 API 对齐——N34 阶段 1.5)
|
||||
* 入口参数:sockfd / buf / len - 同 net_recv
|
||||
* 返回值:>0 字节数;0 = 对端关闭;-1 = 无数据(WOULDBLOCK) 或错误
|
||||
* 限定条件:socket 已建立
|
||||
* 函数说明:等价 net_recv(sockfd, buf, len, NET_MSG_DONTWAIT),不改变 blocking 属性
|
||||
*/
|
||||
int net_recv_nb(int sockfd, void *buf, int len) {
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
return recv_once(sockfd, buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(**显式预算**便捷入口,与 MCU 端同名 API 对齐——N34 阶段 1.5)
|
||||
* 入口参数:sockfd / buf / len - 同 net_recv
|
||||
* timeout_ms - 本次等待预算(ms,必须 >0;0 = 无限等待语义
|
||||
* 属 net_recv 的职责,此处拒 INVAL,与 MCU 端一致)
|
||||
* 返回值:>0 字节数;0 = 对端关闭;-1 = 真错误;-2 = 预算耗尽无数据
|
||||
* 限定条件:socket 已建立
|
||||
* 函数说明:模拟器以 GetTickCount64 + Sleep(1) 轮询兑现预算
|
||||
*/
|
||||
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
if (timeout_ms == 0U) {
|
||||
/* 0 = 无限等待(net_recv 的语义),与本接口"有界"存在理由相反 → 拒绝 */
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
return recv_wait(sockfd, buf, len, 1, (unsigned long long)timeout_ms);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:发送 UDP 数据报(含目标地址)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
@@ -634,6 +731,28 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(与 MCU 端同名 API 对齐——N34 阶段 1.5)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* on - 1 = 阻塞(net_socket/net_accept 默认);0 = 非阻塞
|
||||
* 返回值:0 成功;-1 失败(描述符无效/未占用)
|
||||
* 限定条件:socket 已创建
|
||||
* 函数说明:Winsock 句柄恒非阻塞(FIONBIO,UI 主循环安全底线),本属性只驱动
|
||||
* 包装层的等待策略(net_recv 无限轮询 / 立即 WOULDBLOCK),
|
||||
* 语义与 MCU 端"模式挂控制块属性"一致
|
||||
*/
|
||||
int net_setblocking(int sockfd, int on) {
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_socks[sockfd].blocking = (on != 0) ? 1U : 0U;
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:获取 socket 最后错误码
|
||||
* 入口参数:无
|
||||
|
||||
@@ -310,7 +310,9 @@ class RemoPeer:
|
||||
# --- FTP head-of-line stress (E-02) ---
|
||||
def ftp_stress_worker(self, start_evt, stop_evt, hold_s):
|
||||
"""Wait for the stress window to open, then hold an idle FTP login:
|
||||
lftpd read_line -> blocking net_recv(0) inside netTask (NET_RECV_TIMEOUT_MS)
|
||||
lftpd read_line -> blocking net_recv(0) inside netTask (old 30s default
|
||||
budget NET_RECV_TIMEOUT_MS, removed in N34 phase 1.5; today the wait is
|
||||
an L3 pending recv -> this scenario must NOT freeze the queue anymore)
|
||||
-> every queued message (incl. the LCDMEM response) waits behind it (E-02)."""
|
||||
if not start_evt.wait(120.0):
|
||||
sys.stdout.write("[peer] ftp stress: window never opened\n")
|
||||
|
||||
Reference in New Issue
Block a user