最近在写一个温度实时监控的项目,要用到TLV通讯协议,看了很多博客,慢慢的从里面明白了TLV的实现方式及用c语言实现TLV的编码,下面我将浅谈一下TLV,在讲TLV之前我们先讲一下什么是通讯协议。
通讯协议
1、通讯协议
协议可以使双方不需要了解对方的实现细节的情况下进行通信,因此双方可以是异构的,server可以是c++,client可以是java,基于相同的协议,我们可以用自己熟识的语言工具来实现。
协议一般由一个或多个消息组成,简单的来说,消息就像是一个Table,由表头(消息的字段定义,包括名称与数据类型)与行(字段值)组成。
2、自定义通讯协议
约定好双方交换数据的编解码方式,包括一致的基本数据类型,业务类型,字节序、消息内容等。
什么是自定义通讯协议TLV
TLV协议是BER编码的一种,全称是Tag、length、value。该协议简单高效,能适用于各种通信场景,且具有良好的可扩展性。TLV协议的基本格式如下:
其中,Tag,是报文的唯一标识;Length,表示Value字段的长度;Value字段的数据是需要传输的数据,长度由Length字段表示。
只用TLV可能会出现问题
我们该如何理解TLV那,通信的双方一定要有一种规约,收到的数据该如何解析,比如我们在应用层A用户给B用户发数据:
A------- 数据----------->B
数据在收发时,一定会存在三个非常重要的信息:
1、这是什么数据? Tag
2、数据有多少个字节? Length
3、数据的内容是什么? Value
B收到A发来的信息,就开始按照这三个开始解析,是不是A发来的消息。但是只有TLV封装的话,A给B发送过程中,会出现许多差错,比如:
-
问题1:数据可能重合!如果你设置0x01为温度,那么后面有个0x01可能是表示数据的,而不是温度!
这时候我们需要加一个报头,固定为0xFD,用来标志一个报文的开始 -
问题2:数据可能会跳变!你不能保证数据传输过程中数据的跳变,0x01表示温度,跳变为0x02就表示另外的东西了!
这时候我们需要在一个字节流末尾加一个CRC校验,传输前算一下字节流的大小,传输到另外一个端口后再算一下,对比前后两个CRC的值,如果相同,表示没有发生字节跳变,如果不同,那就舍弃!
-
问题3:报头可能在TLV中!
同样也要加CRC校验
加工后的TLV:
如果还有不清楚为什么要加报文头和CRC校验的同学,可以参考这篇博客https://blog.csdn.net/qq_43296898/article/details/88863854
为接下来我要完成网络socket实现温度上报,要用到自定义通讯协议TLV,我讲用TLV的格式将“ID/时间/温度“上报,如“RPI0001/2019-01-05 11:40:30/30.0C”; 根据上面介绍的我将把这个数据封装成TLV格式上报。这里面有三种数据要发送,所以就要写三个TLV报文。
TLV的封包代码示例
下面是我用c代码实现的TLV报文格式的发送信息:
#include <stdio.h>
#include <string.h>
#include "header.h"
#include "tlv.crc-itu-t.h"
#define HEAD 0xFD //定义一个报文头
#define BUF_SIZE 128 //定义一个buffer的大小
#define TLV_FIXED_SIZE 5 //固定TLV字节流大小,不包含value的值
#define TLV_MINI_SIZE (TLV_FIXED_SIZE+1) //TLV字节流的最小值,value的最小值为1个字节
enum //使用枚举 Tag的值会自加
{
TAG_ACK = 1,
TAG_SN, //树莓派上的id
TAG_TEMP, //温度
TAG_TIME, //时间
};
int pack_ack (char *buf , int size); //声明ack封装函数
int pack_sn (char *buf , int size);//声明id封装函数
int pack_temp (char *buf , int size);//声明温度封装函数
int pack_time (char *buf , int size);//声明时间温度封装函数
int main (int argc, char