我是靠谱客的博主 雪白舞蹈,这篇文章主要介绍TLV协议(完善+封包+解析),现在分享给大家,希望可以做个参考。

通信协议概念

通讯协议就是指通信双方对数据传送控制的一种约定。约定中包括对数据格式,同步方式,传送速度,传送步骤,纠错方式以及控制字符定义等问题做出统一规定,通信双方必须共同遵守。
在计算机通信中,通信协议用于实现计算机与网络连接之间的标准,网络如果没有统一的通信协议,电脑之间的信息传递就无法识别。 通信协议是指通信各方事前约定的通信规则,可以简单地理解为各计算机之间进行相互会话所使用的共同语言。两台计算机在进行通信时,必须使用的通信协议。
更通俗来讲,它可以理解两个节点之间为了协同工作实现信息交换,协商一定的规则和约定,例如规定字节序,各个字段类型等。我们最常见到的可能是**TCP(传输控制协议)/IP(网际协议)、UDP(用户数据报协议)**等。

上面提到的这些协议是操作系统已经设定好了的,并且广泛应用在网络通信中。我们不能更改这些协议。用户自定义的通讯协议就不同了,它的实现需要用户自己设定数据发送的格式以及数据的封装形式,然后通过上面的网络传输协议发送给对端,对端再根据自己定义好的协议对数据进行解析,从而得到想要的数据。TLV协议便是其中的一种。

协议有流程规范编码规范。流程如呼叫流程等信令流程,编码规范规定所有信令和数据如何打包/解包。

编码规范就是我们通常所说的编解码,序列化。不光是用在通信工作上,在存储工作上我们也经常用到。如我们经常想把内存中对象存放到磁盘上,就需要对对象进行数据序列化工作。

TLV协议

ASN.1是一种ISO/ITU-T 标准。其中一种编码BER(Basic Encoding Rules)简单好用,它使用三元组编码,简称TLV编码。
它是由数据的类型Tag(T),数据的长度Length(L),数据的值Value(V)构成的一组数据报文。TLV是基于二进制编码的,将数据以(T -L- V)的形式编码为字节数组,即TLV是字节流的数据传输协议。它规定了一帧数据的首个字节或几个字节来表示数据类型,紧接着一个或几个字节表示数据长度,最后是数据的内容。

每个字段编码后内存组织如下:
在这里插入图片描述
字段可以是结构,即可以嵌套
在这里插入图片描述
以下将分别针对Tag、Length、Value进行解说:

Tag

描述Value的数据类型,TLV嵌套时可以用于描述消息的类型
在这里插入图片描述
Tag由一个或多个字节组成,上图描述首字节0~7位的具体含义

① Tag首节字说明

第6~7位:表示TLV的类型,00表示TLV描述的是基本数据类型(Primitive Frame, int,string,long…),01表示用户自定义类型(Private Frame,常用于描述协议中的消息)。
第5位:表示Value的编码方式,分别支持Primitive及Constructed两种编码方式, Primitive指以原始数据类型进行编码,Constructed指以TLV方式进行编码,0表示以Primitive方式编码,1表示以Constructed方式编码。
第0-4位:当Tag Value小于0x1F(31)时,首字节0~4位用来描述Tag Value,否则0~4位全部置1,作为存在后续字节的标志,Tag Value将采用后续字节进行描述。
在这里插入图片描述
② Tag后续字节说明

后续字节采用每个字节的0~6位(即7bit)来存储Tag Value, 第7位用来标识是否还有后续字节。

第7位:描述是否还有后续字节,1表示有后续字节,0表示没有后续字节,即结束字节。
第0~6位:填充Tag Value的对应bit(从低位到高位开始填充),如:Tag Value为:0000001 11111111 11111111 (10进制:131071), 填充后实际字节内容为:10000111 11111111 01111111。
在这里插入图片描述

Length

描述Value的长度

描述Value部分所占字节的个数,编码格式分两类:定长方式(DefiniteForm)和不定长方式(IndefiniteForm)。

定长方式中,按长度是否超过一个八位,又分为短、长两种形式,编码方式如下:

短形式: 字节第7位为0,表示Length使用1个字节即可满足Value类型长度的描述,范围在0~127之间的。
在这里插入图片描述
长形式:即Value类型的长度大于127时,Length需要多个字节来描述,这时第一个字节的第7位置为1,0~6位用来描述Length值占用的字节数,然后直将Length值转为byte后附在其后,如: Value大小占234个字节(11101010),由于大于127,这时Length需要使用两个字节来描述,10000001 11101010
在这里插入图片描述
不定长方式
Length所在八位组固定编码为0x80,但在Value编码结束后以两个0x00结尾。这种方式使得可以在编码没有完全结束的情况下,可以先发送部分数据给对方。
在这里插入图片描述

Value

描述数据的值

由一个或多个值组成 ,值可以是一个原始数据类型(Primitive Data),也可以是一个TLV结构(Constructed Data)

① Primitive Data 编码
在这里插入图片描述
② Constructed Data 编码
在这里插入图片描述

TLV协议的完善

假如数据报文是如下这样简单的TLV:
在这里插入图片描述
当接收数据时发生一个温度的TLV错误 ,变成如下所示:
在这里插入图片描述
这里一个TLV错误,导致数据读取时找到最近的0x03作为温度的Tag, 0x02表示Length, 0x0c 16 就是value值, 那后面岂不是乱套了, 若定义了0x04标识,那在从该位置进行解析,接收的数据就GG了.
所以加上标识和和CRC校验位, 标识一般采用0xfd,不容易引起错误, 最好不要用0x01 02 等作为头;
CRC校验时需要将整个HTLV都校验,如果检验错误直接丢弃该HTLV数据,再寻找下一个HTLV即可.
在这里插入图片描述

完整的TLV的协议:
在这里插入图片描述

报文头:用来标志一个报文的开始;
CRC16:占2个字节,从报文头开始到数据结尾(Value)所有数据的CRC校验和;

TLV封包

具体可见:https://blog.csdn.net/Shallwen_Deng/article/details/88930288?ops_request_misc=&request_id=&biz_id=102&utm_term=tlv%E5%B0%81%E5%8C%85&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-88930288

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<stdio.h> #include<string.h> #include<unistd.h> #include"crc-itu-t.c" #include<stdlib.h> #define TLV_MAX_SIZE 128 #define TLV_MIN_SIZE 6 #define Tag_temper 0x03 //可以使用枚举 #define HEADER 0xfd int tlv_pack(char *buf, int size, int cmd) { unsigned short crc16; int pack_len = 0x02 /* Only 2 byte value */ if(!buf || size<TLV_MIN_SIZE ) { printf("Invalid input argumentsn"); return 0; } /* Packet head */ buf[0] = HEADER; /* Tag */ buf[1] = Tag_temper; /* Length, this packet total 6 bytes */ buf[2] = pack_len; /* Value */ buf[3] = 0x0e; //如果value多的话,可以用for循环 buf[3] = 0x03; //CRC检验算法网上也比较多,可以直接用就行 crc16 = crc_itu_t(MAGIC_CRC, buf, 4); //HTLV计算出一个unsigned short类型crc的值 ushort_to_bytes(&buf[4], crc16);//将16位的crc值(1个字节8个位)转换成两个字节加到HTLV最后变成HTLV CRC的tlv报文 return pack_len; }

TLV解包

下面是TLV解包的代码有些复杂, 不要过多依赖对端,考虑了粘包的可能性.
加入了ACK/NAK机制:
ACK表示确认收到报文,可以进行下一次传输;
NAK表示报文出错,请求再次重传,一般不超过三次.

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include<stdio.h> #include<string.h> #include<unistd.h> #include "tlv_unpack.h" #include "crc-itu-t.h" #include "hex_str_to_int.h" int tlv_unpack(char *r_buf, int size, int cli_fd, char *tlv) { int i; int len; unsigned short val; unsigned short crc16; int data; start_loop: int ofset = 0; //首先判断读取的全部帧当中,是否存在有效数据, 否则传输NAK报文请求重传 if (size < MIN_PACK_SIZE) { printf("Pack too short and get data stream failure: %sn", strerror(errno)); //写NAK报文给客户端 MAGIC_CRC选定的CRC校验的除数 crc16 = crc_itu_t(MAGIC_CRC, (unsigned char *)nak_buf, 4); //获得unsigned short类型校验位, 第一个为除数, 第二个为数组指针, 第三个为校验长度 ushort_to_bytes(&nak_buf[4], crc16); //将CRC的2字节的校验值,转化成两个一字节存在unsigned char 当中 write(cli_fd, nak_buf, strlen(MIN_PACK_SIZE) ); return -1; } //在一个buf中一帧一帧解析TLV,进行读取数据 for (i=0; i<size; i++) { //读到报文头 if (r_buf[i] == HEARDER) { //只有帧头和标志位,没有value值 (读到buf的最后了) if (size-i < 2) { printf("remain data too short and read ok.n") printf("Wait continue input data.n") //这里需要注意, 若将字符串后面的''也读取到了,那么会将目的buf的后面数据全部清除 memmvoe(r_buf, &r_buf[i], strlen(size-i) ); //数据拷贝到buf首部,等待下一次传输数据,再读取 crc16 = crc_itu_t(MAGIC_CRC, (unsigned char *)nak_buf, 4); //获得unsigned short类型校验位 ushort_to_bytes(&ack_buf[4], crc16); //将CRC的2字节的校验值,转化成两个一字节存在unsigned char 当中 write(cli_fd, ack_buf, strlen(MIN_PACK_SIZE) ); return size-i; //剩余字节数 } ofset += i+2; len = r_buf[ofset]; //帧(length)长度错误,直接丢弃该报文段,并发送NAK报文,请求重新发数据 if ( len < MIN_PACK_SIZE || len > MAX_PACK_SIZE) { memmove(r_buf, &r_buf[ofset], strlen(size-i-len)); crc16 = crc_itu_t(MAGIC_CRC, (unsigned char *)nak_buf, 4); //获得unsigned short类型校验位 ushort_to_bytes(&nak_buf[4], crc16); //将CRC的2字节的校验值,转化成两个一字节存在unsigned char 当中 write(cli_fd, nak_buf, strlen(MIN_PACK_SIZE) ); goto start_loop; } printf("Current TLV data abnormal.n") //报文未读完,继续保存到buf,下一次读取 if (len > size-i) { memmove(r_buf, &r_buf[ofset], strlen(size-i) ); printf("TLV packed is accomplish.n"); printf("Wait next data input.n"); crc16 = crc_itu_t(MAGIC_CRC, (unsigned char *)nak_buf, 4); //获得unsigned short类型校验位 ushort_to_bytes(&ack_buf[4], crc16); //将CRC的2字节的校验值,转化成两个一字节存在unsigned char 当中 write(cli_fd, ack_buf, strlen(MIN_PACK_SIZE) ); return size-i; //剩余字节数 } //正常读取TLV数据包,需要首先判断CRC检验和是否相同, 不同则直接丢弃该报文段 crc16 = crc_itu_t(MAGIC_CRC, (unsigned char *)r_buf[i], len); //获得unsigned short类型校验位 val = bytes_to_ushort(&r_buf[i+len-2], crc16); //将CRC的2字节的校验值,转化成两个一字节存在unsigned char 当中 //CRC若不相等则检验失败,发送NAK报文请求重传 if (crc16 != val) { printf("CRC checkout failure.n"); memmove(r_buf, &r_buf[i], strlen(len) ); crc16 = crc_itu_t(MAGIC_CRC, (unsigned char *)nak_buf, 4); //获得unsigned short类型校验位 ushort_to_bytes(&nak_buf[4], crc16); //将CRC的2字节的校验值,转化成两个一字节存在unsigned char 当中 write(cli_fd, nak_buf, strlen(MIN_PACK_SIZE) ); goto start_loop; } printf("CRC checkout sucessfuly.n"); //解析成功,将该TLV数据包保存进行处理 ofset = 0; ofset = (i+3); if (r_buf[i+1] == Tag_temper) { ; //实现自己想要的功能 } //分析完成一个TLV数据包后,丢弃该包,分析下一个TLV printf("Unpack a TLV data accomplish.n"); memmove(r_buf, &r_buf[i], size-i-len); size = size-i-len; //分析剩余TLV数据 goto start_loop; }//if (r_buf[i] == HEARDER) }//for (i=0; i<size; i++) return 0; }

参考资料:https://blog.csdn.net/Ternence_zq/article/details/105757272
https://blog.csdn.net/qq_43296898/article/details/88863854

最后

以上就是雪白舞蹈最近收集整理的关于TLV协议(完善+封包+解析)的全部内容,更多相关TLV协议(完善+封包+解析)内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部