所谓通讯协议就是指通信双方对数据传送控制的一种约定。约定中包括对数据格式,同步方式,传送速度,传送步骤,纠错方式以及控制字符定义等问题做出统一规定,通信双方必须共同遵守,倘若一方不遵守,便会直接导致数据不能被解析!更通俗来讲,它可以理解两个节点之间为了协同工作实现信息交换,协商一定的规则和约定,例如规定字节序,各个字段类型等。
Tag:标记
Length:一般为value的长度,也可以是整个TLV的长度;
Value:真正的数据长度。
如下表格所示:完整的TLV的协议:
为了读取一个TLV错误导致后面的TLV都全部乱序了。
报文头 | Tag | Length | Value | CRC16校验和 |
---|---|---|---|---|
1 Byte | 1 Byte | n+5 Bytes | n Bytes | 2 Bytes |
报文头:用来标志一个报文的开始;
CRC16:占2个字节,从报文头开始到数据结尾(Value)所有数据的CRC校验和;
这里列举了产品的ID 时间 和温度的TLV报文, 假如数据报文是如下这样简单的TLV:
当接收数据时发生一个温度的TLV错误 ,变成如下所示:
这里一个TLV错误,导致数据读取时找到最近的0x03作为温度的Tag, 0x02表示Length, 0x0c 16 就是value值, 那后面岂不是乱套了, 若定义了0x04标识,那在从该位置进行解析,接收的数据就GG了.
所以加上标识
和和CRC校验位
, 标识一般采用0xfd,不容易引起错误, 最好不要用0x01 02 等作为头;
CRC校验
时需要将整个HTLV都校验,如果检验错误直接丢弃该HTLV数据,再寻找下一个HTLV即可.
TLV封包代码如下:
#include
#include
#include
#include"crc-itu-t.c"
#include
#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 arguments\n");
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解包的代码有些复杂, 不要过多依赖对端,考虑了粘包的可能性.
加入了ACK/NAK
机制:
ACK
表示确认收到报文,可以进行下一次传输;
NAK
表示报文出错,请求再次重传,一般不超过三次.
#include
#include
#include
#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: %s\n", 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")
//这里需要注意, 若将字符串后面的'\0'也读取到了,那么会将目的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
return 0;
}