diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 1c4d685d..d06ff055 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -2629,14 +2629,17 @@ int net_drop_conn(int sockfd) { * 取代 2026-10-24「先把危险面关掉」的临时守卫 NET_ERR_NOT_IN_NETTASK)。 * 执行任务(netTask,或硬件层的 testTask)上下文直接执行不投自己—— * 板级用例仍可在自己任务里裸调本接口。 - * 3. 带 dest_addr 会把芯片切成 **UDP 客户端模式**(DesIP=具体 IP):此后芯片 - * 只收该 IP 的包、且不再附带 8 字节信息头。本层按 p_sock->udp_mode 记账, - * net_recvfrom 据此决定剥不剥头——改造前恒剥头,客户端模式下会吃掉 - * 载荷前 8 字节并回填一个假源地址(SNTP 这类"请求-应答"用法必踩)。 + * 3. 带 dest_addr 会把芯片的对端约束切成 **UDP 客户端形态**(DesIP=具体 IP): + * 此后芯片只收该 IP 的包、发送也固定走它。⚠ 8 字节信息头并不因此消失: + * 头在不在取决于**开 Socket 那一刻**的 DesIP(本层一律 0xFFFFFFFF), + * 2026-11-04 08:16 真机 TC-NET-1603 实测 48 B 载荷可读 56 B、首字节 0x38。 + * 本层按 p_sock->udp_mode 只记这份对端账,net_recvfrom 一律剥头、一律从 + * 头里取源地址(为什么不能按 udp_mode 决定剥不剥,见 net_types.h UDP 形态 + * 注释与 TRAP-NET-22 的 2026-11-04 追加段)。 * 4. 分块粒度、停滞超时、挂起 send 续传与 TCP 共用 net_send_locked, * 不留第二份自旋。单包超过 NET_SEND_CHUNK_MAX(1024) 会被写成多个数据报 * (改造前后一致);一个应用数据报请控制在 1024 B 内。 - * 5. dest_addr == NULL = 不改 DesIP,沿用上次的对端(与改造前一致)。 + * 5. dest_addr == NULL = 不改 DesIP,沿用上一次写进芯片的对端(与改造前一致)。 * 6. flags 目前不被本层使用(与改造前一致,发送不接受 DONTWAIT 语义)。 * 7. 直接路径(执行任务上下文)不做挂起续传:芯片发送缓冲超 2 ms 未腾出时 * 返回 NET_ERR_BUSY,而不是像改造前那样自旋到 1000 ms 后短写。 @@ -2733,11 +2736,14 @@ int net_sendto(int sockfd, const void *buf, int len, int flags, * 返回值:实际接收字节数 int > 0 || -1 * -1 - 接收失败(精确码见本任务 net_get_errno()) int * 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM)**并已 net_bind**;net_init() 已调用 - * 函数说明:1. 一次调用交付**一个完整数据报**:服务器模式剥掉芯片的 8 字节信息头并 - * 据此填 src_addr;客户端模式整包都是载荷、src_addr 取本 Socket 的固定 - * 对端(芯片在该模式下不加头,见 net_types.h UDP 模式宏注释)。 - * 剥不剥头按 p_sock->udp_mode 判定,不再按"字节数 >= 8"猜——猜错即吃掉 - * 载荷前 8 字节(本接口改造前的缺陷,SNTP 应答 48 B 会只剩 40 B)。 + * 函数说明:1. 一次调用交付**一个完整数据报**:剥掉芯片的 8 字节信息头并据此填 + * src_addr。剥头**不看 udp_mode**——本层 Socket 一律在 DesIP=0xFFFFFFFF + * 的形态下开出,芯片就给每个收到的数据报加头,运行时把 DesIP 改成具体 + * 对端(显式 dest_addr 的 net_sendto / net_udp_set_peer)并不会让头消失 + * (2026-11-04 08:16 真机 TC-NET-1603 实测:客户端形态下 48 B 载荷可读 + * 56 B、首字节 0x38 = 信息头长度字段;详见 net_types.h UDP 形态注释与 + * TRAP-NET-22 的 2026-11-04 追加段)。只有不足 8 字节的残包无从判头, + * 整包按载荷交付、源地址退到本 Socket 的对端记账。 * 2. 载荷超出 len 时交付前 len 字节并把剩余部分**读空丢弃**(手册 §9.2.4 * 要求服务器模式一次性读完;留半包会让下一个数据报从尾巴开始解析)。 * 3. 无数据时(与 net_recv 同构):DONTWAIT / 非阻塞属性立即返回 -1 @@ -2858,8 +2864,10 @@ int net_udp_set_peer(int sockfd, const struct net_sockaddr_in *peer) { * 出口参数:无 * 返回值:NET_UDP_MODE_SERVER / NET_UDP_MODE_CLIENT;-1 = 描述符非法 int * 限定条件:无(纯读控制块,任意上下文) - * 函数说明:给调用方与自测用:sendto(带目的地址) 之后模式是 CLIENT,此时 - * recvfrom 不剥信息头;bind 之后是 SERVER。 + * 函数说明:给调用方与自测用:它报的是**对端形态**(下一次不带地址的 sendto 发往 + * 谁)——sendto(带目的地址) 之后是 CLIENT,bind / set_peer(NULL) 之后是 + * SERVER。收包帧格式与之无关:本层 Socket 恒在全 F 形态下 open,芯片恒 + * 前置 8 字节信息头,recvfrom 两种形态都剥(TRAP-NET-22 追加段)。 */ int net_udp_get_mode(int sockfd) { const net_sock_t *p_sock = net_get_sock(sockfd); diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h index ffd62079..eb635fa4 100644 --- a/Drivers/BSP/NET/net_socket.h +++ b/Drivers/BSP/NET/net_socket.h @@ -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); diff --git a/Drivers/BSP/NET/net_types.h b/Drivers/BSP/NET/net_types.h index f145f168..0a18bb09 100644 --- a/Drivers/BSP/NET/net_types.h +++ b/Drivers/BSP/NET/net_types.h @@ -51,23 +51,29 @@ extern "C" { * 具名入口见 net_recv_nb() / net_setblocking()) */ /* - * UDP 芯片模式(仅 type == NET_SOCK_DGRAM 有意义;手册 §9.2.4 步骤②: - * **目的 IP 决定模式**,两种模式的收包格式不同,见下) + * UDP 芯片对端形态(仅 type == NET_SOCK_DGRAM 有意义;手册 §9.2.4:目的 IP 决定 + * 芯片的收/发对端约束。两种形态的差别**只在过滤与发送去向**,收包帧格式不在其中, + * 见下第 3 条——这一条是 2026-11-04 真机量出来的,不是推的。) * - * 服务器模式 DesIP = 0xFFFFFFFF:可与任意远端通讯,CH395F 在**每个**收到的 - * 数据报前添加 8 字节信息头([0-1] 数据长度、[2-3] 源端口(芯片小端)、 - * [4-7] 源 IP(线序字节,首段在低地址)),且手册要求一次性把该包全部读出。 - * 客户端模式 DesIP = 具体 IP:芯片只收该 IP 的包,且**不加**信息头—— - * 数据流直接落到接收缓冲区(可整读也可部分读)。 + * 服务器形态 DesIP = 0xFFFFFFFF(net_bind 的 UDP 分支一律开在这个形态): + * 芯片不按源过滤,可与任意远端通讯;CH395F 在**每个**收到的数据报前添加 + * 8 字节信息头([0-1] 数据长度、[2-3] 源端口(芯片小端)、[4-7] 源 IP + * (线序字节,首段在低地址)),且手册要求一次性把该包全部读出。 + * 客户端形态 DesIP = 具体 IP:芯片只收该 IP 的包,发送也固定走它。 * - * 为什么本层要存这个状态:模式是芯片属性、由"最近一次写 DesIP"决定,而软件 - * 原先看不见它——net_sendto 带目的地址发一次就把芯片静默切到客户端模式,之后 - * 的收包仍按服务器模式剥 8 字节 = **吃掉载荷前 8 字节**并把垃圾字节当源地址 - * (SNTP 48 字节应答会丢掉前 8 字节,2026-11-03 定性,见 - * docs/问题记录/CH395F_Trap_Records.md「UDP 模式」条)。剥不剥头必须由本字段决定。 + * ⚠ 信息头是否在,取决于**开 Socket 那一刻**的 DesIP,不取决于之后的 DesIP: + * 运行时把 DesIP 从 0xFFFFFFFF 改成具体 IP,头并不会消失。2026-11-04 08:16 + * 真机 TC-NET-1603 实测——客户端形态下对端发来 48 B,芯片报可读 56 B、首字节 + * 0x38(=56,正是信息头的长度字段)。所以本层 recvfrom **恒剥 8 字节**、 + * 恒从信息头取源地址;曾经"按 udp_mode 决定剥不剥"的写法会把 8 字节头当载荷 + * 交给应用(SNTP 48 字节应答只剩 40 字节且前 8 字节是头),那正是 + * docs/问题记录/CH395F_Trap_Records.md「TRAP-NET-22」记录的老缺陷的镜像面: + * 改造前恒剥头会在真客户端 Socket 上吃 8 字节,改造初版按运行时形态判剥不剥 + * 则在服务器形态开出的 Socket 上多交 8 字节。两头都要靠"开 Socket 时定形态" + * 这一条事实来对齐,本字段只记账发送去向与源过滤。 */ -#define NET_UDP_MODE_SERVER 0U /* DesIP=0xFFFFFFFF:收包含 8 字节信息头(net_bind 的默认模式) */ -#define NET_UDP_MODE_CLIENT 1U /* DesIP=具体 IP:收包无信息头,源地址=本 Socket 固定对端 */ +#define NET_UDP_MODE_SERVER 0U /* DesIP=0xFFFFFFFF:芯片不按源过滤收包,发送走信息头/显式目的 */ +#define NET_UDP_MODE_CLIENT 1U /* DesIP=具体 IP:芯片只收该对端、发送也走它;信息头仍在(帧格式看 udp_mode 字段注释) */ /* * 网络字节序转换宏 @@ -186,13 +192,17 @@ typedef struct { net_sock_state_t state; /* Socket 状态 */ uint8_t type; /* SOCK_STREAM / SOCK_DGRAM */ uint8_t in_use; /* 是否在用标志 */ - /* UDP 芯片模式(NET_UDP_MODE_SERVER / _CLIENT,宏注释见本文件 UDP 模式定义处)。 + /* UDP 芯片对端形态(NET_UDP_MODE_SERVER / _CLIENT,宏注释见本文件 UDP 模式定义处)。 * 仅 type == NET_SOCK_DGRAM 有意义;置位/清零路径:net_bind 的 UDP 分支 → * SERVER(DesIP=0xFFFFFFFF);net_sendto 带显式目的地址、net_udp_set_peer(非 * NULL) → CLIENT(DesIP=该 IP);net_udp_set_peer(NULL) → 回 SERVER。 * 新 fd / close 由整块 memset 归零 = SERVER,与 bind 默认同值,故不得继承上一条。 - * CLIENT 模式的对端簿记复用 remote_ip_arr[4] + remote_port(与 TCP 同一对字段, - * 语义同为"本会话对端"),recvfrom 据此回填 src_addr。 + * ⚠ 这个字段**只描述芯片的对端过滤与发送去向,不描述收包帧格式**:本层 Socket + * 一律在 DesIP=0xFFFFFFFF 时开出,芯片就固定给每个收到的数据报前置 8 字节信息 + * 头,运行时改 DesIP 不会让头消失(2026-11-04 08:16 真机 TC-NET-1603 实测 + * n=56 / 首字节 0x38,见 TRAP-NET-22 追加段)。因此 recvfrom 一律剥头、一律从 + * 头里取源地址;此处 CLIENT 的对端簿记(复用 remote_ip_arr[4] + remote_port, + * 与 TCP 同一对字段)只用于发送去向与不足 8 字节残包的源地址兜底。 * 位置选择:紧接 in_use 之后,落在 type/in_use 之后原有的 2 字节填充里 * (本结构 8 个实例,净增 RAM = 0,见 AGENTS RAM 红线)。 */ uint8_t udp_mode; diff --git a/Drivers/BSP/NET/net_udp.c b/Drivers/BSP/NET/net_udp.c index 4a982930..7e740124 100644 --- a/Drivers/BSP/NET/net_udp.c +++ b/Drivers/BSP/NET/net_udp.c @@ -598,14 +598,16 @@ static void net_udp_req_complete(int sockfd, int result) { } /* - * 函数功能:从 8 字节信息头回填源地址(服务器模式) + * 函数功能:从 8 字节信息头回填源地址(本层收包的正常路径) * 入口参数:p_sock - 控制块(本函数不用,保留与 peer 版同形签名) net_sock_t * * hdr - 已读出的 8 字节信息头 const uint8_t * * src_addr - 调用方出参(可为 NULL) struct net_sockaddr * * addrlen - 调用方出参长度(可为 NULL) int * * 出口参数:src_addr - 填充发送方地址;*addrlen = sizeof(sockaddr_in) * 返回值:无 - * 限定条件:仅由 net_udp_datagram_read 在服务器模式分支调用 + * 限定条件:由 net_udp_datagram_read 在剥头分支(avail >= 8)调用——本层 Socket + * 一律开在服务器形态,信息头与 udp_mode 无关,故 CLIENT 形态同样走这里, + * 头里的源 IP/端口就是本包的真实来源 * 函数说明:头布局(手册 §9.2.4)[0-1] 数据长度、[2-3] 源端口(芯片小端)、 * [4-7] 源 IP(线序字节,[4] = 第一段)。s_addr 直接按"首段落在低字节" * 组装即得本层网络序约定(2026-10-24 定案:全仓 s_addr 只此一族, @@ -642,15 +644,16 @@ static void net_udp_src_from_header(net_sock_t *p_sock, const uint8_t *hdr, } /* - * 函数功能:从控制块固定对端回填源地址(客户端模式:芯片不加信息头) + * 函数功能:从控制块固定对端回填源地址(不足 8 字节残包的兜底路径) * 入口参数:p_sock - 控制块(remote_ip_arr/remote_port = 本 Socket 的对端) net_sock_t * * src_addr - 调用方出参(可为 NULL) struct net_sockaddr * * addrlen - 调用方出参长度(可为 NULL) int * * 出口参数:src_addr - 填充对端地址;*addrlen = sizeof(sockaddr_in) * 返回值:无 - * 限定条件:仅由 net_udp_datagram_read 在客户端模式分支调用 - * 函数说明:客户端模式下芯片只收这个 IP 的包,源地址不需要从头里取—— - * 反过来说它也**没有**头可取,本函数是唯一的源地址来源。 + * 限定条件:仅由 net_udp_datagram_read 在残包分支(avail < 8)调用 + * 函数说明:本层 Socket 一律开在服务器形态、每个数据报都带信息头,正常包都从 + * 头里取源地址;只有无从判头的残包才退到"源地址=本 Socket 的对端记账", + * 至少比报 NULL 或报上一个包的源更接近真相(TC-NETSOCK-440o 定案)。 */ static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_addr, int *addrlen) { @@ -678,7 +681,7 @@ static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_a /* * 函数功能:从芯片缓冲读出**一个完整数据报**(剥头 + 截断排空 + 清可读标志) * 入口参数:sockfd - Socket 描述符 (0~7) int - * p_sock - 控制块(读 udp_mode) net_sock_t * + * p_sock - 控制块(只在不足 8 字节的残包上用来取对端记账当源地址) net_sock_t * * buf - 调用方缓冲 void * * len - 缓冲容量(> 0) int * src_addr - 源地址出参(可为 NULL) struct net_sockaddr * @@ -687,14 +690,21 @@ static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_a * 出口参数:buf - 载荷(最多 len 字节,多余部分丢弃);src_addr - 发送方地址 * 返回值:交付调用方的字节数(>= 0) int * 限定条件:netTask 上下文;avail 必须是本函数调用前刚读到的 ch395f_get_recv_len() - * 函数说明:1. 服务器模式剥 8 字节信息头并从中取源地址;客户端模式整包都是 - * 载荷、源地址取固定对端。模式判错 = 吃掉载荷前 8 字节(本函数 - * 存在的理由,见 net_types.h UDP 模式注释)。 - * 2. 服务器模式手册要求"一次性将数据全部读出";且不论哪种模式, - * 载荷超出 len 时必须把剩余部分读空——留半包在芯片缓冲里, - * 下一次收包会从上一包的尾巴开始解析,源地址与载荷从此全部错位 - * (旧实现只读 min(可用, len),正是这个缺陷)。 - * 3. 服务器模式收到不足 8 字节的残包:无从判头,整包按载荷交付 + * 函数说明:1. **剥头与 udp_mode 无关,只看开 Socket 时的形态**:本层所有 DGRAM + * Socket 都由 net_bind 在 `DesIP = 0xFFFFFFFF`(服务器形态)下开出来, + * 芯片据此给**每一个**收到的数据报前置 8 字节信息头,此后无论把 DesIP + * 改成具体对端(显式 dest_addr 的 net_sendto / net_udp_set_peer)都不 + * 再改变这一帧格式。2026-11-04 08:16 真机 TC-NET-1603 实测:客户端 + * 形态下发来 48 B,芯片给出 n=56、首字节 0x38(=56,正是信息头的长度 + * 字段)——运行时改 DesIP 不会让头消失。按 udp_mode 决定剥不剥,就会 + * 把 8 字节头当载荷交给应用(SNTP 48 B 应答只剩 40 B 且首 8 字节是头), + * 这正是 TRAP-NET-22 记录的老缺陷;定案见该条 2026-11-04 追加段。 + * 源地址同理一律从头里取:头里的源 IP/端口是**本包**的真实来源,比 + * 对端记账更准(对端记账只用于发送与芯片收包过滤)。 + * 2. 手册要求"一次性将数据全部读出";且载荷超出 len 时必须把剩余部分 + * 读空——留半包在芯片缓冲里,下一次收包会从上一包的尾巴开始解析, + * 源地址与载荷从此全部错位(旧实现只读 min(可用, len),正是这个缺陷)。 + * 3. 不足 8 字节的残包:无从判头,整包按载荷交付、源地址取对端记账 * (保持既有定案 TC-NETSOCK-440o,不猜芯片行为)。 * 4. 长度由调用方传入、本函数**不再自己读第二次**:一次收包的剥头量、 * 载荷量、排空量必须由同一个数字算出。读两次等于多一次 SPI,而且 @@ -715,9 +725,8 @@ static int net_udp_datagram_read(int sockfd, net_sock_t *p_sock, void *buf, int return 0; } - if ((p_sock->udp_mode == NET_UDP_MODE_SERVER) && - ((uint32_t)avail >= NET_UDP_HDR_LEN)) { - /* 信息头暂存(只在服务器形态用得到,放在本分支栈上) */ + if ((uint32_t)avail >= NET_UDP_HDR_LEN) { + /* 信息头暂存(只在剥头这一支用得到,放在本分支栈上) */ uint8_t hdr[NET_UDP_HDR_LEN]; /* 剥头:读即消费芯片缓冲 */ @@ -725,7 +734,7 @@ static int net_udp_datagram_read(int sockfd, net_sock_t *p_sock, void *buf, int net_udp_src_from_header(p_sock, hdr, src_addr, addrlen); payload = avail - (int)NET_UDP_HDR_LEN; } else { - /* 客户端模式(或服务器模式残包):无头可剥,源地址取固定对端 */ + /* 不足 8 字节的残包:无头可剥,源地址取固定对端 */ net_udp_src_from_peer(p_sock, src_addr, addrlen); payload = avail; }