feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73)
前置来自 #57 SNTP 调研:旧 net_sendto / net_recvfrom 由调用方直调 SPI 且无限自旋收包,既违约「netTask 是芯片唯一访问者」,又把 IWDG 预算吃在自旋里。 改造面 - 新增 Drivers/BSP/NET/net_udp.c/.h(真机专属,模拟器不编译): bind/set_peer/sendto/recvfrom 四条执行体 + 挂起收包表 + tick; net_msg_type_t 加四型,公开 API 一律「入队 → 睡在通知上」。 - 源地址出参不搭 net_msg_t(按值拷贝,AGENTS 禁加回传字段):改住 net_udp.c 的 per-fd 槽,netTask 就地写回调用方内存,通知值只带 结果码;每条出口都清槽,泄漏 = 该 fd 永久 BUSY。 - 发送复用 net_send_locked,不写第二份分块逻辑;「会话是否已死」 收成一条判据 net_send_state_down()(TCP 看 ESTABLISHED、UDP 看 UDP_OPEN),原先 4 处各写一遍。 - 顺带修掉真缺陷(新条 TRAP-NET-22):旧 sendto 把 DesIP 写成具体 IP,芯片静默切 UDP Client,而旧 recvfrom 无条件剥 8 字节 → SNTP 这类 48 B 应答丢载荷前 8 字节并交出垃圾源地址。加 udp_mode(塞进 原有 padding,sizeof 不变)、bind 恒 SERVER、显式对端 sendto 切 CLIENT、set_peer(fd, NULL) 轻量回 SERVER,仅 SERVER 且 avail >= 8 才剥头。 - 截断定案:一次调用交付一个数据报,buf 装不下的尾巴用 64 B 栈上 scratch 排空(不用 clear_recv_buf——它连后面已到达的包一起丢)。 - 取消点接齐:net_init 重建 cancel_all、net_socket 分配 fd clear、 net_close settle_close + clear、重 bind 前先结算等包者, net_process_messages 第 5 个 tick 位挂 net_udp_tick。 - 收紧两处契约(原实现无生产调用者):UDP 未 bind 一律 -5; net_recv / _nb / _to 拒 DGRAM(绕过剥头就是静默吃 8 字节); NET_ERR_NOT_IN_NETTASK(-12) 退役为历史常量并写明原因。 验证 - 主机单测 test/host/net_socket 全套 616/616 绿(改造前 470):新 分片 ns_s7_udp.c 20 组,改写 440 / 555 / 560 组;覆盖率 net_udp.c 234/236(99.2%)、net_socket.c 956/969(98.7%), scripts/test_coverage_baseline.json 随本提交登记新文件。 - 测试抓到 4 个被测码真 bug:自等守卫误用调用侧谓词、芯片长度被读 两次、槽指针取在 fd 合法性检查之前、重 bind 静默作废等待者。 - Keil ARMCC 0 Error(s) 0 Warning(s);net_udp.o Code 1,358 / ZI 224 (8 fd × 28 B,其余模块零增长),全仓 RW_IRAM1 72.2%;内存台账 受管区按门禁提示用 --write / --update 机器回填,未手改数字。 - pre-commit 全量钩子(含 doc-links、coding-standard、mem-budget、 generated-fresh、bsp-err、registration、nested-comment)全绿。 限度 - 本轮无板子:真机回归 5 条(两端互通、set_peer(NULL) 能否恢复收 广播、截断排空后下一包完整性、挂起 recv 期间 IWDG 余量、bind 写 DesIP 与 open 先后的真芯片效果)已写进覆盖率口径「台架必验」。 - scripts/stack_report.py 退出码 1 是 10-26 起挂在账上的 iec104Task 栈债(margin 1,016 < 1,024,见 docs/代码规范/pre-commit使用说明.md: 122);本轮只让 net_bind 的 UDP 分支多一层静态帧(1,544 → 1,600), netTask margin 1,888 与 timeSyncTask 1,152 不受影响。抬栈要过 mem-budget 台账,不在本分支自行处理。 - 登记簿销项按仓库约定留给主工作树,本分支不动 docs/任务规划/**。
This commit is contained in:
+168
-37
@@ -39,11 +39,31 @@ typedef enum {
|
||||
NET_MSG_LISTEN,
|
||||
NET_MSG_ACCEPT,
|
||||
NET_MSG_DROP_CONN, /* 主动拆会话、保监听(见 net_drop_conn) */
|
||||
/* UDP 队列化改造(2026-11-03,工单前置 = SNTP 客户端 #57):UDP 三个碰芯片的
|
||||
* 动作从"调用方上下文直调 SPI"改为交 netTask 串行执行,字段用法:
|
||||
* NET_MSG_UDP_BIND - sockfd + len=本地端口(主机序,0 = 执行体内取动态号)
|
||||
* 恒 DesIP=0xFFFFFFFF 服务器模式(bind 不选对端,
|
||||
* 客户端模式见 NET_MSG_UDP_PEER / SENDTO 的 addr)
|
||||
* NET_MSG_UDP_SENDTO - buf/len=载荷 + addr=本包目的地址(按值拷入,**入参**方向)
|
||||
* NET_MSG_UDP_RECVFROM - buf/len=载荷 + timeout_ms=等待预算 + flags
|
||||
* ⚠ 源地址出参(src_addr / addrlen 两个指针)**不走本结构**:结构按值拷入
|
||||
* 队列,指针若在 int 字段里过桥,64 位主机单测构建会被截断,且违反
|
||||
* AGENTS「勿给 net_msg_t 加回传用字段」。改走 net_udp.c 的 per-fd 挂起槽
|
||||
* (调用方在入队前登记自己的指针,netTask 完成后写进调用方内存 + 通知值回码),
|
||||
* 与 TCP 挂起 recv 把数据直接写进调用方 buf 是同一个模型。
|
||||
* 详见 docs/网络/BSD_Socket_API_使用指南.md §6 */
|
||||
NET_MSG_UDP_BIND,
|
||||
NET_MSG_UDP_SENDTO,
|
||||
NET_MSG_UDP_RECVFROM,
|
||||
/* 静默回收认领 + 手动自愈 API 退役(docs/网络/网络自愈架构设计.md §4.1、§4.2):
|
||||
* NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删——
|
||||
* 静默回收由对账⑤自动认领(net_reconcile_tick),重建由 reconciler
|
||||
* 形态③④/D10 单一
|
||||
* 所有者负责;手动自愈 API 面退役(生产调用者本就为零) */
|
||||
/* UDP 模式切换(追加在枚举末尾:既有成员编号是主机单测/板测的钉值对象,
|
||||
* 不在中间插入)——addr 按值携带对端则切客户端模式,addrlen == 0 则回
|
||||
* 服务器模式;不重开 Socket,只写 DesIP/DesPort,语义见 net_udp_set_peer */
|
||||
NET_MSG_UDP_PEER
|
||||
} net_msg_type_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -331,60 +351,132 @@ int net_setblocking(int sockfd, int on);
|
||||
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
|
||||
|
||||
/*
|
||||
* 函数功能:发送 UDP 数据
|
||||
* 函数功能:发送 UDP 数据报(**走消息队列**,任意任务可调用)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 数据缓冲区
|
||||
* len - 数据长度
|
||||
* flags - 通常为 0
|
||||
* dest_addr - 目标地址
|
||||
* addrlen - 地址长度
|
||||
* len - 数据长度(>0)
|
||||
* flags - 目前不使用,传 0(与 net_send 同)
|
||||
* dest_addr - 本包目的地址;NULL = 沿用本 Socket 当前 DesIP
|
||||
* addrlen - 地址长度(NULL 目的时传 0 即可)
|
||||
* 返回值:实际发送字节数 int > 0
|
||||
* -1 失败,精确码经本任务错误码槽给出(net_get_errno()):
|
||||
* NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_TIMEDOUT /
|
||||
* NET_ERR_NOT_IN_NETTASK(调用上下文不对,见「限定条件」)
|
||||
* 限定条件:**仅限本层执行任务(net_init 的调用者,NET 层 = netTask)调用**;
|
||||
* socket 已创建且为 DGRAM;指针非空
|
||||
* 函数说明:1. 设置目标地址后调用 CH395F 发送
|
||||
* 2. ⚠ 本接口**不走消息队列**:它在调用方上下文里直接 net_poll +
|
||||
* 写芯片缓冲,与 net_send/net_recv/net_connect 那一族(入队交 netTask
|
||||
* 串行执行)不是同一条路径。别的任务调用它 = 与本层的 net_poll 抢
|
||||
* 同一根 SPI2(TRAP-NET-14 的根因形态),故加上下文守卫直接拒掉,
|
||||
* 回 NET_ERR_NOT_IN_NETTASK。**该码重试永远无意义**(既非忙也非超时):
|
||||
* 要么把调用挪进执行任务,要么改用走队列的 net_send——没有第三条。
|
||||
* 3. 「把它改成走消息队列」是已知后续项,未做的原因:UDP 的目的地址
|
||||
* 与源地址回传都要穿过 net_msg_t,而 net_msg_t 按值拷入队列、
|
||||
* 错误码只搭任务通知值回来(AGENTS 明文「勿给 net_msg_t 加回传字段」),
|
||||
* 真要串行化得先设计 per-task 结果槽并配真机回归(2026-10-24 定案:
|
||||
* 本轮只把危险面关掉,串行化仍未做)。
|
||||
* 4. 全仓当前**无生产调用者**(只有 test/board 与模拟器),正因如此
|
||||
* 这条守卫要在有人开始用之前就在。
|
||||
* NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_TIMEDOUT / NET_ERR_BUSY
|
||||
* 限定条件:socket 已创建(DGRAM 用法须先 net_bind 或 net_udp_set_peer 打开芯片侧
|
||||
* Socket);net_init() 已执行;**任意任务上下文**(含 netTask 自身,
|
||||
* netTask 内直接执行不入队——自投递给自身 = 死锁)
|
||||
* 函数说明:1. 目的地址是**入参**,按值拷进 net_msg_t.addr 随消息进 netTask,
|
||||
* 与 net_connect 传地址同一条通道;结果(已发字节数/错误码)
|
||||
* 搭任务通知值回来(AGENTS「勿给 net_msg_t 加回传字段」)。
|
||||
* 2. 带显式目的地址 = 芯片 DesIP 切到该 IP → 本 Socket 进入
|
||||
* **UDP 客户端模式**(p_sock->udp_mode = NET_UDP_MODE_CLIENT):
|
||||
* 此后芯片只收该 IP 的包且**不再添加 8 字节信息头**,
|
||||
* net_recvfrom 相应地不剥头、源地址回填该固定对端。
|
||||
* 要回服务器模式(任意源 + 带信息头)调 net_udp_set_peer(sockfd, NULL)。
|
||||
* 3. dest_addr=NULL 时不改 DesIP:服务器模式下 DesIP=0xFFFFFFFF 即
|
||||
* 向 255.255.255.255 广播(板级 UDP 广播测试的用法),模式不变。
|
||||
* 4. 分块上限 NET_SEND_CHUNK_MAX(1KB 发送 FIFO,TRAP-NET-16),
|
||||
* 门控 SENBUF_FREE(TRAP-NET-04);单块等待超 NET_SEND_SPIN_MS
|
||||
* 登记挂起 send、由 net_send_pending_tick 续传 → **netTask 不被
|
||||
* 一次大报发送占住**(与 TCP 发送同一套机制)。
|
||||
* 5. 短写:返回值 < len 表示未全部交付(停滞预算 NET_SEND_TIMEOUT_MS
|
||||
* 用尽),调用方须检查返回值——同 net_send 契约。
|
||||
* 6. 手册 §9.2.4:UDP 一包最大 1472 字节;本层不做长度上限校验
|
||||
* (超出只会分多块写、对端收不到完整包),调用方自负。
|
||||
*/
|
||||
int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
const struct net_sockaddr *dest_addr, int addrlen);
|
||||
|
||||
/*
|
||||
* 函数功能:接收 UDP 数据
|
||||
* 函数功能:接收 UDP 数据报(含源地址;**走消息队列**,任意任务可调用)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* len - 缓冲区大小
|
||||
* flags - 通常为 0;NET_MSG_DONTWAIT 单次强制非阻塞
|
||||
* src_addr - 输出:发送方地址
|
||||
* addrlen - 输入输出:地址长度
|
||||
* 返回值:实际接收字节数(0 = 无数据可报的边界形态见说明) int >= 0
|
||||
* len - 缓冲区大小(>0)
|
||||
* flags - 0 = 按 socket 的 blocking 属性等待;NET_MSG_DONTWAIT = 单次非阻塞
|
||||
* src_addr - 输出:发送方地址(可为 NULL)
|
||||
* addrlen - 输入输出:src_addr 结构容量/实际长度(可为 NULL)
|
||||
* 返回值:实际接收字节数 int >= 0
|
||||
* -1 失败,精确码经本任务错误码槽给出(net_get_errno()):
|
||||
* NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_WOULDBLOCK /
|
||||
* NET_ERR_NOT_IN_NETTASK(调用上下文不对,见「限定条件」)
|
||||
* 限定条件:**仅限本层执行任务(net_init 的调用者,NET 层 = netTask)调用**;
|
||||
* socket 已创建且为 DGRAM;指针非空
|
||||
* 函数说明:1. 读取 CH395F 接收缓冲区,获取对端地址
|
||||
* 2. 阻塞语义与 net_recv 同:blocking 属性下无数据
|
||||
* 无限等待,DONTWAIT / 非阻塞属性立即 NET_ERR_WOULDBLOCK;
|
||||
* 本接口为轮询自旋实现(历史遗留,UDP 专用),受 IWDG 监控的
|
||||
* 任务同样禁止对其无限等待
|
||||
* NET_ERR_TIMEDOUT / NET_ERR_CONNRESET(close/重建取消)/ NET_ERR_BUSY
|
||||
* 限定条件:socket 已创建且为 DGRAM;net_init() 已执行;**任意任务上下文**
|
||||
* (netTask 内直接执行不入队;netTask 内的阻塞等待被登记守卫拒绝 →
|
||||
* 须用 NET_MSG_DONTWAIT)
|
||||
* 函数说明:1. 与 net_recv 同一条路径(入队 → netTask 串行执行芯片事务 →
|
||||
* 结果搭通知值回来),**不再是**"仅限执行任务、自旋 net_poll"的
|
||||
* 历史形态;非 netTask 调用不再回 NET_ERR_NOT_IN_NETTASK。
|
||||
* 2. 一次调用 = 一个数据报:芯片缓冲内该包**全部读空**(服务器模式
|
||||
* 手册要求一次性读出),载荷超出 len 的部分按 BSD 语义截断丢弃,
|
||||
* 不会留下半包污染下一次解析。
|
||||
* 3. 源地址按 udp_mode 取:服务器模式剥 8 字节信息头
|
||||
* ([2-3] 源端口芯片小端、[4-7] 源 IP 线序字节 → 本层 s_addr 网络序
|
||||
* 约定,见 net_inet_addr 注释,2026-10-24 定案同族);
|
||||
* 客户端模式不剥头、源地址=本 Socket 固定对端。
|
||||
* ⚠ 客户端模式若仍按服务器模式剥头,等于吃掉载荷前 8 字节
|
||||
* (SNTP 应答 48 字节会前 8 字节丢失)——这是本字段存在的理由。
|
||||
* 4. 阻塞语义与 net_recv 同构:无数据 + 阻塞 → **登记挂起、netTask
|
||||
* 零占用返回**,数据到点断连由 net_udp_tick 按 1ms 节拍结束;
|
||||
* 不再是自旋(旧形态会吃满 IWDG 预算并违反 netTask 契约)。
|
||||
* 5. ⚠ IWDG required_mask 内任务禁止无限等待形态:用
|
||||
* net_recvfrom_to() 显式预算或 net_select()(同 net_recv 契约)。
|
||||
*/
|
||||
int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
struct net_sockaddr *src_addr, int *addrlen);
|
||||
|
||||
/*
|
||||
* 函数功能:接收 UDP 数据报(**带调用方指定超时**,对应 net_recv_to 的 UDP 版)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf/len - 接收缓冲与容量
|
||||
* flags - 目前不使用,传 0(超时由 timeout_ms 表达)
|
||||
* timeout_ms - 本次等待预算(毫秒,>0)
|
||||
* src_addr - 输出:发送方地址(可为 NULL)
|
||||
* addrlen - 输入输出:src_addr 容量/实际长度(可为 NULL)
|
||||
* 返回值:>0 实际字节数;-1 = 真错误(errno 可查);**-2 = 超时无数据**
|
||||
* 限定条件:非 netTask 上下文(netTask 内须用 net_recvfrom + NET_MSG_DONTWAIT)
|
||||
* 函数说明:1. 与 net_recv_to 同一条路径与同一套超时收口:挂起登记带 deadline,
|
||||
* 到点由 net_udp_tick 以超时结束,对外统一 -2(不靠 errno 区分,
|
||||
* 无跨任务 errno 竞态)。
|
||||
* 2. 受 IWDG 监控的任务(含未来的 SNTP/对时任务)**必须**用本接口或
|
||||
* net_select,不得用 net_recvfrom 的无限等待形态。
|
||||
* 3. timeout_ms = 0 视为无效参数(无限等待请用 net_recvfrom)。
|
||||
*/
|
||||
int net_recvfrom_to(int sockfd, void *buf, int len, uint32_t timeout_ms,
|
||||
struct net_sockaddr *src_addr, int *addrlen);
|
||||
|
||||
/*
|
||||
* 函数功能:设置/查询 UDP Socket 的芯片模式(客户端 ↔ 服务器,见 net_types.h
|
||||
* NET_UDP_MODE_* 与手册 §9.2.4)
|
||||
* 入口参数:sockfd - socket 描述符(须为 DGRAM)
|
||||
* peer - 非 NULL = 客户端模式:DesIP/DesPort 设为该对端,芯片只收
|
||||
* 它的包且**不加** 8 字节信息头;
|
||||
* NULL = 服务器模式:DesIP=0xFFFFFFFF、DesPort=0,收任意来源
|
||||
* 且每个包带信息头(net_bind 的默认形态)
|
||||
* 返回值:0 - 成功 int
|
||||
* -1 - 失败(NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_BUSY,errno 可查)
|
||||
* 限定条件:socket 已创建且为 DGRAM 且已 net_bind(芯片侧 Socket 已打开);
|
||||
* 任意任务上下文(走消息队列,netTask 内直接执行)
|
||||
* 函数说明:1. 存在的理由:模式由"最近一次写 DesIP"决定,net_sendto 带目的地址
|
||||
* 发一次就把芯片静默切到客户端模式。要给多个对端收发(TFTP 式
|
||||
* "收到谁的包回给谁、然后继续听所有人")必须在每次带地址发送后
|
||||
* 显式回服务器模式,否则回程包会被当成带头的包错解析。
|
||||
* 2. 只改 DesIP/DesPort 两个寄存器,不重开 Socket(不丢接收缓冲内
|
||||
* 已到的数据);本层 udp_mode 同步更新,recvfrom 剥头判定据此走。
|
||||
* 3. 客户端 → 服务器切换后**先前**收到的包(无头)无法补上源地址,
|
||||
* 模式切换时刻之后的包才按新模式解析——协议栈请"先切模式再等包"。
|
||||
* 4. 切模式不改动 local_port,也不影响已登记的挂起 recv;挂起期间
|
||||
* 切模式由 tick 在结算时读当前模式解析(同一包不会两解)。
|
||||
*/
|
||||
int net_udp_set_peer(int sockfd, const struct net_sockaddr_in *peer);
|
||||
|
||||
/*
|
||||
* 函数功能:查询 UDP Socket 当前芯片模式
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* 返回值:NET_UDP_MODE_SERVER / NET_UDP_MODE_CLIENT uint8_t
|
||||
* -1 - 描述符无效或非 DGRAM int
|
||||
* 限定条件:任意上下文(纯 RAM 读,不发起芯片事务)
|
||||
* 函数说明:诊断/单测/上层"要不要切回服务器模式"的判读入口
|
||||
*/
|
||||
int net_udp_get_mode(int sockfd);
|
||||
|
||||
/*
|
||||
* 函数功能:关闭 socket
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
@@ -598,6 +690,18 @@ int net_getpeername(int sockfd, struct net_sockaddr *addr, int *addrlen);
|
||||
* 内部 API(需在 netTask 上下文中调用,绕过消息队列)
|
||||
*/
|
||||
|
||||
/*
|
||||
* 函数功能:取下一个动态端口并推进游标(49152~65535 回绕,内部使用)
|
||||
* 入口参数:无
|
||||
* 返回值:分配到的端口(主机序) uint16_t
|
||||
* 限定条件:单线程语义——TCP 绑定路径(调用方上下文)与 UDP 绑定执行体
|
||||
* (netTask)都经此取号,簿记只住 net_socket.c 一处
|
||||
* 函数说明:原逻辑内联在 net_bind 里;UDP 队列化后绑定动作挪进 netTask 执行,
|
||||
* 两处都要用同一游标,故抽成函数(同一事实只在一处)。
|
||||
* 不做"是否已被其他 Socket 占用"检查——与抽取前行为一致。
|
||||
*/
|
||||
uint16_t net_dynamic_port_next(void);
|
||||
|
||||
/*
|
||||
* 函数功能:发送数据(内部版本,直接操作,不经过消息队列)
|
||||
* 入口参数:p_sock - Socket 控制块指针
|
||||
@@ -634,6 +738,33 @@ int net_close_sock(net_sock_t *p_sock);
|
||||
* 限定条件:必须在 netTask 上下文中调用
|
||||
*/
|
||||
int net_listen_locked(int sockfd);
|
||||
|
||||
/*
|
||||
* 函数功能:判当前上下文是否本层执行任务(netTask / 硬件层的 testTask)
|
||||
* 入口参数:无
|
||||
* 返回值:1 - 是本层执行任务(或 net_init 前未捕获);0 - 明确不是 uint8_t
|
||||
* 限定条件:net_init 之后判据才有效;任意上下文可读
|
||||
* 函数说明:队列化接口用本判据决定"直接执行"还是"投递给 netTask"——执行任务
|
||||
* 自己不能往自己的队列里投(要等自己回来泵,队列满即自等死锁);
|
||||
* 挂起登记前也用它确认"完成动作有人做"。判据与留痕见定义处注释。
|
||||
*/
|
||||
uint8_t net_ctx_is_owner_task(void);
|
||||
|
||||
/*
|
||||
* 函数功能:发送数据(内部锁定版本,TCP 与 UDP 共用同一份分块实现)
|
||||
* 入口参数:p_sock - Socket 控制块指针
|
||||
* buf - 数据缓冲区
|
||||
* len - 数据长度
|
||||
* caller - 挂起 send 的完成通知目标(执行任务自身传 NULL 即拒登记)
|
||||
* 返回值:实际发送字节数(>= 0);NET_PENDING_REGISTERED = 已挂起;-1 = 错误
|
||||
* 限定条件:必须在 netTask 上下文中调用
|
||||
* 函数说明:UDP 队列化后 net_udp_sendto_exec 也走本函数——分块粒度
|
||||
* (NET_SEND_CHUNK_MAX)、SENBUF_FREE 门控、末块不等、停滞超时、
|
||||
* 挂起 send 表全部只有一份(TRAP-NET-16 的复发形态就是"两条路径
|
||||
* 各写一套分块自旋",故不复制)。状态门按 type 区分:TCP 须
|
||||
* ESTABLISHED,UDP 须 UDP_OPEN。
|
||||
*/
|
||||
int net_send_locked(net_sock_t *p_sock, const void *buf, int len, TaskHandle_t caller);
|
||||
#endif /* NET_NO_OS */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Reference in New Issue
Block a user