四.结构体(message)
Protobuf的Message结构是由一系列的key-value对组成的。其中,key由两部分组成,成员序号field_number
和value的类型wire_type
,他们通过(field_number
<< 3) | wire_type
的方式编码为一个varint;value根据wire_type而定;
Message结构由.proto文件定义,这是协议双方的接口文件,文件里面定义了每个结构成员的序号即field_number
,以及每个成员的类型(该类型会映射到wire_type),例如:
message MSG_Login_Rsp
{
required 1:uint32 rspcode = 100;//
返回值,1为默认值
required 2:string rspdesc =
"success"; //success
为默认值
}
其中,rspcode的序号为1,类型为uint32,默认值为100;rspdesc的序号为2,类型为string,默认值为success。
下面我们来看如何根据proto文件序列化结构体(proto文件的规则后续的文章中会说明,这里只是原理性的描述)。
首先,需要做成员类型的划分,如下:
Type
|
含义
|
使用场景
|
0
|
Varint
|
uint8,uint16,uint32
|
2
|
Length-delimited
|
String
,bytes,embedded
messages,repeated fields
|
说明1)embedded message为结构体的嵌套类型
2)repeated fields为某个可能重复的字段,重复次数可以为0
3)Length-delimited的含义是“长度+值”,其中长度采用varint编码,值为字符序列或者二进制码流
4)Type的含义就是前文提到的wire_type
1)以uint32为例,假若有如下proto接口
message
Test1
{
required
1: uint32 a = 150;
}
查看序列化的结果,可以得到16进制:
08 96 01
由于uint32类型较为简单,我们容易得出,key为8,其中wire_type
为0,field_number为1,value为150
2)以string字符串为例,假若有如下proto接口
message
Test2
{
required
2: string msg = “testing”;
}
此时如果序列化,我们回得到下面16进制码流:
12
07 74 65 73 74 69 6e 67
刚才已经提到message的编码都是key-value对,OK,我们对号入座,由于key是一个varint,我们很容易断定12就是key,将12转化为二进制格式:0001
0010,得出:wire_type
为2,field_number为2,说明接下来会出现
Length-delimited类型,根据“长度+值”的规则,推断出7为string的长度,string为“testing”。
3)以结构体的嵌套(embedded message)为例,假若有如下proto接口
message
Test3
{
required
3: Test1;
}
看序列化码流:
1a 03 08 96 01
参考上面string的情况,我们得到:key为1a,其中wire_type
为2,field_number为3,value的为 08 96 01,注意此时value本身是一个key-value对,这个对我们在1)中已经做了解析。
下一篇文章会描述proto接口文件的规则(主要基于libprotobuf的规则,但又有简化)