什么是UDP?
UDP是用户数据报协议,是OSI参考模型中的传输层协议。
UDP的特点
缺点:无连接的,不可靠的,不能保证数据安全到达目的地。
优点:消耗资源小,处理速度快。
UDP通信的流程
- 创建一个udp协议控制块。
使用函数:udp_new();
返回udp_pcb *
类型的指针。 - 建立“连接”。注意这里没有真正意义上的连接,没有握手挥手的过程。 只是设置了远端的ip地址和端口,也就是把远端的ip和端口保存到了udp协议控制块中。
使用函数udp_connect
建立“连接”。
在UDP传输中建立“连接”并不是必须的。建立“连接”只是让远端的ip地址和端口保存到udp协议控制块中。
如果执行了建立“连接”这个过程,那么后续可以使用udp_send
函数去发送数据;
如果没有执行建立“连接”这个过程,后续就必须使用udp_sendto
函数去发送数据; - 绑定,这个绑定是必须的,把本机的IP地址端口号,存储到udp协议控制块中。
使用函数udp_bind
。 - 注册回调函数。这个函数会在UDP收到数据以后被调用。
- 经过上边的4步,可谓是万事俱备,只欠东风。我们可以发送数据了。
这一步的核心是通过udp_send
发送数据,不过还需要别的函数来打个辅助。- 申请一段内存。使用
pbuf_alloc
函数。 - 拷贝数据到
pbuf
,使用函数pbuf_take
。 - 发送数据。使用函数
udp_send
。 - 释放内存。使用函数
pbuf_free
。
这些辅助函数其实就是把要发送的数据组织成链表的形式,然后通过udp_send
函数把链表的内容发送出去。
- 申请一段内存。使用
实验功能
开发板可以向PC机上的网络调试助手发送数据。
PC机上的网络调试助手向开发板发送数据。
部分实验源码:
udppcb=udp_new();//创建一个UDP
if(udppcb != NULL)
{
//把远端ip地址整合到struct ip_addr类型的rmtipaddr变量中
IP4_ADDR(&rmtipaddr, lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);
err = udp_connect(udppcb, &rmtipaddr, UDP_DEMO_PORT);//建立"连接",这里只是把udppcb和rmtipaddr关联起来
if(err == ERR_OK)
{
IP4_ADDR(&localaddr, lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
err = udp_bind(udppcb, &localaddr, UDP_DEMO_PORT);//绑定 把udppcb和本机ip关联起来
if(err == ERR_OK)
{
udp_recv(udppcb, udp_demo_recv, NULL);//注册接收回调函数
while(1)
{
ptr=pbuf_alloc(PBUF_TRANSPORT,strlen((char*)tcp_demo_sendbuf),PBUF_POOL); //申请内存
if(ptr)
{
pbuf_take(ptr,(char*)tcp_demo_sendbuf,strlen((char*)tcp_demo_sendbuf)); //将tcp_demo_sendbuf中的数据拷贝到ptr所指地址处
udp_send(udppcb,ptr); //udp发送数据
pbuf_free(ptr);//释放内存
}
delay_ms(100);//延时100ms
lwip_periodic_handle();//lwip周期性处理的事务都在此函数
if(udp_demo_flag&1<<6)
{
printf("%s",udp_demo_recvbuf);
udp_demo_flag&=~(1<<6);//标记数据已经被处理了.
}
}
}
}
}
开发板接收网络调试助手的数据
我们使用udp_recv函数注册好回调函数,回调函数会被lwip自动调用。
回调函数如下:
void udp_demo_recv(void *arg,struct udp_pcb *upcb,struct pbuf *p,struct ip_addr *addr,u16_t port)
{
u32 data_len = 0;
struct pbuf *q;
if(p!=NULL) //接收到不为空的数据时
{
memset(udp_demo_recvbuf,0,UDP_DEMO_RX_BUFSIZE); //数据接收缓冲区清零
for(q=p;q!=NULL;q=q->next) //p指向的是pbuf类型的链表,
{
if(q->len > (UDP_DEMO_RX_BUFSIZE-data_len)) //本次要拷贝的数据长度大于剩余缓存长度
memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_DEMO_RX_BUFSIZE-data_len));//只能拷贝剩余缓存长度的数据,其余的丢掉
else
memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);//拷贝当前节点内的全部数据
data_len += q->len;
if(data_len > UDP_DEMO_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出
}
udp_demo_flag|=1<<6; //标记接收到数据了
pbuf_free(p);//释放掉pbuf的内存
}
}
我们先来看一下接收回调函数的类型定义。
/** Function prototype for udp pcb receive callback functions
* addr and port are in same byte order as in the pcb
* The callback is responsible for freeing the pbuf
* if it's not used any more.
*
* ATTENTION: Be aware that 'addr' points into the pbuf 'p' so freeing this pbuf
* makes 'addr' invalid, too.
*
* @param arg user supplied argument (udp_pcb.recv_arg)
* @param pcb the udp_pcb which received data
* @param p the packet buffer that was received
* @param addr the remote IP address from which the packet was received
* @param port the remote port from which the packet was received
*/
typedef void (*udp_recv_fn)(void *arg, struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *addr, u16_t port);
第一个参数arg
:回调函数的参数。
第二个参数pcb
:哪一个pcb接收到了数据
第三个参数p
:接收数据的缓存区,是链表形式的
第四个参数addr
:远端的ip
第五个参数port
:远端的端口
pbuf节点串成一条链表, pbuf结构体成员变量payload指向了网络数据的缓存地址。我们只要处理pbuf链表就可以了,也就是把pbuf成员变量payload指向的缓存数据拷贝出来。
在接收回调函数中有两个重点工作,第一个就是拷贝数据,第二个就是释放内存。
下面是网络调试助手向开发板发送数据效果
不调用udp_connect函数发送数据
前面说过udp传输可以不调用udp_connect
函数,发送的时候调用udp_sendto
函数,验证一下是否可行。
udppcb=udp_new();//创建一个UDP
if(udppcb != NULL)
{
//把远端ip地址整合到struct ip_addr类型的rmtipaddr变量中
IP4_ADDR(&rmtipaddr, lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);
// err = udp_connect(udppcb, &rmtipaddr, UDP_DEMO_PORT);//建立"连接",这里只是把udppcb和rmtipaddr关联起来
// if(err == ERR_OK)
{
IP4_ADDR(&localaddr, lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
err = udp_bind(udppcb, &localaddr, UDP_DEMO_PORT);//绑定 把udppcb和本机ip关联起来
if(err == ERR_OK)
{
udp_recv(udppcb, udp_demo_recv, NULL);//注册接收回调函数
while(1)
{
ptr=pbuf_alloc(PBUF_TRANSPORT,strlen((char*)tcp_demo_sendbuf),PBUF_POOL); //申请内存
if(ptr)
{
pbuf_take(ptr,(char*)tcp_demo_sendbuf,strlen((char*)tcp_demo_sendbuf)); //将tcp_demo_sendbuf中的数据拷贝到ptr所指地址处
udp_sendto(udppcb, ptr,&rmtipaddr, UDP_DEMO_PORT);
// udp_send(udppcb,ptr); //udp发送数据
pbuf_free(ptr);//释放内存
}
delay_ms(100);//延时100ms
lwip_periodic_handle();//lwip周期性处理的事务都在此函数
if(udp_demo_flag&1<<6)
{
printf("%s",udp_demo_recvbuf);
udp_demo_flag&=~(1<<6);//标记数据已经被处理了.
}
}
}
}
}
到此测试完成。
一个疑问:
打开网络调式助手,开发板发送的数据网络调试助手是收不到的,必须先用网络助手给开发板发送一包数据,然后网络调试助手才能收到开发板的数据。不知道为什么。