当前位置: 首页 > backend >正文

UDP报文的数据结构

主要内容参照https://doc.embedfire.com/net/lwip/zh/latest/doc/chapter14/chapter14.html#id6,整理出来自用。

1. UDP 报文首部结构体(udp_hdr)

        为清晰定义 UDP 报文首部的各个字段,LwIP 设计了udp_hdr结构体,其包含 4 个核心字段,具体结构通过代码定义,各字段功能如下:

  • srcdest:均为 16 位无符号整数(u16_t),分别表示 UDP 通信的源端口号和目的端口号,用于标识通信双方的应用进程。
  • len:16 位无符号整数,代表 UDP 报文的总长度(包括首部和数据部分)。
  • chksum:16 位无符号整数,用于 UDP 报文的校验和计算,保障数据传输的完整性(若值为 0 则表示不进行校验)。

        结构体定义中使用PACK_STRUCT相关宏,是为了确保结构体在内存中紧凑存储,避免因编译器对齐规则导致字段偏移,保证数据解析的准确性。

2. UDP 控制块(udp_pcb)

        与 TCP 类似,LwIP 通过 “UDP 控制块” 管理 UDP 通信的所有关键信息,每个基于 UDP 的应用线程都会对应一个控制块,并与特定端口绑定,以便系统识别和处理该应用的 UDP 数据。

(1)控制块的核心组成

UDP 控制块结构体(udp_pcb)的内容可分为两部分:

#define IP_PCB                             \/* 本地ip地址与远端IP地址 */             \ip_addr_t local_ip;                      \ip_addr_t remote_ip;                     \/* 网卡id */                             \u8_t netif_idx;                          \/* Socket 选项 */                        \u8_t so_options;                         \/* 服务类型   */                         \u8_t tos;                                \/* 生存时间 */                           \u8_t ttl                                 \IP_PCB_NETIFHINT/** UDP控制块 */struct udp_pcb{IP_PCB;//指向下一个控制块struct udp_pcb *next;//控制块状态u8_t flags;/** 本地端口号与远端端口号 */u16_t local_port, remote_port;/** 接收回调函数 */udp_recv_fn recv;/** 回调函数参数 */void *recv_arg;};
  • 复用 IP 层信息:通过引入IP_PCB宏,直接包含 IP 层通信所需的基础信息,如本地 IP 地址、远端(目标)IP 地址、网卡 ID(netif_idx)、Socket 选项(so_options)、服务类型(tos)和生存时间(ttl),避免信息重复定义,简化 IP 层与 UDP 层的交互。
  • UDP 层专属信息:包括控制块链表指针(next,用于连接多个控制块)、控制块状态标识(flags)、本地端口号与远端端口号(local_portremote_port,核心标识字段,用于匹配应用线程),以及接收数据的回调函数(recv)和回调参数(recv_arg,用于数据递交给上层应用)。
(2)控制块的管理方式

        LwIP 会将所有 UDP 控制块通过next指针串联成一个链表,链表的头节点由全局变量udp_pcbs记录。这种链表结构便于系统在处理 UDP 数据时,通过遍历链表快速查找对应的控制块,提高数据处理效率。

(3)回调函数的注册

        回调函数(udp_recv_fn)是 UDP 层向应用层递交数据的关键接口,其函数原型(见原文代码清单 14_3)规定了参数格式:回调参数(arg)、对应的 UDP 控制块(pcb)、存储数据的缓冲区(pbuf)、数据来源的 IP 地址(addr)和端口号(port)。

        回调函数的注册通过udp_recv函数实现:该函数将用户定义(或系统默认)的回调函数及其参数,分别赋值给控制块的recvrecv_arg字段。实际开发中,若使用 NETCONN API 或 Socket API,LwIP 内核会自动注册recv_udp作为回调函数,无需用户手动实现;若使用 RAW API,则需用户自行定义并注册回调函数。

二、UDP 报文发送流程

        UDP 作为传输层协议,需接收上层应用数据并添加首部后,交付给 IP 层发送,核心逻辑由udp_sendto_if_src函数实现,整体流程简洁,具体步骤如下:

  1. 端口绑定检查:若当前 UDP 控制块未绑定本地端口(local_port为 0),则先调用udp_bind函数完成端口绑定,确保数据能被正确识别和处理。
  2. 数据长度与内存检查
    • 校验数据总长度:判断添加 UDP 首部(长度为 UDP_HLEN)后,总长度是否超过 16 位整数的最大值(避免溢出),若超过则返回内存错误(ERR_MEM)。
    • 检查缓冲区空间:尝试在当前数据缓冲区(pbuf)头部预留 UDP 首部空间,若空间不足,则新分配一个仅存储首部的pbuf,并与原数据缓冲区链接成链表(pbuf_chain)。
  3. 填充 UDP 首部:将控制块中的本地端口号、目标端口号,以及缓冲区总长度(转换为网络字节序,通过lwip_htons函数),分别填入 UDP 首部的srcdestlen字段;校验和字段(chksum)默认设为 0(表示不校验,可根据需求调整)。
  4. 交付 IP 层发送:调用ip_output_if_src函数,将封装好 UDP 首部的数据交付给 IP 层,由 IP 层负责通过指定网卡(netif)发送到目标地址;发送完成后,根据缓冲区是否为新分配,决定是否释放内存(pbuf_free),并更新相关统计指标(如udp.xmitmib2.udpoutdatagrams)。

        相较于 TCP,UDP 发送流程无需建立连接、重传确认等复杂逻辑,仅需完成首部封装和层间交付,处理效率更高。

三、UDP 报文接收流程

        当 IP 层接收到 UDP 报文后,会调用udp_input函数将数据递交给 UDP 层处理,核心是通过匹配控制块找到对应应用,并完成数据递交,具体步骤如下:

  1. 合法性初步校验

    • 检查报文长度:若当前缓冲区长度小于 UDP 首部长度(UDP_HLEN),则判定为无效报文,更新错误统计(如udp.lenerr)并释放缓冲区,直接结束处理。
    • 解析基础信息:提取 UDP 首部(转换为udp_hdr类型),判断报文是否为广播包(ip_addr_isbroadcast),并将源端口号、目的端口号从网络字节序转换为主机字节序(lwip_ntohs)。
  2. 遍历控制块链表匹配应用

    • 遍历udp_pcbs链表,对比控制块的 “本地端口号” 与报文的 “目的端口号”,同时通过udp_input_local_match函数校验 IP 地址匹配性(本地 IP 与报文目的 IP),筛选出候选控制块。
    • 进一步筛选 “完全匹配” 的控制块:在候选控制块中,对比控制块的 “远端端口号” 与报文的 “源端口号”、控制块的 “远端 IP” 与报文的 “源 IP”,若均匹配,则确定为目标控制块;若存在多个候选,会将完全匹配的控制块移至链表头部,优化后续查找效率。
    • 无完全匹配时的处理:若未找到完全匹配的控制块,会选取第一个未绑定远端信息(UDP_FLAGS_CONNECTED未置位)的候选控制块作为替代;若仍无候选,则判定为 “无对应应用”。
  3. 数据递交或差错反馈

    • 数据递交(找到对应控制块):先从缓冲区中移除 UDP 首部(pbuf_remove_header),提取纯数据部分;若控制块已注册回调函数(recv不为空),则调用该函数,将数据、源 IP、源端口等信息递交给上层应用(回调函数需负责后续缓冲区释放);若未注册回调函数,则直接释放缓冲区。
    • 差错反馈(无对应控制块):若报文非广播包或多播包,会构造 “端口不可达” 的 ICMP 差错报文(通过icmp_port_unreach函数),反馈给报文源主机,同时释放缓冲区并更新统计指标(如udp.proterrmib2.udpnoports)。
  4. 资源清理与统计更新:处理结束后,释放相关资源(如缓冲区),停止性能计时(PERF_STOP),并更新 UDP 接收相关的统计数据(如udp.recvmib2.udpindatagrams)。

http://www.xdnf.cn/news/18477.html

相关文章:

  • 可转换债券高频交易Level-2五档Tick级分钟历史数据分析
  • 20250823解决荣品RD-RK3588-MID核心板的底板的adb不通
  • 超越基础:Glide 高级优化与自定义实战
  • 12.Shell脚本修炼手册--函数的基础认知与实战演练(fock炸弹!!)
  • 第1.2节:早期AI发展(1950-1980)
  • Mybatis Plus - 代码生成器简单使用
  • Baumer高防护相机如何通过YoloV8深度学习模型实现社交距离的检测识别(python)
  • 【204页PPT】某著名企业信息化规划方案(附下载方式)
  • 【攻防世界】Web_php_include
  • GitLab CI:安全扫描双雄 SAST vs. Dependency Scanning 该如何抉择?
  • 阿德莱德多模态大模型导航能力挑战赛!NavBench:多模态大语言模型在具身导航中的能力探索
  • C++ csignal库详细使用介绍
  • 密码管理中Null 密码
  • 第九届86358贾家庄短片周在山西汾阳贾家庄举办
  • 齐次变换矩阵的逆变换:原理与SymPy实现
  • FIFO核心原理与机制
  • 解决 SymPy Lambdify 中的符号覆盖与语法错误问题
  • PiscCode使用 MediaPipe 检测人脸关键点多样展示
  • 大数据世界的开拓者:深入浅出MapReduce分布式计算经典范式
  • 相似度、距离
  • 一次性密码(OTP)原理及应用
  • OFD格式文件及Python将PDF转换为OFD格式文件
  • Centos 8 管理防火墙
  • 多目标跟踪中基于目标威胁度评估的传感器控制方法复现
  • LeeCode 40.组合总和II
  • SpringBoot -- 集成Spring Security (二)
  • CTFSHOW | 其他篇题解(二)web417 - web437
  • LeetCode第55题 - 跳跃游戏
  • 学习游戏制作记录(合成表UI和技能树的UI)8.22
  • SpringBoot项目创建的五种方式