目录
基于LwIP实现UDP通信
1 什么是UDP
2 基于raw/callback API的UDP
3 raw/callback API UDP的绑定、连接和发送
UDP,即用户数据包协议,属于TCP/IP 中的传输层。同样,TCP,即传输控制协议,也是属于TCP/IP传输层。这两者区别在此处不加以解释,本文主要讲解如何通过LwIP实现UDP传输。
UDP在传输数据之前不需建立连接。远端收到UDP用户数据报,是不需要给出任何应答。虽然UDP是一种不可靠的交付,但在某些情况下UDP却是一种有效的传输方式。
raw/callback API提供了多个UDP相关的接口,如下所示:
struct udp_pcb * udp_new (void); // 动态分配一个UDP控制块
void udp_remove (struct udp_pcb *pcb); // 释放一个UDP控制块
err_t udp_bind (struct udp_pcb *pcb, ip_addr_t *ipaddr,
u16_t port); // UDP控制块绑定本地IP和本地端口
err_t udp_connect (struct udp_pcb *pcb, ip_addr_t *ipaddr,
u16_t port); // UDP控制块连接远端IP和远端端口
void udp_disconnect (struct udp_pcb *pcb); // 取消UDP控制块与远端socket的连接关系
void udp_recv (struct udp_pcb *pcb, udp_recv_fn recv,
void *recv_arg); // UDP回调
err_t udp_sendto_if (struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *dst_ip, u16_t dst_port,
struct netif *netif); // 指定网卡及远端socket发送UDP用户数据报
err_t udp_sendto (struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *dst_ip, u16_t dst_port); // 指定远端socket发送UDP用户数据报
err_t udp_send (struct udp_pcb *pcb, struct pbuf *p); // 根据UDP控制块连接的远端socket发送UDP用户数据报
// 以下几个用于UDP校验,当UDP不使用校验,则校验默认为0。当使用校验,如果校验值为0,则改成0xFFFF。
#if LWIP_CHECKSUM_ON_COPY
err_t udp_sendto_if_chksum(struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *dst_ip, u16_t dst_port,
struct netif *netif, u8_t have_chksum,
u16_t chksum);
err_t udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *dst_ip, u16_t dst_port,
u8_t have_chksum, u16_t chksum);
err_t udp_send_chksum(struct udp_pcb *pcb, struct pbuf *p,
u8_t have_chksum, u16_t chksum);
#endif /* LWIP_CHECKSUM_ON_COPY */
#define udp_flags(pcb) ((pcb)->flags) //
#define udp_setflags(pcb, f) ((pcb)->flags = (f))
/* The following functions are the lower layer interface to UDP. */
void udp_input (struct pbuf *p, struct netif *inp); // UDP输入,获取数据
void udp_init (void); // UDP初始化,主要做UDP的内存池和内存栈初始化
1.1 udp_init
与UDP内存池和内存栈初始化相关。
1.2 udp_input
处理UDP用户数据报相关。
1.3 udp_new和udp_remove
udp_new: 动态分配一个UDP控制块,该控制块记录本地socket和远端socket等信息。
udp_remove: 释放UDP控制块
至于剩下的接口,上面的注释已经解释很清楚了。以下主要分析udp_bind、udp_connect和几个发送接口。
udp_connect和几个发送接口,在没有开发者没有为UDP控制块绑定本地socket时,这几个接口默认会调用udp_bind,为UDP控制块绑定socket,其中端口是动态分配的。
udp_conncet与udp_bind的区别在于UDP控制块的标志是否被设置为连接态(UDP_FLAGS_CONNECTED),因此我们可以对这个进行划分:
udp_conncet用有特定远端socket,而udp_bind用于任何远端socket。也就是说,只要发送给开发板的数据报文的远端socket与开发板本地socket一致,开发板都会正常接收该UDP数据报。如果开发板有UDP控制块指定了远端socket,也有无指定的远端socket控制块,则会优先匹配有远端socket的控制块。当然,收到的UDP数据报的源socket不与开发板任何一个UDP,只要本地socket匹配都会处理该数据报。当然,这种现象是不可能的,毕竟一个UDP控制块对应一个唯一的端口。总的来说,udp_conncet只处理指定远端socket的UDP数据报,而udp_bind可以处理任何远端socket的UDP数据报(前提该数据报符合本地socket)。
下列给出上述两种情况的例子:
(1) 指定远程socket例子
void udp_demo_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *addr, u16_t port)
{
struct ip_addr my_ipaddr;
unsigned char *temp = (unsigned char *)addr;
IP4_ADDR(&my_ipaddr, temp[0], temp[1], temp[2], temp[3]); // 保存源IP
udp_sendto(pcb, p, &my_ipaddr, port); // 将报文返回给原主机
pbuf_free(p);
}
u8 udp_demo(void)
{
struct udp_pcb *pcb;
ip_addr_t remote_ip;
pcb = udp_new();
if(pcb == NULL) // 申请失败
{
return 1;
}else
{
IP4_ADDR(&remote_ip,192, 168, 1, 100);
if(udp_connect(pcb, &remote_ip, 8080) == ERR_OK ) // 连接到指定的IP地址和端口
{
udp_recv(pcb, udp_demo_callback, NULL); // 注册报文处理回调
printf("local_port %d\r\n", pcb->local_port);
}else
return 1;
}
return 0;
}
(2) 不指定远程socket例子
void udp_demo_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *addr, u16_t port)
{
struct ip_addr my_ipaddr;
unsigned char *temp = (unsigned char *)addr;
IP4_ADDR(&my_ipaddr, temp[0], temp[1], temp[2], temp[3]); // 保存源IP
udp_sendto(pcb, p, &my_ipaddr, port); // 将报文返回给原主机
pbuf_free(p);
}
u8 udp_demo(void)
{
struct udp_pcb *pcb;
ip_addr_t remote_ip;
pcb = udp_new();
if(pcb == NULL) // 申请失败
{
return 1;
}else
{
if(udp_bind(pcb, IP_ADDR_ANY, 8080) == ERR_OK ) // 为本地IP绑定端口,IP_ADDR_ANY为0,其实说明使用本地IP地址,推荐优先使用。因为DHCP情况下,我们是无法事先知道IP的。
{
udp_recv(pcb, udp_demo_callback, NULL); // 注册报文处理回调
printf("local_port %d\r\n", pcb->local_port);
}else
return 1;
}
return 0;
}