TLV协议——实现封包与解析

1. 什么是TLV协议?

TLV,即Tag(Type)—Length—Value,是一种简单实用的数据传输方案。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度。
在这里插入图片描述
这里插入个简单应用通讯协议的例子,现在A机器通过网络socket发送数据给B机器,设定数据内容为0x14 0x30 0x47 0x33。如果没有相应的协议规范,那么B机器只是接收了这几个字节的数据,但这些数据所代表的含义就不得而知了。现在我们规定前两个字节是温度,后两个字节是湿度,那么通过这个协议,B机器就可以对接收到的数据进行解析,从而获得温度是20.48摄氏度,相对湿度是48.71%。

2. 只用TLV可能会出现问题

  • 问题1,数据可能重合!如果你设置0x01为温度,那么后面有个0x01可能是表示数据的,而不是温度!

    这时候我们需要加一个报头,固定为0xFD,用来标志一个报文的开始。

  • 问题2,数据可能会跳变!你不能保证数据传输过程中数据的跳变,0x01表示温度,跳变为0x02就表示另外的东西了!

    这时候我们需要在一个字节流末尾加一个CRC校验,传输前算一下字节流的大小,传输到另外一个端口后再算一下,对比前后两个CRC的值,如果相同,表示没有发生字节跳变,如果不同,那就舍弃!

  • 问题3,报头可能在TLV中!

    同样也要加CRC校验

加工后的TLV:在这里插入图片描述

3. 存放字节流buffer的问题

  • 如果buf定义在函数里面,是局部变量,数据存在栈中,可以用malloc,但是麻烦!

  • buf定义在函数外,但不可重入,不能用多个进程同时操作!

所以要把buf写在函数里面,用函数操作buf

pack(buf, sizeof(buf), cmd)

还要判断buf是否合法

if( !buf || sizeof(buf)<0)

4. TLV封包

#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

4. TLV解析

1> memmove()函数

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’也拷贝进来。

函数解释:
第一个参数:是一个数组名,表示首地址
第二个参数:数组名加数字(首地址偏移(数字)位),表示要移动的字符串起始位置
第三个参数:数组长度减去数字,表示从移动的字符串起始位置开始总共的字符

2> 解析步骤

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就做处理,如果是错误的就找下一个帧头,重复上述操作。

TLV协议——实现封包与解析_第1张图片

#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

你可能感兴趣的:(Unix环境编程(APUE))