【LwIP - UDP】- 实现UDP通讯

目录socket

 

基于LwIP实现UDP通讯code

1 什么是UDP接口

2 基于raw/callback API的UDPip

3 raw/callback API UDP的绑定、链接和发送内存


基于LwIP实现UDP通讯

1 什么是UDP

UDP,即用户数据包协议,属于TCP/IP 中的传输层。一样,TCP,即传输控制协议,也是属于TCP/IP传输层。这二者区别在此处不加以解释,本文主要讲解如何经过LwIP实现UDP传输。开发

UDP在传输数据以前不需创建链接。远端收到UDP用户数据报,是不须要给出任何应答。虽然UDP是一种不可靠的交付,但在某些状况下UDP倒是一种有效的传输方式。rem

 

2 基于raw/callback API的UDP

raw/callback API提供了多个UDP相关的接口,以下所示:input

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_initit

与UDP内存池和内存栈初始化相关。io

1.2 udp_input

处理UDP用户数据报相关。

1.3 udp_new和udp_remove

udp_new: 动态分配一个UDP控制块,该控制块记录本地socket和远端socket等信息。

udp_remove: 释放UDP控制块

至于剩下的接口,上面的注释已经解释很清楚了。如下主要分析udp_bind、udp_connect和几个发送接口。

3 raw/callback API UDP的绑定、链接和发送

udp_connect和几个发送接口,在没有开发者没有为UDP控制块绑定本地socket时,这几个接口默认会调用udp_bind,为UDP控制块绑定socket,其中端口是动态分配的。

udp_conncet与udp_bind的区别在于UDP控制块的标志是否被设置为链接态(UDP_FLAGS_CONNECTED),所以咱们能够对这个进行划分:

  • 链接态: udp_conncet
  • 非链接态: udp_bind

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;
}