RAW API 的 UDP 总结1
主要内容参照17. 使用RAW API接口编程 — [野火]LwIP应用开发实战指南—基于野火STM32 文档,整理出来自用。
1.1 新建控制块:udp_new()
UDP 通信的第一步是创建 UDP 控制块,它是存储 UDP 通信相关信息的核心结构,无控制块则无法进行后续通信。
核心功能
- 从内存池(
MEMP_UDP_PCB
类型)中申请一块内存,用于存放 UDP 控制块; - 若内存申请成功,将控制块所有字段初始化为 0,并设置默认生存时间(
ttl = UDP_TTL
); - 返回申请到的控制块指针(失败则返回
NULL
)。
关键说明
- 控制块是 UDP 通信的 “唯一标识载体”,后续绑定、收发数据均依赖此结构;
- 函数内通过
LWIP_ASSERT_CORE_LOCKED()
确保操作在核心锁保护下进行,保证线程安全。
1.2 绑定控制块:udp_bind()
绑定的本质是将 “本机 IP 地址 + 端口号” 与 UDP 控制块关联,使控制块对应唯一应用,确保 UDP 报文能被正确识别并递交到应用层。
核心功能
- IP 地址处理:若传入的
ipaddr
为NULL
,自动绑定到 “任意 IP 地址”(IP4_ADDR_ANY
); - 端口号处理:
- 若端口号为 0,调用
udp_new_port()
随机分配一个未占用的端口; - 若端口号非 0,检查
udp_pcbs
链表(存储所有 UDP 控制块),确保端口未被其他控制块占用(避免冲突);
- 若端口号为 0,调用
- 初始化与入链表:将 “本机 IP + 端口号” 写入控制块的
local_ip
和local_port
字段,若控制块未在udp_pcbs
链表中,则将其插入链表。
关键说明
- 绑定是 UDP 接收报文的前提:无绑定端口的控制块无法接收报文(无法识别报文归属);
- 函数返回
ERR_OK
表示绑定成功,返回ERR_USE
表示端口已占用或无可用端口。
1.3 建立会话:udp_connect()
UDP 是无连接协议,不存在 TCP 式的 “三次握手”,此处 “建立会话” 仅为设置控制块的 “远端 IP + 端口号”,明确数据发送的目标地址。
核心功能
- 前置检查:若控制块未绑定 “本机 IP + 端口号”(
local_port == 0
),自动调用udp_bind()
完成绑定; - 设置远端信息:将传入的远端 IP、端口号分别写入控制块的
remote_ip
和remote_port
字段; - 标记会话状态:设置控制块标志位
UDP_FLAGS_CONNECTED
,表示进入 “会话状态”; - 入链表校验:检查控制块是否已在
udp_pcbs
链表中,未在则插入链表首部。
关键说明
- 会话建立仅 “本地记录远端信息”,不向远端发送任何连接请求(区别于 TCP);
- 进入会话状态后,调用
udp_send()
发送数据时无需重复指定远端地址(直接使用控制块中记录的信息)。
1.4 断开会话:udp_disconnect()
与udp_connect()
功能相反,用于清除控制块中的远端信息,取消 “会话状态”,但不释放控制块内存。
核心功能
- 清除远端信息:将控制块的
remote_ip
设为 “任意地址”、remote_port
设为 0、netif_idx
设为NETIF_NO_INDEX
; - 取消会话状态:通过
udp_clear_flags()
清除UDP_FLAGS_CONNECTED
标志位。
关键提示
- 断开会话仅重置控制块的远端配置,控制块仍存在于
udp_pcbs
链表中,可重新通过udp_connect()
建立新会话; - 函数无返回值,仅需传入待处理的控制块指针。
1.5 接收数据:udp_recv()
该函数并非直接接收数据,而是设置数据接收的 “回调桥梁”,让内核在接收到 UDP 报文后,能通过回调函数将数据递交给应用层。
核心功能
- 定义回调函数类型:
udp_recv_fn
类型回调函数需包含 4 个参数:void *arg
:用户自定义参数;struct udp_pcb *pcb
:接收数据对应的 UDP 控制块;struct pbuf *p
:存储接收数据的缓冲区(pbuf
为 LWIP 的通用数据缓冲区结构);const ip_addr_t *addr
/u16_t port
:发送方的 IP 地址和端口号;
- 绑定回调函数:将传入的回调函数指针
recv
和用户参数recv_arg
,分别赋值给控制块的recv
和recv_arg
字段。
关键说明
- 回调函数是 “内核→应用层” 的数据通道:内核接收到 UDP 报文后,会匹配对应的控制块,再调用其绑定的
recv
函数传递数据; - 若未设置回调函数,内核无法将接收的 UDP 报文递交给应用层。
1.6 发送数据:udp_send()
与udp_sendto()
两者均用于发送 UDP 数据,最终都依赖 IP 层和链路层完成报文封装,核心区别是 “是否需要指定远端地址”。
共同流程
- 数据需先封装为
pbuf
结构,且pbuf
需预留 UDP、IP、以太网首部的空间; - 函数内部会填充 UDP 首部,形成完整 UDP 报文后递交给 IP 层;
- IP 层添加 IP 首部后递交给链路层,链路层添加以太网首部后发送。
功能差异与核心逻辑
函数 | 适用场景 | 核心逻辑 |
---|---|---|
udp_send() | 已通过udp_connect() 建立会话的场景 | 直接使用控制块中remote_ip 和remote_port 作为目标地址,若未设置远端信息则返回ERR_VAL ,最终调用udp_sendto() 发送; |
udp_sendto() | 未建立会话 / 需动态指定目标地址的场景 | 需手动传入目标 IP(dst_ip )和端口(dst_port ),先通过ip_route() 找到合适的网卡(netif ),再调用udp_sendto_if() 完成发送; |
关键说明
- 若未找到合适的发送网卡(
netif == NULL
),返回ERR_RTE
(路由错误); - 两者均返回
ERR_OK
表示发送成功,返回ERR_VAL
表示参数不合法(如 IP 版本不匹配)。
1.7 删除 UDP 控制块:udp_remove()
当 UDP 通信结束后,需通过此函数彻底删除控制块,释放占用的内存资源,避免内存泄漏。
核心功能
- 解绑与链表删除:
- 调用
mib2_udp_unbind()
取消控制块的绑定状态; - 若控制块在
udp_pcbs
链表首部,直接将链表头指向udp_pcbs->next
; - 若控制块在链表中间 / 尾部,遍历链表找到其前驱节点,将前驱节点的
next
指向当前控制块的next
,实现链表移除;
- 调用
- 内存释放:通过
memp_free()
释放控制块占用的MEMP_UDP_PCB
类型内存。
关键说明
- 控制块删除后无法复用,若需重新通信,需通过
udp_new()
重新创建控制块; - 函数无返回值,操作前需确保控制块指针非空(函数内通过
LWIP_ERROR
校验)。