fix(net/udp): 收包剥头只看芯片宣布的字节数,与 udp_mode 无关(#73)
真机第一轮(2026-11-04 08:16,阶段 16 台架)把本轮最大的一条设计错误照了出来: TC-NET-1603 在客户端形态下发 48 字节应答,板子读到的是 `n=56`、首字节 `0x38` ——56 正是信息头的长度字段,也就是**头还在**,而我把它的 8 字节当成了载荷。 设计时按手册 §9.2.4 读出一句"客户端模式不带信息头",就以为运行时把 DesIP 从 全 F 改成具体对端会让头消失。真机给的定案是:**加不加 8 字节信息头,是在 OPEN_SOCKET 那一刻由当时的 DesIP 定死的**,之后再改 DesIP 只改发送去向与芯片 的收包源过滤,不改收包帧格式。本层 `net_bind` 一律以 DesIP=0xFFFFFFFF 开 Socket,所以对本层的 UDP socket 而言头恒在、恒剥。 - `net_udp_datagram_read` 的剥头条件由 `udp_mode == SERVER && avail >= 8` 改为 **只看 `avail >= 8`**;源地址一律从信息头里取——头里的源 IP/端口是本包的真实 来源,比"我把包发给了谁"这本账更准(NAT 后的时间服务器就是两种地址不一致的 那种)。 - 不足 8 字节的残包保持既有定案(TC-NETSOCK-440o):无从判头,整包按载荷交付, 源地址退到对端记账。这也是 `net_udp_src_from_peer()` 如今唯一的读者。 - `udp_mode` 语义收窄为"芯片的对端过滤 + 发送去向",**不描述收包帧格式**; `net_types.h` 的 UDP 形态块、两个模式宏的注释、`net_sendto` / `net_recvfrom` 的函数说明一并按定案改写。字段本身没动,仍复用原有的 2 字节 padding,RAM 零增长。 产品构型 Keil 构建 0 Error 0 Warning,`Code=249120 RO=29836 RW=2052 ZI=149996`, 与改造前的产品基线同尺寸。判据文字与真机证据见同批的测试、文档提交。
This commit is contained in:
@@ -367,11 +367,15 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
|
||||
* 函数说明: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)。
|
||||
* 2. 带显式目的地址 = 芯片 DesIP/DesPort 切到该对端 → 本 Socket 记账进入
|
||||
* **UDP 客户端形态**(p_sock->udp_mode = NET_UDP_MODE_CLIENT):此后
|
||||
* `dest_addr = NULL` 的发送就发往它。**收包帧格式不受影响**——信息头是
|
||||
* OPEN_SOCKET 那一刻按当时的 DesIP 定死的,本层 Socket 一律由 net_bind
|
||||
* 在全 F 形态下开出,所以恒带 8 字节信息头、net_recvfrom 恒剥(真机实测
|
||||
* 见 TRAP-NET-22 的 2026-11-04 追加段)。运行时改窄 DesIP 会不会让芯片
|
||||
* 按源过滤,台架未测出结论,多源服务别依赖它。
|
||||
* 要回服务器形态(任意源可发、下一次 NULL 发送不再沿用旧对端)调
|
||||
* 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),
|
||||
@@ -404,15 +408,19 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
* 函数说明:1. 与 net_recv 同一条路径(入队 → netTask 串行执行芯片事务 →
|
||||
* 结果搭通知值回来),**不再是**"仅限执行任务、自旋 net_poll"的
|
||||
* 历史形态;非 netTask 调用不再回 NET_ERR_NOT_IN_NETTASK。
|
||||
* 2. 一次调用 = 一个数据报:芯片缓冲内该包**全部读空**(服务器模式
|
||||
* 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 字节丢失)——这是本字段存在的理由。
|
||||
* 3. **恒剥 8 字节信息头,源地址恒取自头部**([2-3] 源端口芯片小端、
|
||||
* [4-7] 源 IP 线序字节 → 本层 s_addr 网络序约定,见 net_inet_addr 注释,
|
||||
* 2026-10-24 定案同族),**与 udp_mode 无关**:头是 OPEN_SOCKET 那一刻
|
||||
* 按当时的 DesIP 定死的,本层 Socket 一律由 net_bind 在全 F 形态下开出,
|
||||
* 之后把 DesIP 改成具体对端并不会让头消失(2026-11-04 08:16 真机
|
||||
* TC-NET-1603 实测 48 B 载荷可读 56 B、首字节 0x38 = 头里的长度字段;
|
||||
* 定案见 TRAP-NET-22 追加段)。只有不足 8 字节的残包无从判头,整包按
|
||||
* 载荷交付、源地址退到本 Socket 的对端记账。
|
||||
* ⚠ 曾按"客户端形态不剥头"实现过一版,真机第一轮就把 8 字节头当载荷
|
||||
* 交给了应用——不要再把剥头条件改回跟形态挂钩。
|
||||
* 4. 阻塞语义与 net_recv 同构:无数据 + 阻塞 → **登记挂起、netTask
|
||||
* 零占用返回**,数据到点断连由 net_udp_tick 按 1ms 节拍结束;
|
||||
* 不再是自旋(旧形态会吃满 IWDG 预算并违反 netTask 契约)。
|
||||
@@ -443,27 +451,32 @@ 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)
|
||||
* 函数功能:设置/查询 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 的默认形态)
|
||||
* peer - 非 NULL = 客户端形态:DesIP/DesPort 设为该对端,此后
|
||||
* `dest_addr = NULL` 的 net_sendto 就发往它;
|
||||
* 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 在结算时读当前模式解析(同一包不会两解)。
|
||||
* 函数说明:1. 存在的理由:带目的地址的 net_sendto 发一次就把 DesIP 静默改到了
|
||||
* 那个对端(TFTP 式"收到谁的包回给谁"必须这么回),回完就得显式切回
|
||||
* 服务器形态,否则下一次不带地址的发送会悄悄发给上一个对端。
|
||||
* 本接口是**轻切换**:只写 DesIP/DesPort 两个寄存器、不重开 Socket,
|
||||
* 因此接收缓冲里已经到的应答不会被清掉(SNTP 这类请求-应答靠它)。
|
||||
* 2. **两种形态的收包帧格式相同**:本层 Socket 恒在全 F 形态下 open,
|
||||
* 芯片就恒前置 8 字节信息头,运行时改 DesIP 不改变这一点(真机
|
||||
* TC-NET-1603 实测,见 TRAP-NET-22 追加段),net_recvfrom 因此恒剥头、
|
||||
* 恒从头部取源地址。udp_mode 只用于发送去向与诊断判读。
|
||||
* 3. ⚠ 手册把"只收该 IP 的包"写在客户端形态名下,但**运行时**把 DesIP
|
||||
* 改窄之后芯片还过不过滤别的源,台架没有测出结论(阶段 16 的 1604 只
|
||||
* 验到"切回服务器形态后换源端口的新包能收到")。要做多源服务就保持
|
||||
* 服务器形态、每次发送都带显式目的地址,别把过滤当已证事实。
|
||||
* 4. 切换不改 local_port,也不影响已登记的挂起 recv;挂起期间切模式由
|
||||
* tick 在结算时按当前寄存器状态处理(同一包不会两解)。
|
||||
*/
|
||||
int net_udp_set_peer(int sockfd, const struct net_sockaddr_in *peer);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user