- UDP协议简介
UDP是User Datagram Protocol的简称,中文名是用户数据报协议,是一种无连接、不可靠的协议,它只是简单地实现从一端主机到另一端主机的数据传输功能,这些数据通过IP层发送,在网络中传输,到达目标主机的顺序是无法预知的,因此需要应用程序对这些数据进行排序处理,这就带来了很大的不方便,此外,UDP协议更没有流量控制、拥塞控制等功能,在发送的一端,UDP只是把上层应用的数据封装到UDP报文中,在差错检测方面,仅仅是对数据进行了简单的校验,然后将其封装到IP数据报中发送出去。而在接收端,无论是否收到数据,它都不会产生一个应答发送给源主机,并且如果接收到数据发送校验错误,那么接收端就会丢弃该UDP报文,也不会告诉源主机,这样子传输的数据是无法保障其准确性的,如果想要其准确性,那么就需要应用程序来保障了。
UDP协议的特点:
- 无连接、不可靠。
- 尽可能提供交付数据服务,出现差错直接丢弃,无反馈。
- 面向报文,发送方的UDP拿到上层数据直接添加个UDP首部,然后进行校验后就递交给IP层。而接收的一方在接收到UDP报文后简单进行校验,然后直接去除,数据递交给上层应用。
- 支持一对一,一对多,多对一,多对多的交互通信。
- 速度快,UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是
一个无状态的传输协议,所以它在传递数据时非常快,即使在网络拥塞的时候UDP也不会降低发送的数据。
- UDP常用端口号
与TCP协议一样,UDP 报文协议根据对应的端口号传递到目标主机的应用线程,同样的,传输层到应用层的唯一标识是通过端口号决定的,两个线程之间进行通信必须用端口号进行识别,同样的使用“IP 地址+端口号”来区分主机不同的线程。
常用的端口号如下:
UDP报文
UDP报文也被称为用户数据报,与TCP协议一样,由报文首部与数据区域组成。在UDP协议中,它只是简单将应用层的数据进行封装(添加一个 UDP 报文首部),然后传递到IP层,再通过网卡发送出去,因此,UDP数据也是经过两次封装,具体见下图。
UDP 报文结构示意图具体见下图:
端口号的取值在0~65535 之间;16bit的总长度用于记录UDP报文的总长度,包括8字节的首部长度与数据区域。
- FreeRTOS的创建套接字函数
函数原型:
创建并启动一个套接字。
参数描述:
xDomain:网络类型,创建UDP套接字时必须使用参数FREERTOS_AF_INET。
xType:套接字接口类型,参数只能为FREERTOS_SOCK_STREAM或FREERTOS_SOCK_DGRAM。使用FREERTOS_SOCK_STREAM参数创建一个TCP套接字接口,使用FREERTOS_SOCK_DGRAM参数创建一个UDP套接字接口。
xProtocol:套接字协议类型,参数只能为FREERTOS_IPPROTO_TCP或者FREERTOS_IPPROTO_UDP。使用FREERTOS_IPPROTO_TCP参数将套接字协议类型设置为TCP,使用FREERTOS_IPPROTO_UDP参数将套接字协议类型设置为UDP。
返回值:
如果套接字创建成功,返回套接字句柄。如果FreeRTOS堆栈不足,返回FreeRTOS_VALID_SOCKET。
- FreeRTOS的设置套接字选项函数
BaseType_t FreeRTOS_setsockopt( Socket_t xSocket, int32_t lLevel, int32_t lOptionName, const void *pvOptionValue, size_t xOptionLength );
设置指定套接字的指定参数。
参数描述:
xSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。
lLevel:该参数未使用。
lOptionName:修改的目标选项。参数选择见下图。pvOptionValue:修改的目标选项的值。不同的目标选项,参数值的类型也不同。参见上图。
xOptionLength:该参数未使用。
返回值:
如果参数lOptionName的值不属于合法值,返回FreeRTOS_ENOPROTOOPT。如果参数lOptionName的值属于合法值,返回0。
- FreeRTOS的向套接字发送数据函数
函数原型:
int32_t FreeRTOS_sendto( xSocket_t xSocket, const void *pvBuffer, size_t xTotalDataLength, uint32_t ulFlags, const struct freertos_sockaddr *pxDestinationAddress, socklen_t xDestinationAddressLength );
将数据发送到套接字。该套接字必须由FreeRTOS_socket()创建。
参数描述:
xSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。
*pvBuffer:指针,指向要发送的数据缓冲区。如果使用标准发送,将数据拷贝只到IP堆栈缓冲区。如果使用零拷贝发送,则pvBuffer指向以前从IP堆栈获得的缓冲区,该缓冲区已经包含正在发送的数据。IP堆栈将控制缓冲区,而不是将数据复制到缓冲区中。
xTotalDataLength:要发送数据的字节数。
ulFlags:发送方式选项,使用标准发送或者零拷贝发送。
pxDestinationAddress:指向FreeRTOS_sockaddr结构的指针,该结构包含目标IP地址和端口号(将数据发送到的套接字)。
xDestinationAddressLength:该参数为使用。
返回值:
如果正常发送返回发送字节数,如果发送失败返回0。
- FreeRTOS的从套接字接收数据函数
函数原型:
int32_t FreeRTOS_recvfrom( xSocket_t xSocket, void *pvBuffer, size_t xBufferLength, uint32_t ulFlags, struct freertos_sockaddr *pxSourceAddress, socklen_t *pxSourceAddressLength );
从套接字接收数据。该套接字必须由FreeRTOS_socket()创建。
参数描述:
xSocket:目标套接字,该套接字必须由FreeRTOS_socket()创建。
*pvBuffer:指针,指向储存接收数据的缓冲区。如果使用标准接收,将数据拷贝到接收缓冲区。如果使用零拷贝接收,那么*pvBuffer将被设置指向已经保存接收数据的缓冲区。PvBuffer用于从FreeRTOS_recvfrom()中传递对接收数据的引用,而不复制任何数据。
xBufferLength:接收缓冲区的字节大小。如果使用零拷贝,该参数无效。
ulFlags:接收方式选项,使用标准发送或者零拷贝发送。
pxSourceAddress:指向FreeRTOS_sockaddr结构的指针,该结构将被设置,以包含刚接收数据的套接字的IP地址和端口号。
pxSourceAddressLength:该参数未使用。
返回值:
如果接收超时返回FreeRTOS_EWOULDBLOCK,如果套接字没有绑定端口返回FreeRTOS_EINVAL,如果正常接收返回接收到的字节数。
部分代码、详细代码请参考附加资源。
#include "UDP_Demo.h"
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "FreeRTOS_UDP_IP.h"
// 定义目标端口号
#define echoECHO_PORT (6902)
struct freertos_sockaddr xEchoServerAddress;
Socket_t xSocket_UDP;
static const TickType_t xReceiveTimeOut = pdMS_TO_TICKS( 2000 );
// 创建UDP套接字
void UDP_Creat(void)
{
// 将目标端口和IP地址填入结构体
xEchoServerAddress.sin_port = FreeRTOS_htons( echoECHO_PORT );
xEchoServerAddress.sin_addr = FreeRTOS_inet_addr_quick( configECHO_SERVER_ADDR0,configECHO_SERVER_ADDR1,configECHO_SERVER_ADDR2,configECHO_SERVER_ADDR3 );
// 创建UDP套接字
xSocket_UDP = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_DGRAM, FREERTOS_IPPROTO_UDP );
configASSERT( xSocket_UDP != FREERTOS_INVALID_SOCKET );
// 设置接收超时时间
FreeRTOS_setsockopt( xSocket_UDP, NULL, FREERTOS_SO_RCVTIMEO, &xReceiveTimeOut, NULL );
}
// UDP发送函数
void UDP_Send(uint8_t *pSendBuff,uint32_t lBuffLen)
{
// 调用套接字发送函数,将数据发送到套接字
FreeRTOS_sendto(xSocket_UDP,pSendBuff,lBuffLen,0,&xEchoServerAddress,NULL);
}
// UDP接收函数
void UDP_Recive(uint8_t * buffer,uint32_t lBuffLen)
{
// 调用套接字接收函数,从套接字接受数据
FreeRTOS_recvfrom(xSocket_UDP,buffer,lBuffLen,0,&xEchoServerAddress,NULL);
}
uint8_t senddata[]=" Please send a message, the length is less than 90!";
uint8_t recivedata[100];
// UDP实验任务
void Task_UDPTest(void *pvParameters)
{
uint8_t num = 0;
UDP_Creat();
while(1)
{
if(num == 0) UDP_Send(senddata,sizeof (senddata));
UDP_Recive(recivedata,sizeof(recivedata));
if(recivedata[0] != 0)
{
num = 1;
UDP_Send(recivedata,sizeof (recivedata));
memset( ( void * ) recivedata, 0x00, sizeof( recivedata ) );
}
vTaskDelay(50 / portTICK_RATE_MS );
}
}