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就能避免错误。
最后我们能成功解析出数据来