TLV,即Tag(Type)—Length—Value,是一种简单实用的数据传输方案。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度。
这里插入个简单应用通讯协议的例子,现在A机器通过网络socket发送数据给B机器,设定数据内容为0x14 0x30 0x47 0x33。如果没有相应的协议规范,那么B机器只是接收了这几个字节的数据,但这些数据所代表的含义就不得而知了。现在我们规定前两个字节是温度,后两个字节是湿度,那么通过这个协议,B机器就可以对接收到的数据进行解析,从而获得温度是20.48摄氏度,相对湿度是48.71%。
问题1,数据可能重合!如果你设置0x01为温度,那么后面有个0x01可能是表示数据的,而不是温度!
这时候我们需要加一个报头,固定为0xFD,用来标志一个报文的开始。
问题2,数据可能会跳变!你不能保证数据传输过程中数据的跳变,0x01表示温度,跳变为0x02就表示另外的东西了!
这时候我们需要在一个字节流末尾加一个CRC校验,传输前算一下字节流的大小,传输到另外一个端口后再算一下,对比前后两个CRC的值,如果相同,表示没有发生字节跳变,如果不同,那就舍弃!
问题3,报头可能在TLV中!
同样也要加CRC校验
如果buf定义在函数里面,是局部变量,数据存在栈中,可以用malloc,但是麻烦!
buf定义在函数外,但不可重入,不能用多个进程同时操作!
所以要把buf写在函数里面,用函数操作buf
pack(buf, sizeof(buf), cmd)
还要判断buf是否合法
if( !buf || sizeof(buf)<0)
#include
#include
#include"crc-itu-t.h"
#include"crc-itu-t.c"
#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个字节
#define ON 1
#define OFF 0
enum //使用枚举,Tag的值会自加
{
TAG_LOGON=1, //登陆Tag
TAG_CAMERA, //摄像头Tag
TAG_LED, //照明灯Tag
};
int pack_logon(char *buf, int size, char *psw); //声明登陆(logon)封装函数
int pack_camera(char *buf, int size, int cmd); //声明摄像头(camera)封装函数
int pack_led(char *buf, int size, int cmd); //声明照明灯(led)封装函数
void dump_buf(char*type,char *data,int len); //声明dump_buf
int main(int argc, char **argv)
{
char buf[BUF_SIZE];
int bytes; //一个封装函数的字节流的个数
bytes = pack_logon(buf,sizeof(buf),"iot@yun"); //设置登陆(logon)函数
dump_buf("Logon",buf,bytes); //设置dump_buf函数,把所有的字节流都放到dump_buf里面
bytes = pack_camera(buf,sizeof(buf),ON); //设置摄像头(camera)函数
dump_buf("Camera",buf,bytes);
bytes = pack_led(buf, sizeof(buf), ON); //设置照明灯(led)函数
dump_buf("LED",buf,bytes);
return 0;
}
int pack_logon(char *buf, int size, char *psw) //定义登陆(logon)封装函数
//buf 缓冲区
//size 缓冲区的大小
//psw 密码
{
unsigned short crc=0; //crc校验值
int pack_len=0;
int data_len=0;
int ofset=0; //buf的索引位置
if( !buf || !psw || size
memmove用于拷贝字节,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。
函数简介:
原型:void *memmove( void *dest, const void *src, size_t count );
头文件:
功能:由src所指内存区域复制count个字节到dest所指内存区域。
相关函数:memset、memcpy
程序示例:
#include
#include
int main(void)
{
char s[]="Golden Global View";
memmove(s,s+7,strlen(s)+1-7);
printf("%s",s);
getchar();
return 0;
}
程序输出结果: Global View
*注意:这里的拷贝长度strlen(s)+1-7表示把字符串结尾的’\0’也拷贝进来。
函数解释:
第一个参数:是一个数组名,表示首地址
第二个参数:数组名加数字(首地址偏移(数字)位),表示要移动的字符串起始位置
第三个参数:数组长度减去数字,表示从移动的字符串起始位置开始总共的字符
tlv报文的格式: [帧头] [Tag] [Length] [Value] [CRC校验和] (这里我用的CRC校验和是16位的所以占两个字节)
先从一个最简单的解一个一帧的包说起,首先需要找到帧头,接着往下读到长度len,从帧头开始到len-2(除开后面2位CRC)可以算出一个CRC值,用算出来的CRC值与tlv最后两位CRC的值做对比,如果相等说明传过来的tlv报文是正确的,接着根据Tag类型以及Value值做相对应的处理。
多帧解包也是如此,假设tlv报文总的长度是bytes,做个循环从0开始到bytes找,找到帧头接着就往下读tlv的长度,算CRC做比较,如果是正确的tlv就做处理,如果是错误的就找下一个帧头,重复上述操作。
#include
#include
#include
#include"crc-itu-t.c"
#include
#define TLV_MAX_SIZE 128
#define TLV_MIN_SIZE 6
#define HEAD 0xfd
enum
{
TAG_LOGON=1,
TAG_CAMERA,
TAG_LED,
};
int unpack(char *buf,int bytes);
int main()
{
int bytes;
char array[]={0xfd,0x03,0x06,0x00,0xb2,0x0e};
bytes = unpack(array,sizeof(array));
printf("The array has %d bytes!\n",sizeof(array));
printf("array has %d bytes now!\n",bytes);
return 0;
}
int unpack(char *buf,int bytes)
{
int i;
char *ptr=NULL;
int len;
unsigned short crc,val;
if( !buf )
{
printf("Invailed input!\n");
return 0;
}
again:
if( bytes < TLV_MIN_SIZE ) //数据小于一帧
{
printf("TLV packet is too short!\n");
printf("Wait for continue input...\n");
return bytes; //返回半帧的值
}
for( int i=0; i TLV_MAX_SIZE) //这一帧中(length)长度错误
{
memmove(buf,&ptr[2],bytes-i-2); //把这一帧扔掉
goto again; //继续遍历
}
if(len > bytes-i) //(length)比剩下的还要长
{
memmove(buf,ptr,bytes-i); //把半帧移到buf的开端
printf("TLV packet is incomplete!\n");
printf("Wait for continue input...\n");
return bytes-i; //返回半帧的值
}
crc = crc_itu_t(MAGIC_CRC,(unsigned char *)ptr,(len-2));
val = bytes_to_ushort( (unsigned char *)&ptr[len-2],2 );
if(val != crc) //两次的CRC不同
{
printf("CRC check error\n");
memmove(buf,&ptr[len],bytes-i-len); //把这一帧扔掉
bytes = bytes-i-len;
goto again; //继续遍历
}
for(int i=0; i