在网络通讯中,我们有常见的TCP与UDP,这两者各有各的优势。今天着重来了解一下UDP通讯协议。
UDP为应用程序提供了一种无需建立连接就可以发送封装的 IP数据包的方法,UDP是User Datagram Protocol的简称,中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
许多应用只支持UDP,如:多媒体数据流,不产生任何额外的数据,即使知道有破坏的包也不进行重发。当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择。
UDP的报文格式:
在UDP协议层次模型中,UDP位于IP层之上。应用程序访问UDP层然后使用IP层传送数据包。IP数据包的数据部分即为UDP数据包。IP层的报头指明了源主机和目的主机地址,而UDP层的报头指明了主机上的源端口和目的端口。UDP传输的段(segment)有8个字节的报头和有效载荷字段构成。
UDP报头由4个域组成,其中每个域各占用2个字节,具体包括源端口号、目标端口号、数据包长度、校验值。
端口号:
UDP协议使用端口号为不同的应用保留其各自的数据传输通道。UDP和TCP协议正是采用这一机制实现对同一时刻内多项应用同时发送和接收数据的支持。数据发送一方(可以是客户端或服务器端)将UDP数据包通过源端口发送出去,而数据接收一方则通过目标端口接收数据。有的网络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态端口。因为UDP报头使用两个字节存放端口号,所以端口号的有效范围是从0到65535。一般来说,大于49151的端口号都代表动态端口。UDP端口号指定有两种方式:由管理机构指定端口和动态绑定的方式。
数据长度
数据报的长度是指包括报头和数据部分在内的总字节数。因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分(又称为数据负载)。数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为65535字节。不过,一些实际应用往往会限制数据报的大小,有时会降低到8192字节。
校验值
UDP协议使用报头中的校验值来保证数据的安全。校验值首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算值将不会相符,由此UDP协议可以检测是否出错。这与TCP协议是不同的,后者要求必须具有校验值。
CH32V307EVT有支持有线网路通讯的接口,电路原理图如下:
工程源码参考如下:
#include "string.h"
#include "eth_driver.h"
#define UDP_RECE_BUF_LEN 1472
u8 MACAddr[6]; //MAC address
u8 IPAddr[4] = { 192, 168, 0, 60 }; //IP address
u8 GWIPAddr[4] = { 192, 168, 0, 1 }; //Gateway IP address
u8 IPMask[4] = { 255, 255, 255, 0 }; //subnet mask
u8 DESIP[4] = { 192, 168, 0, 22 }; //destination IP address
u16 desport = 8080; //destination port
u16 srcport = 8080; //source port
u8 SocketId;
u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][UDP_RECE_BUF_LEN]; //socket receive buffer
u8 MyBuf[UDP_RECE_BUF_LEN];
/*********************************************************************
* @fn mStopIfError
*
* [url=home.php?mod=space&uid=2666770]@Brief[/url] check if error.
*
* [url=home.php?mod=space&uid=3142012]@param[/url] iError - error constants.
*
* [url=home.php?mod=space&uid=1141835]@Return[/url] none
*/
void mStopIfError(u8 iError)
{
if (iError == WCHNET_ERR_SUCCESS)
return;
printf("Error: %02X\r\n", (u16) iError);
}
/*********************************************************************
* @fn TIM2_Init
*
* @brief Initializes TIM2.
*
* @return none
*/
void TIM2_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = { 0 };
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = SystemCoreClock / 1000000 - 1;
TIM_TimeBaseStructure.TIM_Prescaler = WCHNETTIMERPERIOD * 1000 - 1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
NVIC_EnableIRQ(TIM2_IRQn);
}
/*********************************************************************
* @fn WCHNET_CreateUdpSocket
*
* @brief Create UDP Socket
*
* @return none
*/
void WCHNET_CreateUdpSocket(void)
{
u8 i;
SOCK_INF TmpSocketInf;
memset((void *) &TmpSocketInf, 0, sizeof(SOCK_INF));
memcpy((void *) TmpSocketInf.IPAddr, DESIP, 4);
TmpSocketInf.DesPort = desport;
TmpSocketInf.SourPort = srcport++;
TmpSocketInf.ProtoType = PROTO_TYPE_UDP;
TmpSocketInf.RecvStartPoint = (u32) SocketRecvBuf[SocketId];
TmpSocketInf.RecvBufLen = UDP_RECE_BUF_LEN;
i = WCHNET_SocketCreat(&SocketId, &TmpSocketInf);
printf("WCHNET_SocketCreat %d\r\n", SocketId);
mStopIfError(i);
}
/*********************************************************************
* @fn WCHNET_DataLoopback
*
* @brief Data loopback function.
*
* @param id - socket id.
*
* @return none
*/
void WCHNET_DataLoopback(u8 id)
{
#if 1
u8 i;
u32 len;
u32 endAddr = SocketInf[id].RecvStartPoint + SocketInf[id].RecvBufLen; //Receive buffer end address
if ((SocketInf[id].RecvReadPoint + SocketInf[id].RecvRemLen) > endAddr) { //Calculate the length of the received data
len = endAddr - SocketInf[id].RecvReadPoint;
}
else {
len = SocketInf[id].RecvRemLen;
}
i = WCHNET_SocketSend(id, (u8 *) SocketInf[id].RecvReadPoint, &len); //send data
if (i == WCHNET_ERR_SUCCESS) {
WCHNET_SocketRecv(id, NULL, &len); //Clear sent data
}
#else
u32 len, totallen;
u8 *p = MyBuf;
len = WCHNET_SocketRecvLen(id, NULL); //query length
printf("Receive Len = %02x\n", len);
totallen = len;
WCHNET_SocketRecv(id, MyBuf, &len); //Read the data of the receive buffer into MyBuf
while(1){
len = totallen;
WCHNET_SocketSend(id, p, &len); //Send the data
totallen -= len; //Subtract the sent length from the total length
p += len; //offset buffer pointer
if(totallen)continue; //If the data is not sent, continue to send
break; //After sending, exit
}
#endif
}
/*********************************************************************
* @fn WCHNET_HandleSockInt
*
* @brief Socket Interrupt Handle
*
* @param socketid - socket id.
* intstat - interrupt status
*
* @return none
*/
void WCHNET_HandleSockInt(u8 socketid, u8 intstat)
{
if (intstat & SINT_STAT_RECV) //receive data
{
WCHNET_DataLoopback(socketid); //Data loopback
}
if (intstat & SINT_STAT_CONNECT) //connect successfully
{
printf("TCP Connect Success\r\n");
}
if (intstat & SINT_STAT_DISCONNECT) //disconnect
{
printf("TCP Disconnect\r\n");
}
if (intstat & SINT_STAT_TIM_OUT) //timeout disconnect
{
printf("TCP Timeout\r\n");
}
}
/*********************************************************************
* @fn WCHNET_HandleGlobalInt
*
* @brief Global Interrupt Handle
*
* @return none
*/
void WCHNET_HandleGlobalInt(void)
{
u8 intstat;
u16 i;
u8 socketint;
intstat = WCHNET_GetGlobalInt(); //get global interrupt flag
if (intstat & GINT_STAT_UNREACH) //Unreachable interrupt
{
printf("GINT_STAT_UNREACH\r\n");
}
if (intstat & GINT_STAT_IP_CONFLI) //IP conflict
{
printf("GINT_STAT_IP_CONFLI\r\n");
}
if (intstat & GINT_STAT_PHY_CHANGE) //PHY status change
{
i = WCHNET_GetPHYStatus();
if (i & PHY_Linked_Status)
printf("PHY Link Success\r\n");
}
if (intstat & GINT_STAT_SOCKET) {
for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++) { //socket related interrupt
socketint = WCHNET_GetSocketInt(i);
if (socketint)
WCHNET_HandleSockInt(i, socketint);
}
}
}
/*********************************************************************
* @fn main
*
* @brief Main program
*
* @return none
*/
int main(void)
{
u8 i;
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(115200); //USART initialize
printf("UdpClient Test\r\n");
printf("SystemClk:%d\r\n", SystemCoreClock);
printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
printf("net version:%x\n", WCHNET_GetVer());
if ( WCHNET_LIB_VER != WCHNET_GetVer()) {
printf("version error.\n");
}
WCHNET_GetMacAddr(MACAddr); //get the chip MAC address
printf("mac addr:");
for(i = 0; i < 6; i++)
printf("%x ",MACAddr[i]);
printf("\n");
TIM2_Init();
i = ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr); //Ethernet library initialize
mStopIfError(i);
if (i == WCHNET_ERR_SUCCESS)
printf("WCHNET_LibInit Success\r\n");
for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++)
WCHNET_CreateUdpSocket(); //Create UDP Socket
while(1)
{
/*Ethernet library main task function,
* which needs to be called cyclically*/
WCHNET_MainTask();
/*Query the Ethernet global interrupt,
* if there is an interrupt, call the global interrupt handler*/
if(WCHNET_QueryGlobalInt())
{
WCHNET_HandleGlobalInt();
}
}
}
/*********************************************************************
* @fn WCHNET_ETHIsr
*
* @brief Ethernet Interrupt Service program
*
* @return none
*/
void WCHNET_ETHIsr(void)
{
uint32_t int_sta;
int_sta = ETH->DMASR;
if (int_sta & ETH_DMA_IT_AIS)
{
if (int_sta & ETH_DMA_IT_RBU)
{
ETH_DMAClearITPendingBit(ETH_DMA_IT_RBU);
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_AIS);
}
if( int_sta & ETH_DMA_IT_NIS )
{
if( int_sta & ETH_DMA_IT_R )
{
/*If you don't use the Ethernet library,
* you can do some data processing operations here*/
ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
}
if( int_sta & ETH_DMA_IT_T )
{
ETH_DMAClearITPendingBit(ETH_DMA_IT_T);
}
if( int_sta & ETH_DMA_IT_PHYLINK)
{
ETH_PHYLink( );
ETH_DMAClearITPendingBit(ETH_DMA_IT_PHYLINK);
}
if (int_sta & ETH_DMA_IT_TBU)
{
/* Resume DMA transmission */
ETH->DMATPDR = 0;
ETH_DMAClearITPendingBit(ETH_DMA_IT_TBU);
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
}
}
/*********************************************************************
* @fn ETH_Init
*
* @brief Ethernet initialization.
*
* @return none
*/
void ETH_Init( uint8_t *macAddr )
{
#if( PHY_MODE == USE_10M_BASE )
ETH_LedConfiguration( );
#endif
Delay_Ms(100);
ETH_Configuration( macAddr );
ETH_DMATxDescChainInit(DMATxDscrTab, MACTxBuf, ETH_TXBUFNB);
ETH_DMARxDescChainInit(DMARxDscrTab, MACRxBuf, ETH_RXBUFNB);
pDMARxSet = DMARxDscrTab;
pDMATxSet = DMATxDscrTab;
NVIC_EnableIRQ(ETH_IRQn);
}
/*********************************************************************
* @fn ETH_LibInit
*
* @brief Ethernet library initialization program
*
* @return command status
*/
uint8_t ETH_LibInit( uint8_t *ip, uint8_t *gwip, uint8_t *mask, uint8_t *macaddr )
{
uint8_t s;
struct _WCH_CFG cfg;
memset(&cfg,0,sizeof(cfg));
cfg.TxBufSize = ETH_TX_BUF_SZE;
cfg.TCPMss = WCHNET_TCP_MSS;
cfg.HeapSize = WCHNET_MEM_HEAP_SIZE;
cfg.ARPTableNum = WCHNET_NUM_ARP_TABLE;
cfg.MiscConfig0 = WCHNET_MISC_CONFIG0;
cfg.MiscConfig1 = WCHNET_MISC_CONFIG1;
#if( PHY_MODE == USE_10M_BASE )
cfg.led_link = ETH_LedLinkSet;
cfg.led_data = ETH_LedDataSet;
#endif
cfg.net_send = ETH_TxPktChainMode;
cfg.CheckValid = WCHNET_CFG_VALID;
s = WCHNET_ConfigLIB(&cfg);
if( s ){
return (s);
}
s = WCHNET_Init(ip,gwip,mask,macaddr);
ETH_Init( macaddr );
return (s);
}
CH32V307EVT板子连上网线,然后使用MounRiver Studio直接编译,下载到开发板中。
首先查询本机电脑的IP地址,测试ping能否正常收发数据包。
再使用网络调试助手NetAssist,选择UDP通讯协议,设置与代码中相同的端口号,远程主机ip则是CH32V307EVT开发板做为Client端的ip地址,必须与本机电脑的ip同属一个网段,两者的端口号必须一致。测试的效果如下图所示,数据收发没问题。