前言:之前做过的网络socket上报温度是基于TCP。和UDP.HTTP一样都是通信协议,也就是通信时所遵守的规则,只有双方按照这个规则“说话”,对方才能理解或为之服务。这里说的协议,都是基于应用层的协议,一切应用层协议说白了,就是数据包的结构定义。至于TLV格式,就是Type-Length-Value ,数据包里放数据,要么用定长,要么用TLV这种可变长度的。同时TLV是一种通用的socket网络协议数据格式。具有扩展兼容性好,解析快,结构性好,数据包小的特点。
一:TCP HTTP UDP三者的关系:
TCP/IP是个协议组,可分为四个层次:网络接口层、网络层、传输层和应用层。
在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。
在传输层中有TCP协议与UDP协议。
在应用层有FTP、HTTP、TELNET、SMTP、DNS等协议。
因此,HTTP本身就是一个协议,是从Web服务器传输超文本到本地浏览器的传送协议。
二:socket:
这是为了实现以上的通信过程而建立成来的通信管道,其真实的代表是客户端和服务器端的一个通信进程,双方进程通过socket进行通信,而通信的规则采用指定的协议。socket只是一种连接模式,不是协议,tcp,udp,简单的说(虽然不准确)是两个最基本的协议,很多其它协议都是基于这两个协议如,http就是基于tcp的,.用socket可以创建tcp连接,也可以创建udp连接,这意味着,用socket可以创建任何协议的连接,因为其它协议都是基于此的。
三:数据包TLV的设计
从应用层HTTP协议,到超文本置标语言HTML(HyperText Mark-up Language),再到可扩展置标语言XML(Extensible Markup Language),它们提供了数据的格式化存储、传输和格式化显示的规范,是网络通信的基石。然而HTTP协议以及HTML/XML置标语言的本质就是定义一堆标签(Tag)对数据进行串行化序列化,然后接收方再根据标签解析、还原数据。
自定义通信协议的关键是对数据包的合理构造(construct)和正确解析(parse),即制定编解码规则。
抽象语法标记 ASN(Abstract Syntax Notation) BER的长度确定的编码方式,由3部分组成Identifier octets、Length octets和Contents octets,实际上这就是一种TLV(Type-Length-Value)模型:类型字段(Type或Tag)是关于标签和编码格式的信息;长度字段(Length)定义数值的长度; 内容字段(Value)表示实际的数值。
因此,一个编码值又称TLV三元组。编码可以是基本型或结构型,如果它表示一个简单类型的、完整的显式值,那么编码就是基本型(primitive);如果它表示的值具有嵌套结构,那么编码就是结构型 (constructed)。
TLV编码就是指对Type(Tag)、Length和Value进行编码,形成比特流;解码是编码的逆过程,是从比特流缓冲区中解析还原出原始数据。
参考博客:https://blog.csdn.net/phunxm/article/details/5086080
客户端数据装包思路:
当然中间的T.L.V可以自己随意定义,这个根据自己的实际需求来。
一张图大家应该能看明白:
ID:LSJ 3bytes
时间:原本应该为2019-04-18 21:52:37 2019=119(+1900)
数据包: 119 4 18 21 52 37 6bytes
温度:原本应该为19.56℃
数据包:19 56 2bytes
装包过程明白了原理就很好写
装包代码:
#define HEAD 0xFD
#define ID 0X01
#define INT_TIME 0X02
#define FLOAT_TEMP 0X03
int makepack(char *buf,int size)
{
int id_len=0;
int data_len=0;
int time_len=0;
int temp_len=0;
int buf_place=0;
int i;
char *id="lsj";
char datatime[32];
float temp;
unsigned short crc16=0;
if(!buf || size<18)
{
printf("Invalid input arguements\n");
return 0;
}
buf[buf_place]=HEAD;
buf_place+=1;
data_len=11;
buf[buf_place]=data_len;
buf_place+=1;
buf[buf_place]=ID;
buf_place+=1;
id_len=strlen(id);
memcpy(&buf[buf_place],id,id_len);
buf_place+=id_len;
buf[buf_place]=INT_TIME;
buf_place+=1;
memset(datatime,0,sizeof(datatime));
int time=getdatatime(datatime);
time_len=time;
printf("time_len is %d\n",time_len);
for(int i=0;i
CRC校验代码直接网上找的,直接套用。没有深入去了解它。大家有兴趣可以去了解。
四:数据包解包
数据包解包的流程就是装包的逆过程,流程图如下:
第一次使用yed画流程图,有点糙,哈哈哈
源代码如下:
这里使用了大量的memmove函数,大家可以去了解一下这个函数
#include "tlv.server.h"
#include "tlv.crc.h"
#define HEAD 0xFD
#define ID 0X01
#define INT_TIME 0X02
#define FLOAT_TEMP 0X03
static sqlite3 *db;
int dividepack(char *buf,int size)
{
int i;
unsigned short crc16=0;
char user[32];
int year;
int month;
int day;
int hour;
int minute;
int second;
int temp_1;
int temp_2;
int rc;
char *db_name="TEMP.db";
char *error=0;
char *insert=(char*)malloc(256);
char *select="select * from TEMP;";
char *sql="CREATE TABLE IF NOT EXISTS TEMP(""ID INT PRIMARY KEY,"
"USER CHAR(10),"
"TIME CHAR(50),"
"TEMP CHAR(10));";
memset(user,0,sizeof(user));
if(!buf||size<=6)
{
printf("Invalid input arguements\n");
return 0;
}
flag1:if(size>6)
{
flag2:for(i=0;i5)
{
if(buf[i+1]==11)
{
if(buf[i+2]==ID)
{
memcpy(user,&buf[i+3],3);
printf("user is %s\n",user);
if(buf[i+6]==INT_TIME)
{
year=buf[i+7];
year+=1900;
printf("year is %d\n",year);
month=buf[i+8];
printf("month is %d\n",month);
day=buf[i+9];
printf("day is %d\n",day);
hour=buf[i+10];
printf("hour is %d\n",hour);
minute=buf[i+11];
printf("minute is %d\n",minute);
second=buf[i+12];
printf("second is %d\n",second);
if(buf[i+13]==FLOAT_TEMP)
{
temp_1=buf[i+14];
printf("temp_1 is %d\n",temp_1);
temp_2=buf[i+15];
printf("temp_2 is %d\n",temp_2);
crc16=crc_itu_t(MAGIC_CRC,buf,18);
//ushort_to_bytes(&buf[i+16],crc16);
if(crc16==0)
{
printf("crc true\n");
printf("divide pack finish\n");
memmove(buf,&buf[i],size);size=0;
}
else
{
memmove(buf,&buf[i+16],size-i-16);size=size-i-16;goto flag1;
}
}
else
{
memmove(buf,&buf[i+13],size-i-13);size=size-i-13;goto flag1;
}
}
else
{
memmove(buf,&buf[i+6],size-i-6);size=size-i-6;goto flag1;
}
}
else
{
memmove(buf,&buf[i+2],size-i-2);size=size-i-2;goto flag1;
}
}
else
{
memmove(buf,&buf[i+1],size-i-1);size=size-i-1;goto flag1;
}
}
else
{
memmove(buf,&buf[i],size-i);return size-i;goto flag1;
}
}
else
{
goto flag2;
}
}
}
else
{
return size;
}
下面部分是数据库的内容
rc=sqlite3_open(db_name,&db);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"sql error:%s\n",sqlite3_errmsg(db));
}
else
{
printf("open sqlite successfully!\n");
}
rc=sqlite3_exec(db,sql,NULL,NULL,&error);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"sql error:%s\n",error);
sqlite3_close(db);
sqlite3_free(error);
exit(1);
}
else
{
printf("create table successfully!\n");
}
snprintf(insert,256,"insert into TEMP(USER,TIME,TEMP)values('%s','%04d-%02d-%02d %02d:%02d:%02d','%d.%d');",user,year,month,day,hour,minute,second,temp_1,temp_2);
rc=sqlite3_exec(db,insert,NULL,NULL,&error);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"sql error:%s\n",error);
sqlite3_close(db);
sqlite3_free(error);
exit(1);
}
else
{
printf("insert successfully!\n");
}
rc=sqlite3_exec(db,select,NULL,NULL,&error);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"sql error:%s\n",error);
sqlite3_close(db);
sqlite3_free(error);
exit(1);
}
else
{
printf("select successfully!\n");
}
sqlite3_close(db);
}
后序:这篇博客重点在于理解tlv是干嘛的,有什么作用,以及如何运用它。还有要熟练掌握网络socket编程。
项目源码已上传码云:https://gitee.com/lsj123456/tlv.git