摘要:
上次我们讲完了一个温度采集系统的底层感知网络该如何搭建,今天就让我们来讲讲底层感知网络与服务器之间数据传输如何实现。本文主要包括了TCP/IP协议的基础知识,以及如何编写协议代码,也会介绍如何进行连接测试。
Abstract:
Last time we talked about how to build a low-level aware network of a temperature acquisition system, let us talk about how the data transmission between the underlying network and the server is realized. This article mainly covers the basics of the TCP/IP protocol, how to write protocol code, and how to perform connection testing.
1.TCP/IP协议
TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。
1.1 TCP/IP协议的组成
TCP/IP协议在一定程度上参考了OSI的体系结构。OSI模型共有七层,从下到上分别是物理层、数据链路层、网络层、运输层、会话层、表示层和应用层。但是这显然是有些复杂的,所以在TCP/IP协议中,它们被简化为了四个层次。
(1)应用层、表示层、会话层三个层次提供的服务相差不是很大,所以在TCP/IP协议中,它们被合并为应用层一个层次。
(2)由于运输层和网络层在网络协议中的地位十分重要,所以在TCP/IP协议中它们被作为独立的两个层次。
(3)因为数据链路层和物理层的内容相差不多,所以在TCP/IP协议中它们被归并在网络接口层一个层次里。只有四层体系结构的TCP/IP协议,与有七层体系结构的OSI相比要简单了不少,也正是这样,TCP/IP协议在实际的应用中效率更高,成本更低。
分别介绍TCP/IP协议中的四个层次。
应用层:应用层是TCP/IP协议的第一层,是直接为应用进程提供服务的。
(1)对不同种类的应用程序它们会根据自己的需要来使用应用层的不同协议,邮件传输应用使用了SMTP协议、万维网应用使用了HTTP协议、远程登录服务应用使用了有TELNET协议。
(2)应用层还能加密、解密、格式化数据。
(3)应用层可以建立或解除与其他节点的联系,这样可以充分节省网络资源。
运输层:作为TCP/IP协议的第二层,运输层在整个TCP/IP协议中起到了中流砥柱的作用。且在运输层中,TCP和UDP也同样起到了中流砥柱的作用。
网络层:网络层在TCP/IP协议中的位于第三层。在TCP/IP协议中网络层可以进行网络连接的建立和终止以及IP地址的寻找等功能。
网络接口层:在TCP/IP协议中,网络接口层位于第四层。由于网络接口层兼并了物理层和数据链路层所以,网络接口层既是传输数据的物理媒介,也可以为网络层提供一条准确无误的线路。

1.2 特点
TCP/IP协议能够迅速发展起来并成为事实上的标准,是它恰好适应了世界范围内数据通信的需要。它有以下特点:
(1)协议标准是完全开放的,可以供用户免费使用,并且独立于特定的计算机 硬件与操作系统。
(2)独立于网络硬件系统,可以运行在广域网,更适合于互联网。
(3)网络地址统一分配,网络中每一设备和终端都具有一个唯一地址。
(4)高层协议标准化,可以提供多种多样可靠网络服务。
1.3 通信过程及相关协议
在网络通信的过程中,将发出数据的主机称为源主机,接收数据的主机称为目的主机。当源主机发出数据时,数据在源主机中从上层向下层传送。源主机中的应用进程先将数据交给应用层,应用层加上必要的控制信息就成了报文流,向下传给传输层。传输层将收到的数据单元加上本层的控制信息,形成报文段、数据报,再交给网际层。网际层加上本层的控制信息,形成IP数据报,传给网络接口层。网络接口层将网际层交下来的IP数据报组装成帧,并以比特流的形式传给网络硬件(即物理层),数据就离开源主机。
1.3.1 链路层
以太网协议规定,接入网络的设备都必须安装网络适配器,即网卡,数据包必须是从一块网卡传送到另一块网卡。而网卡地址就是数据包的发送地址和接收地址,有了MAC地址以后,以太网采用广播形式,把数据包发给该子网内所有主机,子网内每台主机在接收到这个包以后,都会读取首部里的目标MAC地址,然后和自己的MAC地址进行对比,如果相同就做下一步处理,如果不同,就丢弃这个包。
所以链路层的主要工作就是对电信号进行分组并形成具有特定意义的数据帧,然后以广播的形式通过物理介质发送给接收方。
1.3.2 网络层
1.3.2.1 IP协议
网络层引入了IP协议,制定了一套新地址,使得我们能够区分两台主机是否同属一个网络,这套地址就是网络地址,也就是所谓的IP地址。IP协议将这个32位的地址分为两部分,前面部分代表网络地址,后面部分表示该主机在局域网中的地址。如果两个IP地址在同一个子网内,则网络地址一定相同。为了判断IP地址中的网络地址,IP协议还引入了子网掩码,IP地址和子网掩码通过按位与运算后就可以得到网络地址。
1.3.2.2 ARP协议
即地址解析协议,是根据IP地址获取MAC地址的一个网络层协议。其工作原理如下:ARP首先会发起一个请求数据包,数据包的首部包含了目标主机的IP地址,然后这个数据包会在链路层进行再次包装,生成以太网数据包,最终由以太网广播给子网内的所有主机,每一台主机都会接收到这个数据包,并取出标头里的IP地址,然后和自己的IP地址进行比较,如果相同就返回自己的MAC地址,如果不同就丢弃该数据包。ARP接收返回消息,以此确定目标机的MAC地址;与此同时,ARP还会将返回的MAC地址与对应的IP地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
1.3.2.3 路由协议
首先通过IP协议来判断两台主机是否在同一个子网中,如果在同一个子网,就通过ARP协议查询对应的MAC地址,然后以广播的形式向该子网内的主机发送数据包;如果不在同一个子网,以太网会将该数据包转发给本子网的网关进行路由。网关是互联网上子网与子网之间的桥梁,所以网关会进行多次转发,最终将该数据包转发到目标IP所在的子网中,然后再通过ARP获取目标机MAC,最终也是通过广播形式将数据包发送给接收方。而完成这个路由协议的物理设备就是路由器,路由器扮演着交通枢纽的角色,它会根据信道情况,选择并设定路由,以最佳路径来转发数据包。
所以,网络层的主要工作是定义网络地址、区分网段、子网内MAC寻址、对于不同子网的数据包进行路由。
1.3.3 传输层
链路层定义了主机的身份,即MAC地址,而网络层定义了IP地址,明确了主机所在的网段,有了这两个地址,数据包就从可以从一个主机发送到另一台主机。但实际上数据包是从一个主机的某个应用程序发出,然后由对方主机的应用程序接收。而每台电脑都有可能同时运行着很多个应用程序,所以当数据包被发送到主机上以后,是无法确定哪个应用程序要接收这个包。因此传输层引入了UDP协议来解决这个问题,为了给每个应用程序标识身份。
1.3.3.1 UDP协议
UDP协议定义了端口,同一个主机上的每个应用程序都需要指定唯一的端口号,并且规定网络中传输的数据包必须加上端口信息,当数据包到达主机以后,就可以根据端口号找到对应的应用程序了。UDP协议比较简单,实现容易,但它没有确认机制,数据包一旦发出,无法知道对方是否收到,因此可靠性较差,为了解决这个问题,提高网络可靠性,TCP协议就诞生了。
1.3.3.2 TCP协议
TCP即传输控制协议,是一种面向连接的、可靠的、基于字节流的通信协议。简单来说TCP就是有确认机制的UDP协议,每发出一个数据包都要求确认,如果有一个数据包丢失,就收不到确认,发送方就必须重发这个数据包。为了保证传输的可靠性,TCP协议在UDP基础之上建立了三次对话的确认机制,即在正式收发数据前,必须和对方建立可靠的连接。TCP数据包和UDP一样,都是由首部和数据两部分组成,唯一不同的是,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。
传输层的主要工作是定义端口,标识应用程序身份,实现端口到端口的通信,TCP协议可以保证数据传输的可靠性。
1.3.4 应用层
理论上讲,有了以上三层协议的支持,数据已经可以从一个主机上的应用程序传输到另一台主机的应用程序了,但此时传过来的数据是字节流,不能很好的被程序识别,操作性差,因此,应用层定义了各种各样的协议来规范数据格式,常见的有http,ftp,smtp等,在请求Header中,分别定义了请求数据格式Accept和响应数据格式Content-Type,有了这个规范以后,当对方接收到请求以后就知道该用什么格式来解析,然后对请求进行处理,最后按照请求方要求的格式将数据返回,请求端接收到响应后,就按照规定的格式进行解读。
所以应用层的主要工作就是定义数据格式并按照对应的格式解读数据。
2.协议代码的编写
首先,我们要编写协议代码,就必须了解通信协议文档,这个文档包括了本次我们这个系统的协议语句各个组成部分解析。
2.1 通信协议数据结构
所有的通讯包都是由ASCII 码(汉字除外,采用UTF-8 码,8 位,1字节)字符组成。
通讯协议数据结构如下图所示

通讯包结构组成表如下表所示

2.2 数据包详解
2.2.1 包头:
包头都是固定以两个ASCII码“&&”为一个通讯包的起始,后接“数据段长度”段,无分隔符
2.2.2 数据段长度:
包含通讯包中数据段所有有效字节的长度,无分隔符,后面直接接数据段。
例:传输包中数据段字节长度为120个字节,0120
2.2.3 数据段:
2.2.3.1 上传包数据段:
上传数据段由系统编码,设备唯一标识,指令参数三部分组成

指令参数格式为“编码=XX”根据不同类型有不同格式,如表4所示,每一种类型结尾以“;”结束,其中温度与PH值需有小数点后一位,且PH值范围在014之间,其他参数范围在099之间。
例:a01001=15.6;a01002=5;w01001=7.5;a21005=15;a05001=50;a21003=40;

2.2.3.2 下传包数据段:
下传数据段由系统编码,系统唯一标识,命令编码,命令响应序列号四部分组成。
其中系统编码与系统唯一标识部分与上传包数据类型相同。


2.2.4 响应回复:
协议中规定响应包需要加上收到下发命令的序列号,这个字段用来对映响应包对映的收到的下发包序列编号,其序列号字段位于校验码字段前。协议中用 ASCII码“ACK=”表示。以ASCII“;”结束。
例1:收到服务器端某时刻需要设备型号为01,标识为0123456789的启动运行命令,命令序列为8时,收到
&&0034ST=01;MN=0123456789;CN=9101;CNN=8;CRC=DEC1
响应回复为:
&&0034ST=01;MN=0123456789;CN=9101;ACK=8;CRC=DBC1
例2:收到服务器端某时刻需要设备型号为01,标识为0123456789的获取当前所有信息,命令序列为9的命令时,收到
&&0034ST=01;MN=0123456789;CN=9102;CNN=9;CRC=8D01
响应回复为:
&&0089ST=01;MN=0123456789;a01001=15.6;a01002=52;w01001=7.5;a21005=15;a05001=50;a21003=40;ACK=9;CRC=AE41
2.2.5 校验
校验只针对数据段进行,得到的CRC结果本协议认定为校验段,CRC校验部分识别符在协议中用 ASCII 码"CRC="表示(该识别符不属于校验段而认定在数据段中)。校验结果是ASCII表示的四位十六进制数。
例:完整协议包&&0097ST=01;MN=0123456789ABCD9876543210;a01001=15.6;a01002=50;w01001=7.0;a21005=01;a05001=15;a21003=20;CRC=D701
黄色部分为需要校检字段,绿色部分为校检值
校检方法见附录A。
2.2.6 包尾
固定为(回车,换行)
以上就是我们编写代码所需要的协议文件以及基础知识储备。由于我们的底层感知网络是用STM32F103C8T6芯片作为主控芯片,所以协议代码也将应用C语言进行编写。
(话不多说,上代码)
#include "stm32f10x.h"
#include "usart2.h"
#include "usart3.h"
#include "myiic.h"
#include "delay.h"
#include "tmp275.h"
#include "wifi.h"
#include "oled.h"
int num;
int number;
float i;
char crc[20];
char str[200];
char end[200];
char data[200];
char crc_reg[20];
char Temp_Result[20];
int main(void)
{
TMP275_Init();
delay_init();
Usart2_Init(9600);
Usart3_Init(115200);
WiFi_ResetIO_Init();
OLED_Init();
OLED_Clear();
while(1)
{
OLED_ShowString(0,5,(u8*)"Connecting");
if(Connect_flag==1)
{
TMP275_Read_Registers(0X00);
i = TMP275_Get_Temp();//读温度
sprintf(Temp_Result,"%.1f",i);//温度转字符串
memset(data,0,sizeof(data));//清空
sprintf(data,"%s%s%s%s;",ST,MN,Temp_Head,Temp_Result);//数据段组合
//sprintf(data,"%s%s%s%s%s;",ST,MN,Temp_Head,Temp_Result,ph_Head);//数据段组合
number=strlen(data);//计算数据段长度
if(number>=1&&number<10)
{
sprintf(str,"000%d",number);//补齐4位数据段
}
else if(number>=10&&number<100)
{
sprintf(str,"00%d",number);
}
else if(number>=100&&number<1000)
{
sprintf(str,"0%d",number);
}
else if(number>=1000&&number<10000)
{
sprintf(str,"%d",number);
}
else
{
u2_printf("数据段错误");
}
memset(crc_reg,0,sizeof(crc_reg));//清空
sprintf(crc_reg,"%X",CRC16_Checkout(data,number));//CRC校检返回值
num=strlen(crc_reg);
if (num==0)
{
sprintf(crc,"0000");//补齐4位校检值
}
else if (num==1)
{
sprintf(crc,"000%s",crc_reg);
}
else if (num==2)
{
sprintf(crc,"00%s",crc_reg);
}
else if (num==3)
{
sprintf(crc,"0%s",crc_reg);
}
else if (num==4)
{
sprintf(crc,"%s",crc_reg);
}
else
{
u2_printf("CRC校检异常");
}
memset(end,0,sizeof(end));//清空
sprintf(end,"%s%s%s%s%s",Head,str,data,CRC_Client,crc);//最终协议组合
WiFi_printf("%s\r\n",end);//发送
OLED_Clear();
OLED_ShowString(0,2,(u8*)"The temp-sensor is working");
delay_ms(10000);
}
else
{
u2_printf("需要连接服务器\r\n");
TIM_Cmd(TIM4,DISABLE);//关闭TIM4
WiFi_RxCounter=0;//清零
memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE);
if(WiFi_Connect_IoTServer()==0)
{
u2_printf("\r\n建立TCP连接成功\r\n");
Connect_flag = 1;
}
}
}
}
此处代码编写我们运用到了CRC算法,以下给大家详细讲讲
循环冗余校验(CRC)算法
CRC校验(Cyclic Redundancy Check)是一种数据传输错误检查方法。本标准采用 ANSI CRC16,简称CRC16。
CRC16码由传输设备计算后加入到数据包中。接收设备重新计算接收数据包的CRC16码,并与接收到的CRC16码比较,如果两值不同,则有误。
CRC16校验字节的生成步骤如下:
1)CRC16校验寄存器赋值为0xFFFF;
2)取被校验串的第一个字节赋值给临时寄存器;
3)临时寄存器与 CRC16 校验寄存器的高位字节进行“异或”运算,赋值给 CRC16 校验寄存器;
4)取CRC16校验寄存器最后一位赋值给检测寄存器;
5)把CRC16校验寄存器右移一位;
6)若检测寄存器值为1,CRC16校验寄存器与多项式0xA001进行“异或”运算,赋值给CRC16校验寄存器;
7)重复步骤4~6,直至移出8位;
8)取被校验串的下一个字节赋值给临时寄存器;
9)重复步骤 3~8,直至被校验串的所有字节均被校验;
10)返回 CRC16 校验寄存器的值。
校验码按照先高字节后低字节的顺序存放。
CRC校验算法示例:
/**************************************************************************
函 数: CRC16_Checkout
描 述: CRC16 循环冗余校验算法
参 数 一: *puchMsg:需要校验的字符串指针
参 数 二: usDataLen:要校验的字符串长度
返 回 值: 返回 CRC16 校验码
**************************************************************************/
unsigned int CRC16_Checkout (unsigned char *puchMsg, unsigned int usDataLen)
{
unsigned int i,j,crc_reg,check;
crc_reg=0xFFFF;
for(i=0;i<97;i++)
{
crc_reg=(crc_reg>>8)^puchMsg[i];
for(j=0;j<8;j++)
{
check=crc_reg&0x0001;
crc_reg>>=1;
if(check==0x0001)
{
crc_reg^=0xA001;
}
}
}
return crc_reg;
}
示例: &&0101QN=20160801085857223;ST=32;CN=1062;PW=100000;MN=010000A8900016F000169DC0;Flag=5;CP=&&RtdInterval=30&&1C80\r\n,
其中1C80为CRC16校验码,是对数据段QN=20160801085857223;ST=32;CN=1062;PW=100000;MN=010000A8900016F000169DC0;Flag=5;CP=&&RtdInterval=30&&
进行CRC16校验所得的校验码。
3. 连接测试
在编写通信代码的时候,建议加入一个串口以便调试使用。调试的时候如果没有服务器,可以直接在cmd输入ipconfig/all获取本机的IP地址,并使用SocketTool进行数据的监听。请注意将所有设备连接在同一局域网下。
(以下为建立数据连接的代码)
#ifndef _WIFI_H
#define _WIFI_H
#include "stm32f10x.h"
void WiFi_ResetIO_Init(void);
#define WiFi_printf u3_printf //串口3 发送WiFi
#define WiFi_RxCounter Usart3_RxCounter //串口3 控制WiFi
#define WiFi_RX_BUF Usart3_RxBuff //串口3 接收数据
#define WiFi_RXBUFF_SIZE USART3_RXBUFF_SIZE //串口3 接收数据个数
#define SSID "Ed-Bric" //路由器SSID名称
#define PASS "20784259" //路由器密码
#define ServerIP "112.118.134.2" //服务器IP
#define ServerPort 1883 //服务器端口号
#define Head "&&" //报头
#define ST "ST=01;" //系统编码
#define MN "MN=swust0002;" //设备唯一标识
#define Temp_Head "a01001=" //温度
//#define ph_Head ";a01002=7.0" //ph值
#define CRC_Client "CRC=" //CRC校检头
extern char Connect_flag; //连接标志
#define RESET_IO(x) GPIO_WriteBit(GPIOB, GPIO_Pin_1|GPIO_Pin_0, (BitAction)x) //PB1,PB2控制WiFi的复位
char WiFi_Reset(int timeout);
char WiFi_SendCmd(char *cmd, int timeout);
char WiFi_JoinAP(int timeout);
char WiFi_Connect_Server(int timeout);
char WiFi_Connect_IoTServer(void);
void WiFi_ResetIO_Init(void);
unsigned int CRC16_Checkout (char *puchMsg, int usDataLen);
#endif

(此窗口便是本机监听窗口)
本文详细介绍了物联网温度采集系统底层感知网络与服务器之间的数据传输实现,包括TCP/IP协议的基础知识、协议代码编写及连接测试。重点讨论了TCP/IP协议的四个层次、CRC校验算法以及数据包的结构和校验过程。通过这段内容,读者可以了解到如何实现物联网设备与服务器间的可靠通信。
819

被折叠的 条评论
为什么被折叠?



