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:
+130
-33
@@ -65,6 +65,7 @@
|
||||
| `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含挂起 recv 登记路径 |
|
||||
| `net_events.c`(**真机专属**) | **状态面收敛**(2026-10-14 从 `net_socket.c` 抽出,与 `net_listener.c` 同族):5 个 `handle_*_event`(`net_poll` 检测中断后的分派目标)+ `fire_event` + TRAP-NET-17 周期对账 + 监听重建**合一** reconciler(`net_reconcile_tick`,100ms,分级去抖+冷却,形态①②③④⑤/U11)+ `net_lost_connect_probe`;**只置事实与登记意图**,重建动作在 `net_listener_tick`;内部头 `net_events.h`,模拟器不编译 |
|
||||
| `net_listener.c`(**真机专属**) | **监听生命周期**(E17 单一所有者收口):`net_listener_request`(**只登记意图**,5 个登记点)/ `net_listener_tick`(**唯一执行者**,门集 + D10 重试 30ms/2s)/ `net_listener_clear` / `net_listener_settle`(沉降/隔离 100ms/400ms);"该不该重建"的周期判据在 `net_reconcile_tick` 形态③④(单一 100ms tick);内部头,模拟器不编译 |
|
||||
| `net_udp.c`(**真机专属**) | **UDP 数据面执行体**(2026-11-03 队列化改造新增):`net_udp_bind_exec` / `net_udp_set_peer_exec`(切芯片形态,不重开 Socket)/ `net_udp_sendto_exec`(**复用** `net_send_locked` 的 1KB 分块与挂起 send)/ `net_udp_recvfrom_exec` + `net_udp_tick`(挂起 recv 完成 / 预算超时)+ per-fd 出参槽(源地址写回调用方自己的内存,不占 `net_msg_t`)+ 截断排空;内部头 `net_udp.h`,模拟器不编译;用法与约束见 §6.0 / §6.3,调用结构见 `docs/网络/BSD_Socket_API_函数调用关系.md` §4.11 |
|
||||
| `net_select.h` | select/poll API 头文件 |
|
||||
| `net_select.c` | select/poll 实现(仅 MCU 端编译) |
|
||||
| `net_socket_sim.c`(`simulator/sim/`) | Winsock2 后端(模拟器端,与 `net_socket.c` 互斥编译),详见 §2.3 |
|
||||
@@ -156,10 +157,10 @@ sequenceDiagram
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()` / `net_drop_conn()`——请求入队列,netTask 串行执行,调用方阻塞等回执。**自愈无显式 API**(`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 reconciler 形态③④自动认领,见 §3.6) |
|
||||
| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()` / `net_drop_conn()`——请求入队列,netTask 串行执行,调用方阻塞等回执。UDP 侧同一口径:`net_bind()`(UDP) / `net_sendto()` / `net_recvfrom()` / `net_recvfrom_to()` / `net_udp_set_peer()`(2026-11-03 队列化改造,见 §6)。**自愈无显式 API**(`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 reconciler 形态③④自动认领,见 §3.6) |
|
||||
| **`netTask` 内** | 内部 API `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` / `net_listen_locked()` |
|
||||
| **`net_socket()` / `net_bind()`** | 直接调用,无需队列;实际在就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)中创建监听 |
|
||||
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **4 个非阻塞 tick**(D10 重监听重试 → 挂起 recv 完成 → 挂起 send 推进 → TRAP-NET-17 对账+监听重建合一 reconciler 100ms),再处理队列 |
|
||||
| **`net_socket()` / `net_bind()`** | `net_socket()` 纯 RAM(分配控制块),任意任务直接调用;`net_bind()` 分两支——**TCP** 只记本地端口,仍是直接调用;**UDP** 要开芯片侧 Socket,走队列在 netTask 里执行(要发 5 条芯片命令,见 §6.0) |
|
||||
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **5 个非阻塞 tick**(D10 重监听重试 → 挂起 recv 完成 → 挂起 send 推进 → **UDP 挂起 recv 完成/超时** → TRAP-NET-17 对账+监听重建合一 reconciler 100ms),再处理队列 |
|
||||
|
||||
### 3.4 内部 API 说明
|
||||
|
||||
@@ -440,8 +441,29 @@ void tcp_client(void)
|
||||
|
||||
## 6. UDP 编程
|
||||
|
||||
> **本项目不使用 UDP(2026-10-15 定案)**:UDP API 保留为 BSD socket 完整性,仅**单任务独占期**可用
|
||||
> (`net_sendto` / `net_recvfrom` 直碰 SPI、不经队列,见 §2 三例外);本平台无 UDP 消费者。
|
||||
> **UDP 已于 2026-11-03 队列化**(原"本项目不使用 UDP(2026-10-15 定案)"作废):
|
||||
> `net_bind`(UDP) / `net_sendto` / `net_recvfrom` / `net_recvfrom_to` / `net_udp_set_peer` 与 TCP 同一口径
|
||||
> ——请求投 `netMsgQueue`,芯片事务**全部在 `netTask` 里串行执行**,调用方阻塞等回执(结果搭 32 位任务
|
||||
> 通知值回来)。任何任务都能安全调用,不再有"单任务独占期"这个前提。第一个真实消费者是 SNTP(请求-应答)。
|
||||
|
||||
### 6.0 先分清楚:芯片的 UDP 两种模式
|
||||
|
||||
CH395F 用 `DesIP` 一个寄存器区分两种 UDP 形态(手册 `docs/芯片手册/CH395F.md` §9.2.4),本层把它做成
|
||||
socket 的**模式属性**(`net_types.h` `NET_UDP_MODE_SERVER` / `NET_UDP_MODE_CLIENT`)。两种形态收上来的
|
||||
字节**不一样多**,这是 UDP 侧最容易踩的坑:
|
||||
|
||||
| 模式 | DesIP | 收包布局 | 谁能发给我 | 本层如何进入 |
|
||||
|---|---|---|---|---|
|
||||
| **服务器** | `0xFFFFFFFF` | **8 字节信息头** + 载荷(头 [0-1] 长度、[2-3] 源端口、[4-7] 源 IP 线序) | 任意主机 | `net_bind()`(恒此模式:BSD 的 bind 只定本地端口,不选对端) |
|
||||
| **客户端** | 具体 IP | **无信息头**,整段就是载荷 | 只有该 IP | `net_sendto()` 带目的地址,或 `net_udp_set_peer(fd, &peer)` |
|
||||
|
||||
- 收包时**按模式**决定是否剥头:客户端模式绝不剥。旧实现不分模式恒剥 8 字节,而 `net_sendto` 一旦给出
|
||||
目的地址芯片就已是客户端形态——于是 SNTP 这类应答的**前 8 字节载荷被吃掉**,源地址还是从载荷里当
|
||||
头读出来的假值(缺陷登记见 `docs/问题记录/CH395F_Trap_Records.md`,回归钉子在
|
||||
`test/host/net_socket/ns_s7_udp.c` UDP-3 / UDP-4)。
|
||||
- 从客户端模式回服务器形态:`net_udp_set_peer(fd, NULL)`,只改 DesIP/DesPort,**不重开 Socket**;
|
||||
再次 `net_bind()` 则是重开(顺带丢掉芯片缓冲里的残留包),两者用途不同。
|
||||
- 客户端模式下 `net_recvfrom` 回填的源地址就是那个固定对端——芯片已经替我按 IP 过滤过一遍,包上也没有头可取。
|
||||
|
||||
### 6.1 UDP Client
|
||||
|
||||
@@ -463,37 +485,43 @@ void udp_client(void)
|
||||
return;
|
||||
}
|
||||
|
||||
/* 2. 绑定本地端口(可选) */
|
||||
/* 2. 绑定本地端口:UDP 侧**必做**(芯片侧 Socket 要在这里 open,
|
||||
* 没 bind 就收发一律 NET_ERR_NOTCONN)。端口给 0 = 本层取动态端口 */
|
||||
local_addr.sin_family = AF_INET;
|
||||
local_addr.sin_port = net_htons(12345);
|
||||
local_addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
|
||||
|
||||
net_bind(sock, (struct net_sockaddr *)&local_addr, sizeof(local_addr));
|
||||
if (net_bind(sock, (struct net_sockaddr *)&local_addr, sizeof(local_addr)) != 0) {
|
||||
printf("绑定失败\r\n");
|
||||
net_close(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 3. 设置目标地址 */
|
||||
dest_addr.sin_family = AF_INET;
|
||||
dest_addr.sin_port = net_htons(8888);
|
||||
dest_addr.sin_addr.s_addr = net_inet_addr("192.168.31.200");
|
||||
|
||||
/* 4. 发送数据 */
|
||||
/* 4. 发送数据:带目的地址的 sendto 顺手把 DesIP 指向该地址,
|
||||
* 芯片从此是**客户端模式**(只收这个 IP 的包,包上不带信息头,见 §6.0) */
|
||||
const char *msg = "UDP Hello!";
|
||||
net_sendto(sock, msg, strlen(msg), 0,
|
||||
(struct net_sockaddr *)&dest_addr, sizeof(dest_addr));
|
||||
printf("发送: %s\r\n", msg);
|
||||
if (net_sendto(sock, msg, strlen(msg), 0,
|
||||
(struct net_sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
|
||||
printf("发送失败 %d\r\n", net_get_errno());
|
||||
net_close(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 5. 接收数据 */
|
||||
struct net_sockaddr_in src_addr;
|
||||
int src_addr_len = sizeof(src_addr);
|
||||
|
||||
len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0,
|
||||
(struct net_sockaddr *)&src_addr, &src_addr_len);
|
||||
/* 5. 接收应答:受 IWDG 监管的任务用带预算的 net_recvfrom_to,
|
||||
* 超时返回 -2(不置 errno),不会把喂狗窗口耗在等待上(§10.1) */
|
||||
len = net_recvfrom_to(sock, buf, sizeof(buf) - 1, 2000U, NULL, NULL);
|
||||
|
||||
if (len > 0) {
|
||||
buf[len] = '\0';
|
||||
char ip_str[16];
|
||||
printf("收到: %s (from %s:%d)\r\n", buf,
|
||||
net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str),
|
||||
net_ntohs(src_addr.sin_port));
|
||||
/* 客户端模式的源地址就是这个固定对端,包上没有信息头 */
|
||||
printf("收到: %s (from server)\r\n", buf);
|
||||
} else if (len == -2) {
|
||||
printf("应答超时\r\n");
|
||||
}
|
||||
|
||||
net_close(sock);
|
||||
@@ -531,15 +559,15 @@ void udp_server(void)
|
||||
|
||||
printf("UDP Server 启动,监听端口 8888\r\n");
|
||||
|
||||
/* 3. 循环接收数据 */
|
||||
/* 3. 循环接收数据:一次调用 = 一个完整数据报(服务器模式自动剥 8 字节信息头) */
|
||||
while (1)
|
||||
{
|
||||
net_poll();
|
||||
|
||||
struct net_sockaddr_in src_addr;
|
||||
int src_addr_len = sizeof(src_addr);
|
||||
|
||||
int len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0,
|
||||
/* 用带预算的收包当循环节拍:-2 = 本轮没人发包,不是错误;
|
||||
* 换 net_recvfrom(...) 就是无限等,受 IWDG 监管的任务里别这么写(§10.1) */
|
||||
int len = net_recvfrom_to(sock, buf, sizeof(buf) - 1, 500U,
|
||||
(struct net_sockaddr *)&src_addr, &src_addr_len);
|
||||
|
||||
if (len > 0) {
|
||||
@@ -549,14 +577,29 @@ void udp_server(void)
|
||||
net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str),
|
||||
net_ntohs(src_addr.sin_port));
|
||||
|
||||
/* 回显数据 */
|
||||
net_sendto(sock, buf, len, 0,
|
||||
/* 回显数据。⚠ 带目的地址的 sendto 会把芯片切成**客户端模式**
|
||||
* (DesIP = 这个客户端),此后只有它一家能再发包进来。要长期服务
|
||||
* 多个客户端,回完就把它切回来: */
|
||||
(void)net_sendto(sock, buf, len, 0,
|
||||
(struct net_sockaddr *)&src_addr, src_addr_len);
|
||||
(void)net_udp_set_peer(sock, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 UDP 调用约束(2026-11-03 队列化之后)
|
||||
|
||||
| 约束 | 行为与理由 |
|
||||
|---|---|
|
||||
| 未 `net_bind()` 就 `sendto` / `recvfrom` / `set_peer` | 一律 `NET_ERR_NOTCONN`(-5):芯片侧 Socket 还没 open,此时写 DesIP 也会被随后的 open 复位 |
|
||||
| 一次收包 = 一个完整数据报 | 缓冲装不下时交付 `len` 字节并把剩余部分**读空丢弃**(留半包在芯片缓冲里,下一次收包会从上一包的尾巴开始解析,源地址与载荷从此全部错位);短交付靠返回值判读 |
|
||||
| 同一 fd 并发收包 | 第二个接收者拿 `NET_ERR_BUSY`(-11):数据报语义下无法把包归属到两个等待者,拒绝比交错投递安全(与 TCP 挂起 recv 同一守卫) |
|
||||
| 阻塞预算 | 受 IWDG 监管的任务禁用无限等待的 `net_recvfrom`,改 `net_recvfrom_to(fd, buf, len, to_ms, ...)`:超时返回 **-2** 且不置 errno(与 `net_recv_to` 同口径);或先 `net_select()` / `net_can_read()` 门控 |
|
||||
| 单包载荷 | ≤ 1472 字节(手册 §9.2.4);更长由本层按 1KB 分块、复用 TCP 的挂起 send 在 netTask 里推进,短写返回已发送字节数,调用方须检查返回值 |
|
||||
| fd 额度 | 与 TCP 共用 `NET_MAX_SOCKETS`(8),一个 UDP fd 占一个名额 |
|
||||
| 收包不占 netTask | 没数据时登记挂起(本 fd 一个槽)立即让出 netTask,完成条件由 `net_udp_tick` 每轮判定——与挂起 recv/send 同一套账,netTask 不会被一次 UDP 等待钉住 |
|
||||
|
||||
---
|
||||
|
||||
## 7. I/O 多路复用
|
||||
@@ -738,10 +781,13 @@ void event_callback_example(void)
|
||||
的 `s_addr` **同族、可直接相等比较**。历史背景(同一文件曾并存两套约定、`ntoa` 注释里的例子取了废弃族的值)
|
||||
见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-21 的 2026-10-24 后续条目。
|
||||
|
||||
**UDP 直调限制**:`net_sendto` / `net_recvfrom` **不走消息队列**——它们在调用方上下文里直接 `net_poll()` + 读写芯片缓冲,
|
||||
因此**仅限本层执行任务(NET 层 = netTask)调用**;别的任务调用回 `NET_ERR_NOT_IN_NETTASK`(-12)。该码**不是运行时故障,
|
||||
重试永远无意义**:要么把调用挪进执行任务,要么改用走队列的 `net_send` / `net_recv`,没有第三条路。
|
||||
「把这两个接口改成经 `netMsgQueue` 串行化」是已知后续项,未做的理由见 `Drivers/BSP/NET/net_socket.h` 同名函数头。
|
||||
**UDP 也走队列(2026-11-03 改造)**:`net_bind`(UDP) / `net_sendto` / `net_recvfrom` / `net_recvfrom_to` /
|
||||
`net_udp_set_peer` 与 TCP 同一口径——投 `netMsgQueue`、在 `netTask` 里碰芯片、结果搭 32 位任务通知值回来。
|
||||
改造前有、现在作废的两件事:①"仅限本层执行任务(netTask)调用"——`NET_ERR_NOT_IN_NETTASK`(-12)自本
|
||||
改造起**没有任何返回路径**,常量保留只为老代码与历史文档对得上号,新代码不要拿它当判据;②"UDP 只在单任务
|
||||
独占期可用"。源地址回传不占 `net_msg_t`(该结构按值拷入队列,AGENTS 禁止加"回传用"字段),而是经
|
||||
`Drivers/BSP/NET/net_udp.c` 的 per-fd 出参槽写回调用方自己的内存,代价是**同一 fd 不允许两个任务同时收包**
|
||||
(第二个拿 `NET_ERR_BUSY`)。芯片形态(服务器 / 客户端)与剥头规则见 §6.0,调用约束见 §6.3。
|
||||
|
||||
### 9.1 端口号转换
|
||||
|
||||
@@ -1007,20 +1053,71 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
|
||||
|
||||
调用方累计自身总预算:见到 -2 发起下一片,直到收齐 / EOF / 总预算耗尽。生产用途:lftpd 数据行读、timeSync 帧读(队头阻塞修复,完整例 §11.2);受 IWDG 监控任务的长等待必须走本接口(或 `net_select`)+ 每片喂心跳。
|
||||
|
||||
#### net_sendto - 发送 UDP 数据
|
||||
#### net_sendto - 发送 UDP 数据报
|
||||
|
||||
```c
|
||||
int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
const struct net_sockaddr *dest_addr, int addrlen);
|
||||
```
|
||||
|
||||
#### net_recvfrom - 接收 UDP 数据
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `sockfd` | **必须已 `net_bind()`**(未 bind 回 -1 / `NET_ERR_NOTCONN`:芯片侧 Socket 还没 open) |
|
||||
| `buf` / `len` | 一个数据报的载荷,单包 ≤ 1472 字节(手册 §9.2.4);更长由本层按 1KB 分块、复用 TCP 的挂起 send 在 netTask 里推进 |
|
||||
| `flags` | 目前只消费 `NET_MSG_DONTWAIT`:芯片发送缓冲不可用时立即回 `NET_ERR_WOULDBLOCK`,不登记挂起 |
|
||||
| `dest_addr` / `addrlen` | 非 NULL = 顺带把芯片 DesIP 指向它 → 芯片转**客户端模式**(此后只收该 IP 的包、收包不带信息头,见 §6.0);NULL = 不改目的地址,沿用当前形态 |
|
||||
| 返回值 | > 0 = 已发送字节数(**可能短写**,须检查);-1 = 失败(`net_get_errno()`) |
|
||||
|
||||
任意任务可调用:请求入 `netMsgQueue`,芯片事务在 `netTask` 串行执行(2026-11-03 起,旧"仅限 netTask 直调"形态作废)。
|
||||
|
||||
#### net_recvfrom - 接收 UDP 数据报(无限等待形态)
|
||||
|
||||
```c
|
||||
int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
struct net_sockaddr *src_addr, int *addrlen);
|
||||
```
|
||||
|
||||
- 一次调用 = 一个**完整数据报**:服务器模式自动剥 8 字节信息头并从中取源地址,客户端模式整段是载荷、源地址回填固定对端(§6.0)。
|
||||
- `buf` 装不下时交付 `len` 字节并把剩余部分**读空丢弃**——半包留在芯片缓冲里,下一次收包会从上一包的尾巴开始解析。
|
||||
- `src_addr` / `addrlen` 可为 NULL;非 NULL 时 `*addrlen` 给 `sizeof(struct net_sockaddr_in)`,回填的 `s_addr` 是网络序整数(§9.0)。
|
||||
- ⚠ **无数据即无限等待**:受 IWDG 监管的任务改 `net_recvfrom_to()`,或先 `net_select()` / `net_can_read()` 门控。
|
||||
- 同一 fd 已有别的任务在收 → 本调用回 -1 / `NET_ERR_BUSY`(数据报无法归属两个等待者)。
|
||||
|
||||
#### net_recvfrom_to - 接收 UDP 数据报(显式预算形态)
|
||||
|
||||
```c
|
||||
int net_recvfrom_to(int sockfd, void *buf, int len, uint32_t timeout_ms,
|
||||
struct net_sockaddr *src_addr, int *addrlen);
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `timeout_ms` | 等待预算(ms,**必须 > 0**;0 = 无限等待,与本接口"有界"的存在理由相反,直接拒 `NET_ERR_INVAL`——无限形态请用 `net_recvfrom`) |
|
||||
| 返回值 | > 0 = 本包交付字节数(小于芯片内包长 = 被截断,多余部分已丢弃);**-2 = 预算内没等到**(**不是 errno**,不得据此判错误);-1 = 真错误 |
|
||||
|
||||
与 `net_recv_to()` 同一口径,是受 IWDG 监控任务的合法 UDP 收包入口;等待期本 fd 登记一个挂起槽、netTask 立即让出(不占 CPU、不占 netTask)。
|
||||
|
||||
#### net_udp_set_peer - 切换 UDP 芯片形态(不重开 Socket)
|
||||
|
||||
```c
|
||||
int net_udp_set_peer(int sockfd, const struct net_sockaddr_in *peer);
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `peer` | 非 NULL = 客户端模式,DesIP/DesPort 指向该对端(只收它的包,收包无信息头);NULL = 回服务器模式(DesIP = `0xFFFFFFFF`、DesPort = 0,任意主机可发,收包带 8 字节信息头) |
|
||||
| 返回值 | 0 成功;-1 失败(未 bind → `NET_ERR_NOTCONN`) |
|
||||
|
||||
只写两个寄存器、**不重开 Socket**,因此不清芯片缓冲;要连残留包一起丢掉就再 `net_bind()` 一次(重开形态)。
|
||||
|
||||
#### net_udp_get_mode - 读当前 UDP 芯片形态
|
||||
|
||||
```c
|
||||
int net_udp_get_mode(int sockfd);
|
||||
```
|
||||
|
||||
返回 `NET_UDP_MODE_SERVER` / `NET_UDP_MODE_CLIENT`,参数错回 -1。给诊断、单测和上层判读"要不要切回服务器模式"用;纯 RAM 读,不入队。
|
||||
|
||||
#### net_close - 关闭 Socket
|
||||
|
||||
```c
|
||||
|
||||
Reference in New Issue
Block a user