基于LWIP协议栈RAW API的 UDP传输实验

什么是UDP?

UDP是用户数据报协议,是OSI参考模型中的传输层协议。

UDP的特点

缺点:无连接的,不可靠的,不能保证数据安全到达目的地。
优点:消耗资源小,处理速度快。

UDP通信的流程

  1. 创建一个udp协议控制块。
    使用函数:udp_new();
    返回udp_pcb *类型的指针。
  2. 建立“连接”。注意这里没有真正意义上的连接,没有握手挥手的过程。 只是设置了远端的ip地址和端口,也就是把远端的ip和端口保存到了udp协议控制块中。
    使用函数udp_connect建立“连接”。
    在UDP传输中建立“连接”并不是必须的。建立“连接”只是让远端的ip地址和端口保存到udp协议控制块中。
    如果执行了建立“连接”这个过程,那么后续可以使用udp_send函数去发送数据;
    如果没有执行建立“连接”这个过程,后续就必须使用udp_sendto函数去发送数据;
  3. 绑定,这个绑定是必须的,把本机的IP地址端口号,存储到udp协议控制块中。
    使用函数udp_bind
  4. 注册回调函数。这个函数会在UDP收到数据以后被调用。
  5. 经过上边的4步,可谓是万事俱备,只欠东风。我们可以发送数据了。
    这一步的核心是通过udp_send发送数据,不过还需要别的函数来打个辅助。
    1. 申请一段内存。使用pbuf_alloc函数。
    2. 拷贝数据到pbuf,使用函数pbuf_take
    3. 发送数据。使用函数udp_send
    4. 释放内存。使用函数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);//标记数据已经被处理了.
				} 					
			}
		}
	}
}

到此测试完成。
一个疑问:
打开网络调式助手,开发板发送的数据网络调试助手是收不到的,必须先用网络助手给开发板发送一包数据,然后网络调试助手才能收到开发板的数据。不知道为什么。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值