有线网络通信实验2之UDP协议

描述

25.1 UDP协议概述

UDP协议是TCP/IP协议栈中传输层协议,是一个简单的面向数据报的协议,在传输层中还有一个TCP协议,UDP不提供数据包分组,组装,无法对数据包进行排序,当报文发送出去之后无法知道是否安全,完整的到达,但是由于UDP不属于连接性协议,所以消耗资源小,处理速度快,通常用于音频,视频和普通数据传输中,UDP数据包结构如下图所示。

数据传输

端口号表示发送和接收进程,UDP使用端口号为不同的应用保留各自的数据传输通道,UDP和TCP都是采用端口号的形式对同一时刻多个应用同时发送和接受数据,而数据接收方则通过目标端口接受数据,有的网络只能使用预先预留或注册的静态端口,而一些网络可以使用没有被注册的动态端口,由于UDP包头使用两个字节存放端口号,所以端口的有效范围0~65535,一般,大于49151的端口号都代表动态端口。

数据包的长度指的是包括包头和数据部分在内的总字节数,由于包头的长度固定,所以这个区域主要用于计算可变长度的数据部分,数据包的最大长度根据操作环境选择,理论上说,包括包头在内的数据报文最大长度为65535字节。

UDP通过包头中的校验和来保证数据的完整性,校验和首先在数据发送方通过特殊的算法计算出,传递到接收方之后,需要重新计算,如果某个数据在输出过程中被篡改或某种原因损坏,那么发送方和接收方的校验和就会不一致,因此,UDP协议具有检测报文是否出错的能力。

udp.c和udp.h这两个文件就是负责实现UDP传输协议的文件,与UDP报文处理有关的函数之间的关系如下图所示。

数据传输

LWIP协议中API编程方式是基于回调机制的,在我们初始化应用的时候必须为内核中不同的事件注册给出对应的回调函数,当对应的事件发生后这些回调函数就会被调用,udp.c中常用的API功能函数如下表所示。

API函数 函数功能
udp_new 新建一个UDP的PCB块
udp_remove 将一个PCB控制块从链表中删除,并释放这个控制块的内存
udp_bind 为UDP的PCN控制块绑定一个本地IP地址和端口号
udp_connect 连接到指定IP地址主机的指定端口上
udp_disconnent 断开连接,将控制块设置为非连接状态
udp_send 通过一个PCB控制块发送数据
udp_recv 需要创建一个回调函数,当接受到数据的时候被调用

25.2 应用编写

在LWIP/app/udp_demo目录下创建udp_demo.c和udp_demo.h文件。

25.2.1 udp_demo.c代码编写

#include "udp_demo.h" 
#include "delay.h"
#include "usart1.h"
#include "lcd.h"
#include "malloc.h"
#include "string.h"
#include "comm.h"
#include "lwip/pbuf.h"
#include "lwip/udp.h"
#include "lwip/tcp.h"
//UDP 测试全局状态标记变量
//bit6:数据接收状态
//bit5:连接状态
u8 udp_demo_flag;
//设置远端IP地址
void udp_demo_set_remoteip()
{
  u8 *tbuf ;
  LCD_Clear( WHITE ) ;
  POINT_COLOR = RED ;
  tbuf = mymalloc( SRAMIN, 100 ) ;                                  //申请内存
  if( tbuf==NULL )
    return ;
  //前三个IP保持和DHCP得到的IP一致
  lwipdev.remoteip[ 0 ] = lwipdev.ip[ 0 ] ;
  lwipdev.remoteip[ 1 ] = lwipdev.ip[ 1 ] ;
  lwipdev.remoteip[ 2 ] = lwipdev.ip[ 2 ] ;
  lwipdev.remoteip[ 3 ] = 113 ;
  sprintf( ( char* )tbuf, "Remote IP:%d.%d.%d.", lwipdev.remoteip[0], lwipdev.remoteip[1], lwipdev.remoteip[2] ) ;
  LCD_ShowString( 30, 150, tbuf ) ;                //远端IP
  myfree( SRAMIN, tbuf ) ;
}
// UDP接收回调函数
u8 udp_demo_recvbuf[ UDP_DEMO_RX_BUFSIZE ] ;            //UDP接收数据缓冲区
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 ) ;    //数据接收缓冲区清零
    //遍历完整个pbuf链表
    for( q=p; q!=NULL; q=q->next )
    {
      //拷贝数据
      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 ;
      //超出TCP客户端接收数组,跳出
      if( data_len>UDP_DEMO_RX_BUFSIZE )
        break ;  
    }
    upcb->remote_ip = *addr ;                  //记录远程主机的IP地址
    upcb->remote_port = port ;                //记录远程主机的端口号
    lwipdev.remoteip[ 0 ] = upcb->remote_ip.addr&0xFF ;      //IADDR4
    lwipdev.remoteip[ 1 ] = ( upcb->remote_ip.addr>>8 )&0xFF ;  //IADDR3
    lwipdev.remoteip[ 2 ] = ( upcb->remote_ip.addr>>16 )&0xFF ;  //IADDR2
    lwipdev.remoteip[ 3 ] = ( upcb->remote_ip.addr>>24 )&0xFF ;  //IADDR1 
    udp_demo_flag |= 1<<6 ;                  //标记接收到数据了
    pbuf_free( p ) ;                      //释放内存
  }
  else
  {
    udp_disconnect( upcb ) ;
    LCD_Clear( WHITE ) ;                    //清屏
    udp_demo_flag &= ~( 1<<5 ) ;                //标记连接断开
  }
}
// UDP服务器发送数据
const u8 *tcp_demo_sendbuf="STM32F103 UDP send data\\r\\n";
void udp_demo_senddata( struct udp_pcb *upcb )
{
  struct pbuf *ptr ;
  ptr = pbuf_alloc( PBUF_TRANSPORT, strlen( ( char* )tcp_demo_sendbuf ), PBUF_POOL ) ;//申请内存
  if( ptr )
  {
    ptr->payload = ( void* )tcp_demo_sendbuf ;
    udp_send( upcb, ptr ) ;                    //udp发送数据 
    pbuf_free( ptr ) ;                                        //释放内存
  }
}
//关闭UDP连接
void udp_demo_connection_close( struct udp_pcb *upcb )
{
  udp_disconnect( upcb ) ;
  udp_remove( upcb ) ;                      //断开UDP连接 
  udp_demo_flag &= ~( 1<<5 ) ;                  //标记连接断开
  LCD_Clear( WHITE ) ;                      //清屏
}
// UDP测试
void udp_demo_test()
{
   err_t err ;
  struct udp_pcb *udppcb ;                    //定义一个TCP服务器控制块
  struct ip_addr rmtipaddr ;                                      //远端ip地址
  u8 *tbuf ;
  u8 res=0 ;
  udp_demo_set_remoteip() ;                    //先选择IP
  LCD_Clear( WHITE ) ;                      //清屏
  tbuf = mymalloc( SRAMIN, 200 ) ;                //申请内存
  //内存申请失败了,直接退出
  if( tbuf==NULL )
    return ;
  sprintf( ( char* )tbuf, "Local IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  LCD_ShowString( 30, 150, tbuf ) ;                //服务器IP
  sprintf( ( char* )tbuf, "Remote IP:%d.%d.%d.%d", lwipdev.remoteip[0], lwipdev.remoteip[1], lwipdev.remoteip[2], lwipdev.remoteip[3] ) ;
  LCD_ShowString( 30, 170, tbuf ) ;                //远端IP
  sprintf( ( char* )tbuf, "Remote Port:%d", UDP_DEMO_PORT ) ;
  LCD_ShowString( 30, 190, tbuf ) ;                //客户端端口号
  LCD_ShowString( 30, 210, "STATUS:Disconnected" ) ;
  udppcb = udp_new() ;
  //创建成功
  if( udppcb )
  { 
    IP4_ADDR( &rmtipaddr, lwipdev.remoteip[0], lwipdev.remoteip[1], lwipdev.remoteip[2], lwipdev.remoteip[3] ) ;
    err = udp_connect( udppcb, &rmtipaddr, UDP_DEMO_PORT ) ;  //UDP客户端连接到指定IP地址和端口
    if( err==ERR_OK )
    {
      err = udp_bind( udppcb, IP_ADDR_ANY, UDP_DEMO_PORT ) ; //绑定本地IP地址与端口号
      //绑定完成
      if( err==ERR_OK )
      {
        udp_recv( udppcb, udp_demo_recv, NULL ) ;      //注册接收回调函数
        LCD_ShowString( 30, 210, "STATUS:Connected   " ) ;  //标记连接上了
        udp_demo_flag |= 1<<5 ;              //标记已经连接上
        LCD_ShowString( 30, 230, "Receive Data:" ) ;      //提示消息
      }
      else
        res = 1 ;
    }
    else
      res = 1 ;
  }
  else
    res = 1 ;
  while( res==0 )
  {
    //是否收到数据
    if( udp_demo_flag&1<<6 )
    {
      LCD_ShowString( 30, 250, udp_demo_recvbuf ) ;      //显示接收到的数据
      udp_demo_senddata( udppcb ) ;              //发送数据
      udp_demo_flag &= ~( 1<<6 ) ;              //标记数据已经被处理了
    } 
    lwip_periodic_handle() ;
    lwip_pkt_handle() ;
    delay_ms( 2 ) ;
  }
  udp_demo_connection_close( udppcb ) ;
  myfree( SRAMIN, tbuf ) ;
}

25.2.2 udp_demo.h代码编写

#ifndef _UDP_DEMO_H_
#define _UDP_DEMO_H_
#include "sys.h"
#define UDP_DEMO_RX_BUFSIZE  2000              //定义udp最大接收数据长度 
#define UDP_DEMO_PORT      8089              //定义udp连接的端口 
void udp_demo_test( void ) ;                    //UDP测试
#endif

25.2.3 主函数代码编写

#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "tim.h"
#include "lcd.h"
#include "malloc.h"
#include "dm9000.h"
#include "lwip/netif.h"
#include "comm.h"
#include "lwipopts.h"
#include "udp_demo.h"
int main()
{
  u8 buf[ 30 ];
   STM32_Clock_Init( 9 ) ;                        //系统时钟设置
  SysTick_Init( 72 ) ;                          //延时初始化
  USART1_Init( 72, 115200 ) ;                      //串口初始化为115200
  LCD_Init() ;                            //初始化LCD
  TIM3_Init( 1000, 719 ) ;                        //定时器3频率为100hz
  my_mem_init( SRAMIN ) ;                      //初始化内部内存池
  while( lwip_comm_init() ) ;                      //lwip初始化
  //等待DHCP获取成功/超时溢出
  while( ( lwipdev.dhcpstatus!=2 )&&( lwipdev.dhcpstatus!=0xFF ) )
  {
    lwip_periodic_handle() ;                    //LWIP内核需要定时处理的函数
    lwip_pkt_handle() ;
  }
  POINT_COLOR=RED;
  LCD_ShowString( 30, 110, "LWIP Init Successed" ) ;
  //打印动态IP地址
  if( lwipdev.dhcpstatus==2 )
    sprintf( ( char* )buf, "DHCP IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  //打印静态IP地址
  else
    sprintf( ( char* )buf, "Static IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  LCD_ShowString( 30, 130, buf ) ; 
  //得到网速
  if( ( DM9000_Get_SpeedAndDuplex()&0x02 )==0x02 )
    LCD_ShowString( 30, 150, "Ethernet Speed:10M" ) ;
  else
    LCD_ShowString( 30, 150, "Ethernet Speed:100M" ) ;
   while( 1 )
  {
    udp_demo_test();
    lwip_periodic_handle() ;
    lwip_pkt_handle() ;
    delay_ms( 2 ) ;
  }
}

25.3 实验结果

数据传输

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分