基于STM32的TCP/IP协议栈代码之UDP分析

发布者:平和思绪最新更新时间:2016-09-19 来源: eefocus关键字:STM32  TCP  IP  协议栈  UDP分析 手机看文章 扫描二维码
随时随地手机看文章
1. UDP介绍

UDP是一个简单的面向数据报的运输层协议:进程的每个输出操作都正好产生一个 UDP数据报,并组装成一份待发送的IP数据报。这与面向流字符的协议不同,如TCP,应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联系。
UDP数据报封装成一份 IP数据报的格式如图11 - 1所示。

41

RFC 768 [Postel 1980] 是UDP的正式规范。
UDP不提供可靠性:它把应用程序传给IP层的数据发送出去,但是并不保证它们能到达目的地。由于缺乏可靠性,我们似乎觉得要避免使用UDP而使用一种可靠协议如TCP。在讨论完TCP后将再回到这个话题,看看什么样的应用程序可以使用UDP。

2. UDP首部
UDP首部的各字段如图11 - 2所示。

42

端口号表示发送进程和接收进程。在图 1 - 8中,我们画出了TCP和UDP用目的端口号来分用来自IP层的数据的过程。

43

由于IP层已经把IP数据报分配给TCP或UDP(根据I P首部中协议字段值) ,因此TCP端口号由TCP来查看,而UDP端口号由UDP来查看。TCP端口号与UDP端口号是相互独立的。
尽管相互独立,如果TCP和UDP同时提供某种知名服务,两个协议通常选择相同的端口号。这纯粹是为了使用方便,而不是协议本身的要求。
UDP长度字段指的是UDP首部和UDP数据的字节长度。该字段的最小值为 8字节(发送一份0字节的UDP数据报是OK) 。这个UDP长度是有冗余的。 IP数据报长度指的是数据报全长(图3 - 1) ,因此UDP数据报长度是全长减去IP首部的长度(该值在首部长度字段中指定,如图3 - 1所示)

44

UDP检验和覆盖UDP首部和UDP数据。回想IP首部的检验和,它只覆盖IP的首部—并不覆盖IP数据报中的任何数据。
UDP和TCP在首部中都有覆盖它们首部和数据的检验和。UDP的检验和是可选的,而TCP的检验和是必需的。
尽管UDP检验和的基本计算方法与我们在描述的IP首部检验和计算方法相类似(16 bit字的二进制反码和,但是稍微有所不同,在根据字段类型判定为UDP或者TCP时加入了一些处理,看代码就晓得了) ,但是它们之间存在不同的地方。首先, UDP数据报的长度可以为奇数字节,但是检验和算法是把若干个 16 bit字相加。解决方法是必要时在最后增加填充字节0,这只是为了检验和的计算(也就是说,可能增加的填充字节不被传送) 。
其次,UDP数据报和TCP段都包含一个1 2字节长的伪首部(本TCP/IP协议栈有所不同,只加入了4字节源IP地址和4字节目的IP地址,即利用IP首部的尾巴,实现了空间上的复用,看代码就晓得了),它是为了计算检验和而设置的。伪首部包含IP首部一些字段。其目的是让 UDP两次检查数据是否已经正确到达目的地(例如,IP没有接受地址不是本主机的数据报,以及IP没有把应传给另一高层的数据报传给UDP) 。UDP数据报中的伪首部格式如图11 - 3所示。

45

在该图中,我们特地举了一个奇数长度的数据报例子,因而在计算检验和时需要加上填充字节(0)。注意,UDP数据报的长度在检验和计算过程中出现两次。
如果检验和的计算结果为 0,则存入的值为全1(65535) ,这在二进制反码计算中是等效的。如果传送的检验和为0,说明发送端没有计算检验和。(因为协议要求如此,故代码需要实现之。)如果发送端没有计算检验和而接收端检测到检验和有差错,那么 UDP数据报就要被悄悄地丢弃。不产生任何差错报文(当IP层检测到IP首部检验和有差错时也这样做)。
UDP检验和是一个端到端的检验和。它由发送端计算,然后由接收端验证。其目的是为了发现UDP首部和数据在发送端到接收端之间发生的任何改动。


/*下面阐述UDP校验和的一些历史和必要性*/
尽管UDP检验和是可选的,但是它们应该总是在用。在 80年代,一些计算机产商在默认条件下关闭UDP检验和的功能,以提高使用UDP协议的NFS(Network File System)的速度。
在单个局域网中这可能是可以接受的,但是在数据报通过路由器时,通过对链路层数据帧进行循环冗余检验(如以太网或令牌环数据帧)可以检测到大多数的差错,导致传输失败。不管相信与否,路由器中也存在软件和硬件差错,以致于修改数据报中的数据。如果关闭端到端的UDP检验和功能,那么这些差错在UDP数据报中就不能被检测出来。另外,一些数据链路层协议(如SLIP)没有任何形式的数据链路检验和。
Host Requirements RFC声明,UDP检验和选项在默认条件下是打开的。它还声明,如果发送端已经计算了检验和,那么接收端必须检验接收到的检验和(如接收到检验和不为0) 。但是,许多系统没有遵守这一点,只是在出口检验和选项被打开时才验证接收到的检验和。

另外需要解释几个术语: IP数据报是指IP层端到端的传输单元(在分片之前和重新组装之后) ,分组是指在IP层和链路层之间传送的数据单元。一个分组可以是一个完整的 IP数据报,也可以是IP数据报的一个分片。(这里有如何分片的说明,书里介绍的详细,简而言之,超过MTU就需要分,但是第一片和接下来的片是有区别的:第一个有UDP首部,其他没有,但是可以通过IP的flags来组合起来。下面的图很形象的说明了。)

46

 

------------------------------------------以下内容产生于代码及分析--------------------------------------
3. UDP宏定义实现

// ******* UDP *******
#define UDP_HEADER_LEN  8
//源端口位置
#define UDP_SRC_PORT_H_P 0x22
#define UDP_SRC_PORT_L_P 0x23
//目标端口位置
#define UDP_DST_PORT_H_P 0x24
#define UDP_DST_PORT_L_P 0x25
//UDP数据长度位置
#define UDP_LEN_H_P       0x26
#define UDP_LEN_L_P       0x27
//UDP校验和位置
#define UDP_CHECKSUM_H_P 0x28
#define UDP_CHECKSUM_L_P 0x29
//UDP数据起始地址
#define UDP_DATA_P 0x2a

 

 

4. UDP函数实现
本TCP/IP协议栈中的UDP实现只一个make_udp_reply_from_request函数——udp服务器,可以响应其他udp的请求。在连接的顺序看来,在stm32板子上面的为服务器,等待pc机客户端的请求,当请求到来的时候,返回由程序员自行设定的响应,如本文中将做出3个响应的例子(当然udp一旦建立之后,就部分客户端和服务器端,地位是对等的,但是认为发起者为clien比较符合认知而已)。
这里说以下输入吧:buf为缓冲区,data为要传输的数据,datalen即为sizeof(data),port即为pc端的udp端口号

void make_udp_reply_from_request(unsigned char *buf, char *data, unsigned int datalen, unsigned  int port)
{
    unsigned int i = 0, tol_len;
    unsigned  int ck;
    //如前面的ARP和ICMP一样的
    make_eth(buf);
    // total length field in the IP header must be set:
    //如IP Header
    tol_len = IP_HEADER_LEN + UDP_HEADER_LEN + datalen;
    buf[IP_TOTLEN_H_P] = tol_len >> 8;
    buf[IP_TOTLEN_L_P] = tol_len;
    //如ICMP
    make_ip(buf);
    //本地UDP的端口号
    buf[UDP_DST_PORT_H_P] = port >> 8;
    buf[UDP_DST_PORT_L_P] = port & 0xff;
    // source port does not matter and is what the sender used.
    // calculte the udp length:最大16bit长度,即65535-14-20-8,但一般会设置的较小,原因么,上文里面讲过。
    buf[UDP_LEN_H_P] = datalen >> 8;
    buf[UDP_LEN_L_P] = UDP_HEADER_LEN + datalen;
    // zero the checksum
    buf[UDP_CHECKSUM_H_P] = 0;
    buf[UDP_CHECKSUM_L_P] = 0;

    // copy the data:
    while(i < datalen)
    {
        buf[UDP_DATA_P + i] = data[i];
        i++;
    }

    //UDP_DEBUG插入此处
    //这里的16字节是UDP的伪首部,即IP的源地址-0x1a+目标地址-0x1e(和标准的有差异),
    //+UDP首部=4+4+8=16
    ck = checksum(&buf[IP_SRC_P], 16 + datalen, 1);
    buf[UDP_CHECKSUM_H_P] = ck >> 8;
    buf[UDP_CHECKSUM_L_P] = ck & 0xff;
    enc28j60PacketSend(UDP_HEADER_LEN + IP_HEADER_LEN + ETH_HEADER_LEN + datalen, buf);
}

5. UDP实验
在有了以上的UDP实现之后,你还需要有UDP的请求进来,如下代码所示:
下面的代码放在一个while(1)或者RTOS进程里面,作为服务器来等待客户端的响应

/*--------------------- udp server start, we listen on udp port 1200=0x4B0 -----------------------------*/
      if (buf[IP_PROTO_P]==IP_PROTO_UDP_V&&buf[UDP_DST_PORT_H_P]==4&&buf[UDP_DST_PORT_L_P]==0xb0)
      {
        //UDP数据长度
          udpdatalen=buf[UDP_LEN_H_P];
          udpdatalen=udpdatalen<<8;
          udpdatalen=(udpdatalen+buf[UDP_LEN_L_P])-UDP_HEADER_LEN;
          //udpdatalen=buf[UDP_LEN_L_P]-UDP_HEADER_LEN;
           //获取pc端的udp port
          pcudpport=buf[UDP_SRC_PORT_H_P]<<8 | buf[UDP_SRC_PORT_L_P];
        //将udp客户端得到的数据buf写入buf1,因为下面的实验需要输入的信息来做出相应的动作
          for(i1=0; i1                         buf1[i1]=buf[UDP_DATA_P+i1];
               
          make_udp_reply_from_request(buf,buf1,udpdatalen,pcudpport);         
      }
/*----------------------------------------udp end -----------------------------------------------*/

ps:本实验中板子udp的port为1200,pc机的port为4001
实验部分实现了三个简单的实验:
1.通过串口输出UDP客户端的IP地址及端口号
2.通过串口和UDP输出UDP的输入数据,即USART ECHO和UDP ECHO
3.实现UDP命令控制STM32板子上面的LED

void make_udp_reply_from_request(unsigned char *buf, char *data, unsigned int datalen, unsigned  int port)
{
    unsigned int i = 0, tol_len;
    unsigned  int ck;
    //如前面的ARP和ICMP一样的
    make_eth(buf);
    // total length field in the IP header must be set:
    //如IP Header
    tol_len = IP_HEADER_LEN + UDP_HEADER_LEN + datalen;
    buf[IP_TOTLEN_H_P] = tol_len >> 8;
    buf[IP_TOTLEN_L_P] = tol_len;
    //如ICMP
    make_ip(buf);
    //本地UDP的端口号
    buf[UDP_DST_PORT_H_P] = port >> 8;
    buf[UDP_DST_PORT_L_P] = port & 0xff;
    // source port does not matter and is what the sender used.
    // calculte the udp length:最大16bit长度,即65535-14-20-8,但一般会设置的较小,原因么,上文里面讲过。
    buf[UDP_LEN_H_P] = datalen >> 8;
    buf[UDP_LEN_L_P] = UDP_HEADER_LEN + datalen;
    // zero the checksum
    buf[UDP_CHECKSUM_H_P] = 0;
    buf[UDP_CHECKSUM_L_P] = 0;

    // copy the data:
    while(i < datalen)
    {
        buf[UDP_DATA_P + i] = data[i];
        i++;
    }

#ifdef UDP_DEBUG
    i = 0;
    printf("UDP Server Test. \r\n");
    printf("udp客户端的IP地址及端口号 : \r\n");

    while(i < sizeof(ipv4_addr))
    {
        //注意这里我们建立的是UDP Server,输出UDP Client的IP地址
        printf("%d", buf[IP_DST_P + i]);

        if(i != sizeof(ipv4_addr) - 1)
        {
            printf(".");
        }

        i++;
    }

    i = 0;
    //输出pc端的udp port
    printf(":%d \r\n", port);

    //串口打印UDP Client发过来的数据
    printf("udp客户端发送的数据 : \r\n");
    printf("%s \r\n", data);

    //实现UDP Server来响应UDP Client的控制LED命令
    //如:led1=on,led1=off
    if(strcmp(data, "led1=on") == 0)
    {
        GPIO_ResetBits(GPIOA, GPIO_Pin_8);
    }

    if(strcmp(data, "led1=off") == 0)
    {
        GPIO_SetBits(GPIOA, GPIO_Pin_8);
    }

    //如:led2=on,led2=off
    if(strcmp(data, "led2=on") == 0)
    {
        GPIO_ResetBits(GPIOD, GPIO_Pin_2);
    }

    if(strcmp(data, "led2=off") == 0)
    {
        GPIO_SetBits(GPIOD, GPIO_Pin_2);
    }

#endif
    //这里的16字节是UDP的伪首部,即IP的源地址-0x1a+目标地址-0x1e(和标准的有差异),
    //+UDP首部=4+4+8=16
    ck = checksum(&buf[IP_SRC_P], 16 + datalen, 1);
    buf[UDP_CHECKSUM_H_P] = ck >> 8;
    buf[UDP_CHECKSUM_L_P] = ck & 0xff;
    enc28j60PacketSend(UDP_HEADER_LEN + IP_HEADER_LEN + ETH_HEADER_LEN + datalen, buf);
}

TCP&UDP测试工具现象:echo实现

47


串口现象:符合预期
注:关闭打开UDP重连才可以看到随机分配的不同udp port。

48
WireShark现象:顺利抓到包~~~

49

开发板现象:
LED2亮了,初步打通了原子世界和数字世界了,但是体验很糟糕,O(∩_∩)O

关键字:STM32  TCP  IP  协议栈  UDP分析 引用地址:基于STM32的TCP/IP协议栈代码之UDP分析

上一篇:STM32 KEIL下的堆栈设置问题
下一篇:STM32学习笔记——外部中断的初步了解

推荐阅读最新更新时间:2024-03-16 15:10

如何在STM32串口通信程序中使用printf发送数据
在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。 下面就说一下使用printf需要做哪些配置。 有两种配置方法: 一、对工程属性进行配置,详细步骤如下 1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。 2、在main文件中重定义函数 如下: // 发送数据 int fputc(int ch, FILE *f) { USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等 while (!(US
[单片机]
如何在<font color='red'>STM32</font>串口通信程序中使用printf发送数据
浅析STM32的hex文件
前段时间需要用到BIN文件 随便把HEX文件也了解了一下 参考查阅了一些网上资料,如有雷同除了巧合还有借鉴参考。。。 正题:HEX文件格式如下 0x3a 0x0d 0x0a 分析下面具体例子 :020000040800F2 : 这个就是 0x3a ,也就是冒号 02 这个就是说明这行数据区的数据内容长度,也就是数据内容为两个字节在后面的 0800 那里 0000 这个是数据要送往的地址 要看具体数据类型 04 解释整行数据的数据类型,主要有这几种 '00' Data Record 数据 '01' End of File Record 文件结束标志 '02' Extended
[单片机]
浅析<font color='red'>STM32</font>的hex文件
12M晶振下,STM32串口波特率设置问题
使用外接12MHz的晶振,会造成很多的问题,如USART的波特率不正确,Systick走时不准等问题,在无论是在实际调试还是在软件模拟中都会发现这个情况,其实,这不能怪ST官方,我们必须肯定ST官方为方便用户开发所做的努力,下面我们就通过简单的三个步骤就可以让你随意的使用4—16MHz之内任何频点的晶振,我们以STM32F10x_StdPeriph_Lib_V3.4.0为例说明。 第一步,打开stm32f10x.h,将 #define HSE_VALUE ((uint32_t)8000000) 修改为: #define HSE_VALUE ((uint32_t)12000000) 第二步,打开system_stm32f10x.c,
[单片机]
STM32之timer3产生PWM
一、简介 本文介绍STM32系列如何使用timer3的第3通道(PB0)产生38K频率的PWM。 二、实验平台 库版本:STM32F10x_StdPeriph_Lib_V3.5.0 编译软件:MDK4.53 硬件平台:STM32开发板(主芯片stm32f103c8t6) 仿真器:JLINK 三、版权声明 四、实验前提 1、在进行本文步骤前,请先阅读以下博文: 暂无 2、在进行本文步骤前,请先实现以下博文: 暂无 五、基础知识 暂无 六、实验步骤 1、编写并添加PWM驱动 1)编写驱动GUA_Timer3_PWM.c(存放在“……HARDWARE”) //*********
[单片机]
<font color='red'>STM32</font>之timer3产生PWM
stm32的按键扫描[操作寄存器+库函数]
本例将实现stm32的按键扫描功能。 操作寄存器 stm32的I/O口作为输入使用时,是通过读取GPIOx - IDR 寄存器的内容来读取I/O口状态的。 IDR寄存器各位描述如下: 由于systick不能像库函数那样方便的产生中断,通过查询systick状态位后,再查询各管脚状态反而更为不方便,所以和库函数方法不一样,直接查询了管脚状态来检测按键。 代码中调用 PAout(x) 、 PAin(x)等函数 在sys.h文件中,参见:(sys.h 代码参照 stm32 直接操作寄存器开发环境配置 ) 直接操作寄存器代码: #include stm32f10x_lib.h #include system
[单片机]
<font color='red'>stm32</font>的按键扫描[操作寄存器+库函数]
关于STM32串口使用DMA的教程
1 前言 直接存储器访问(Direct Memory Access),简称DMA。DMA是CPU一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件,数据拷贝过程不需CPU干预,数据拷贝结束则通知CPU处理。 因此,大量数据拷贝时,使用DMA可以释放CPU资源。DMA数据拷贝过程,典型的有: 内存— 内存,内存间拷贝 外设— 内存,如uart、spi、i2c等总线接收数据过程 内存— 外设,如uart、spi、i2c等总线发送数据过程 2 串口有必要使用DMA吗 串口(uart)是一种低速的串行异步通信,适用于低速通信场景,通常使用的波特率小于或等于115200bps。 对于小于或者等于
[单片机]
关于<font color='red'>STM32</font>串口使用DMA的教程
STM32 初练总结(GPIO)
最近有项目需要用到STM32,开始系统的学习一下STM32这块芯片,整理了一些初学的心得,以备以后忘记了可以回来再看。 管脚资源确认 首先确定所用的STM32型号对应的GPIO管脚资源,一般都会把GPIO管脚分成好几组,比如GPIOA、GPIOB、GPIOC、GPIOD~GPIOG等,一般管脚数越多,分的组也越多,有些管脚是GPIO和其他功能复用的,一些GPIO管脚已经默认了特定的功能,如果要重新自定义映射管脚,需要打开AFIO功能开启重映射。 STM32 库导入和确认 1、STM官网上有STM开发相关的组件资源和库文件,这个网上有很多资源可以利用,先下载然后解压,并按照keil的流程建立一个STM32工程。
[单片机]
<font color='red'>STM32</font> 初练总结(GPIO)
IP网络电话中常用的语音压缩编码技术的性能分析
    摘要: 从语音编码技术中常用的三种编码方法入手,由浅入深地引出了IP网络电话中常用的几种语音压缩编码方法,并对之进行了性能分析和比较。     关键词: IP网络电话 语音压缩编码 线性预测(LP) 合成-分析法 随着互联网的迅速发展,最近几年出现了一种在互联网上提供电话服务的新业务——IP电话业务。一次IP电话呼叫的成本为本地市话费、IP网络使用费以及远端市话费,与传统长途电话昂贵的长途通信费相比,可以极大地降低用户的通信费用。正是由于这种极大的价格优势,使得它一出现便引起了世人充分的注意,同时互联网的蓬勃发展,有效地利用现有的网络资源,来缓解现有通信网的紧张局面,也是促成这项新技术迅速发展的另一原因
[应用]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved