目录socket
基于LwIP实现UDP通讯code
1 什么是UDP接口
3 raw/callback API UDP的绑定、链接和发送内存
UDP,即用户数据包协议,属于TCP/IP 中的传输层。一样,TCP,即传输控制协议,也是属于TCP/IP传输层。这二者区别在此处不加以解释,本文主要讲解如何经过LwIP实现UDP传输。开发
UDP在传输数据以前不需创建链接。远端收到UDP用户数据报,是不须要给出任何应答。虽然UDP是一种不可靠的交付,但在某些状况下UDP倒是一种有效的传输方式。rem
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和几个发送接口。
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; }