TLV学习

TLV的基本定义

TLV即tag length value,广义上来讲他并不是一个固定格式的协议,他可以是人们自己定义的用来网络通讯的协议,只有遵循了定义人的装包解包流程才能建立通讯。
从应用层HTTP协议,到超文本置标语言HTML(HyperText Mark-up Language),再到可扩展置标语言XML(Extensible Markup Language),它们提供了数据的格式化存储、传输和格式化显示的规范,是网络通信的基石。然而HTTP协议以及HTML/XML置标语言的本质就是定义一堆标签(Tag)对数据进行串行化序列化,然后接收方再根据标签解析、还原数据。

自定义通信协议的关键是对数据包的合理构造(construct)和正确解析(parse),即制定编解码规则。

抽象语法标记ASN(Abstract Syntax Notation) BER的长度确定的编码方式,由3部分组成Identifier octets、Length octets和Contents octets,实际上这就是一中TLV(Type-Length-Value)模型:类型字段(Type或Tag)是关于标签和编码格式的信息;长度字段(Length)定义数值的长度; 内容字段(Value)表示实际的数值。

因此,一个编码值又称TLV三元组。编码可以是基本型或结构型,如果它表示一个简单类型的、完整的显式值,那么编码就是基本型(primitive);如果它表示的值具有嵌套结构,那么编码就是结构型 (constructed)。

TLV编码就是指对Type(Tag)、Length和Value进行编码,形成比特流数据包;解码是编码的逆过程,是从比特流缓冲区中解析还原出原始数据。
那么TLV只是应用层协议,我们还需要不同层面的协议,比如传输层的TCP/UDP协议,这里就不细说。

字节流和字符流

字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串

  • 字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
  • 字节流默认不使用缓冲区;字符流使用缓冲区。
  • 字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元
  • 字节流传输数值时能节省相当一些空间,字符流不行。
    所以说字节流是非常强大,在为了追求低功耗的物联网时代,能对少字节的传达正确的信息是必不可少的,那么字节流传输必是首选的方式。

自定义TLV

实现温度打包网络传输,再解包加存入数据库
定义一个包 分为 tag datalenth value , value分为 id time temperature,三个值分别用分隔符隔开,方便解析。
客户端流程图(ps:第一次画图。。画的不太好)

在这里插入图片描述
在这里插入图片描述
client代码

        pack_buf[pointer] = THE_HEAD;// head
        pointer += 1;
 
        pack_buf[pointer] = LHJ;
        pointer += 1;
 
        datalen = strlen(id_buf)+6+2+2;
        pack_buf[pointer] = datalen;//lenth
        pointer += 1;
           
        datalen = strlen(id_buf);
        memcpy(pack_buf+pointer, id_buf, datalen);
        pointer += datalen;//value
        
        pack_buf[pointer] = '|';
        pointer += 1;
        /* time part */
        int byte = get_sys_time(time_buf);
        datalen = byte;
        //memcpy(pack_buf+pointer, time_buf, datalen);
            
        for(int i=0;i<datalen;i++,pointer++)
            pack_buf[pointer] = time_buf[i];
        /* temper part */
        pack_buf[pointer] = '|';
            pointer += 1;
        float temper = ds18b20_get_temper();
        int temper_p1;
        int temper_p2;
        temper_p1 = (int)temper;// integer 1 byte
        if(temper_p1 > temper)
            temper_p1 -= 1;
 
        temper_p2 = (int)((temper-temper_p1)*100);// decimal 1 byte
        if((sizeof(pack_buf)-pointer) < (2+2))
        {
            printf("have no more space to put temperature!\n");
            return 1;
        }
        
 
        pack_buf[pointer] = temper_p1;
        pointer += 1;
        pack_buf[pointer] = temper_p2;
        pointer += 1;
 
        crc16 = crc_itu_t(MAGIC_CRC, pack_buf, pointer);//CRC
        ushort_to_bytes(&pack_buf[pointer], crc16);
        pointer += 2;
        pack_len = pointer;
 
        send_temper(pack_buf, fd,pack_len);
        pointer = 0;
        sleep(time_space);

server流程图

在这里插入图片描述
下图是虚线框的细节处理
在这里插入图片描述
server代码

int get_true_msg(int *flag,char buf[SIZE],char true_buf[SIZE])
{
    int len         = 0;
    int byte        = 0;
    int crc         = 0;
    int crc_ago     = 0;
 
 
    for(int i=0;i<SIZE;i++)
    {
        if(buf[i] == 253)
        {
            if(*flag-i < MINSIZE)//if leave space < minsize 
            {
                memmove(buf, &buf[i], *flag-i);
                *flag = *flag-i;
                printf("get incomplete packet.\n");
                break;
            }
            else if(buf[i+1] == 170)//tag
            {
                len = buf[i+2];
                printf("%d\n",len);
                if(len+5 > *flag-i)//the message is incomplete
                {
                    memmove(buf, &buf[i], *flag-i);
                    *flag = *flag - i;
                    printf("get incomplete packet.\n");
                    break;
                }
                else//the message is complete and we will confirm if the crc right
                {
                    crc = crc_itu_t(MAGIC_CRC, &buf[i], len+3);
                    crc_ago = bytes_to_ushort(&buf[i+3+len],2);
                    
                    if(crc != crc_ago)
                    {
                        printf("crc is not match.");
                        continue;
                    }
                    else
                    {
                        memmove(true_buf,&buf[i+3],len);
                        byte += len;
                        continue;
                    }
                }
            }
        }
        *flag = 0;
        return byte;
    }
}

那么对包的解析要做到没有瑕疵,即所有的可能性都包括。代码后面我都标识了。
flag是buf读到的位置,函数中对flag的处理是将flag指向下一次将read的位置这样才能将不完整的消息补齐。
处理成功后将有用的字节放进true buf中,在根据分隔符拿出不同的数据,进行处理。这样解包就完成了。
这里还用到了memmove,这个函数在memcpy上做了改进,移一段buf到另一块区域时,如果目的区域与源区域有重叠,则会出现错误,这个时候memmove就能避免错误。
最后我们能成功解析出数据来
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_40215005/article/details/89114939