protobuf基本⽤法详解
⽂章⽬录
⾸先看下下⾯这个proto⽂件,我们后⾯的proto基本⽤法都是基于这个proto进⾏讲解
package pkgName;
option java_package = "st2";
option java_outer_classname = "TestClass";
message mmData {
optional int32 num = 1;
optional int32 def_num = 2 [default=10];
required string str = 3;c++string类型
repeated string rep_str = 4;
}
1、包名package
proto⽂件使⽤关键字package指定当前包名,它类似于java中的包名或者C++中的命名空间,主要是⽤来防⽌不同消息类型的命名冲突。使⽤protobuf编译器将proto⽂件编译成C++代码之后,当前proto⽂件中的所有声明都将位于命名空间pkgName::下。
2、option
在消息定义之前,可以通过option来进⾏配置,常⽤的两个option:
option java_package=“xxx/xxx” 该选项指定了java⽂件⽣成的路径
option java_outer_classname=“xxx” 该选项制定了⽣成的java类名
3、消息类型
3.1 message
Protobuf中定义⼀个消息类型是通过关键字message字段指定的,这个关键字类似于C++/Java中的cla
ss关键字,使⽤protobuf编译器将proto编译成C++代码之后,每个message都会⽣成⼀个名字与之对应的C++类。
如上⾯的message 将⽣成⼀个名字为mmData的类,该类公开的继承google::protobuf::Message。
3.2 字段规则
message中的字段规则有三种。
required : 字段属性为必填字段。若不设置,则会导致编解码异常,导致消息被丢弃。
optional : 字段属性为可选字段。发送⽅可以选择性根据需要进⾏设置;对于optional属性的字段,可以通过default关键字为字段设置默认值,即当发送⽅没有对该字段进⾏设置的时候,将使⽤默认值。如果没有对字段设置默认值,就会根据特定的类型给字段赋予特定的默认值。对于bool类型,默认值为false;对于string类型,默认值为空字符串;对于数值类型,默认值为0;对于枚举类型,默认值是枚举类型中的第⼀个值。
repeated : 字段属性为可重复字段,该字段可以包含[0,n]个元素,字段中的元素顺序被保留。
注意:
(1)在proto3版本中,字段规则上移除了required,并把optional字段改名为singular。所有没有指定字段规则的字段默认为optional,对于为什么删除了require规则,参考:
(2)在proto2版本中,默认配置下,⼀个optional没有被设置或者被显⽰的设置为默认值,在序列化⼆进制格式的时候,这个字段将会被去掉,导致反序列化之后,⽆法区分当初没有设置还是设置了默认值,即使使⽤hasXXX()⽅法,对于设置的默认值的字段,也是返回false。解决⽅法:
3.3 标识号
在消息体的定义中,每个字段都必须要有⼀个唯⼀的标识号。这些标识号是⽤来在消息的⼆进制格式中识别各个字段的,⼀旦使⽤就不能再改变,否则会导致原有消息编解码出现异常。
标识号是[0,2^29 - 1]范围内的⼀个整数,其中**[19000,19999)之间的标识号在protobuf协议的实现中被预留了**,所以特写注意不要使⽤这个范围内的标识号,若使⽤进⾏编译的时候也会告警.
Field numbers 19000 through 19999 are reserved for the protocol buffer library implementation.
注意:[1,15]内的标识号在编码的时候占⽤⼀个字节,[16,2047]之内的标识符占⽤两个字节,所以尽量为频繁使⽤的字段分配[1,15]内的标识号,另外预留出来⼀部分给未来可能频繁使⽤的字段。
3.4 数据类型
3.4.1 基本数据类型
消息体中的每个字段都必须指定字段类型,可选的字段类型和语=与其对应的C++/Java中的类型如下图:
[外链图⽚转存失败,源站可能有防盗链机制,建议将图⽚保存下来直接上传(img-KTrq0uIB-1588870734147)
(/Users/wingwei/workspace/wx_workspace/⽂档/image/proto数据类型.png)]
图⽚资源来源:blog.csdn/m159********/article/details/79976528
3.4.2 枚举类型
字段类型除了上述基本的字段类型之外,也可以是枚举类型。protobuf中的枚举类型使⽤⽅法与C++中的枚举类型类似,在将proto⽂件编译成C++代码后,其对应的类型也是C++中的枚举类型。
package pkgName;
// 定义枚举类型
enum DayName {
Sun = 0;
Mon = 1;
Tues = 2;
Wed = 3;
Thur = 4;
Fri = 5;
Sat = 6;
}
message workDay {
// 消息类型使⽤枚举类型
optional DayName day = 1;
}
枚举常量的值必须在32位整数范围内,因为enum值是使⽤可编码⽅式存储的,对负数存储不够⾼效,因此不推荐在enum中使⽤负数。
枚举类型可以定义在message内,也可以定义在message外,若定义在message内,其他message要使⽤则需要通过umType来进⾏引⽤。
默认情况下,枚举类型中的字段值不可重复,但是通过对enum添加**option allow_alias = true;**来达到对同⼀个枚举值起⼀个别名的⽬的,若不添加allow_alise并且有重复的枚举值编译的时候会报错。
package pkgName;
enum DayName {
// 若不添加该option,会报错:
// "pkgName.Test" uses the same enum value as "pkgName.Sat". If this is intended, set 'option allow_alias = true;' to the enum definition.
option allow_alias = true;
Sun = 0;
Mon = 1;
Tues = 2;
Wed = 3;
Thur = 4;
Fri = 5;
Sat = 6;
Test = 6; // Test与Sat字段值重名
}
3.4.3 map数据类型
除了上述类型之外,message还⽀持map<Type,Type>类型。
package pkgName;
message Tdata {
map<int32,string> data = 1;
}
注意:
(1) protobuf中的map实质上是unordered_map
(2) proto中map类型不能⽤optional/required/repeated任何类型修饰。
3.4.4 message类型
protobuf允许将其他消息类型⽤作字段类型。如下⾯userData中存在⼀个workDay类型的数据。
package pkgName;
message workDay {
optional int day = 1;
}
message userData {
optional workDay userDays = 1;
}
3.4.5 嵌套消息类型
与C++中的类可以嵌套类似,消息也可以嵌套。即允许你在⼀个messageType中定义另⼀个messageType,然后使⽤它,
package pkgName;
message OutterData {
// 嵌套消息定义,在⽣成的C++代码中,该message被组织为类:outterData_Tdata
message Tdata {
optional int32 a = 1;
}
// 引⽤嵌套消息
optional Tdata data = 1;
}
message嵌套在⽣成C++代码之后,实际上内部类⽣成的类名为OutterData_Tdata
4、import导⼊其他proto⽂件
4.1 import
我们可以通过import导⼊其他proto⽂件,并使⽤该proto⽂件中的定义的消息类型。与c++中的include或者Java中的import类似。
如:
// a.proto⽂件
package Ap;
message A{
optional int32 a = 1;
}
// b.proto⽂件
import "a.proto"; // 导⼊a.proto⽂件,这样就可以直接使⽤a.proto中定义的消息类型了。
package Bp;
message B{
optional Ap.A a = 1;
}
4.2 import public
默认情况下,proto只允许引⽤直接import的⽂件中定义的数据类型。如b.proto中导⼊了a.proto,c.proto中导⼊了b.proto;默认情况下,c.proto中只能引⽤b.proto中定义的数据类型,⽽引⽤不到a.proto中的数据类型。若c.proto要使⽤a.proto中定义的数据类型,则b.proto引⽤a.proto的时候要使⽤import public。
// a.proto⽂件
package Ap;
message A{
optional int32 a = 1;
}
// b.proto⽂件,使⽤import public 允许a.proto对b.proto的引⽤者(c.proto)可见
import public "a.proto";
package Bp;
message B{
optional Ap.A a = 1;
}
// c.proto
import "b.proto";
package Cp;
message C{
optional Ap.A a = 1;
}
这种⽤法在迁移proto⽂件到新的位置的时候⼗分有⽤,如Message类要从old.proto迁移到new.proto⽂
件中,这个时候如果要在不修改对old.proto的⽂件的情况下,直接将Message移动到new.proto中,然后在old.proto中import public new.proto即可。
5、更新Message消息类型原则
为了达到前后消息类型兼容的⽬的,扩展Message消息类型的时候需要注意⼀下⼏点:
1、不要更改任何已有的字段的数值标识。
2、所添加的字段属性必须是optional 或者repeated类型,如果扩展required类型,会导致旧的消息解析异常
3、⾮required字段可以移除。要保证它们的标⽰在新的消息类型中不再使⽤
4、⼀个⾮required的字段可以转换为⼀个扩展,反之亦然——只要它的类型和标识号保持不变。
5、int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的⼀个转换为另外⼀个,⽽不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进⾏了强制类型转换⼀样(例如,如果把⼀个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
6、sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
7、string和bytes是兼容的——只要bytes是有效的UTF-8编码。
8、嵌套消息与bytes是兼容的——只要bytes包含该消息的⼀个编码过的版本。
9、fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
6、protobuf扩展
6.1 extensions&extend
protobuf允许Message中预留出⼀个标识号段⽤作给第三⽅扩展使⽤。当其他⼈需要在message中扩展新的字段的时候,就不需要直接修改原⽂件,直接在⾃⼰的proto⽂件中定义该Message的扩展字段即可。(注意:⼀定要保证不同⽂件中扩展的标识号不能重复,否则会导致数据不⼀致的现象)
// base.proto
package base;
message BaseProto{
optional int32 id = 1;
extensions 1000 to 2000;
}
// 扩展BaseProto
extend base.BaseProto{
optional string name = 1000;
}
注意访问扩展字段的⽅式与访问普通字段的⽅式有所不同,如在C++中对扩展字段设置为:
base::BaseProto test;
test.SetExtension(name, "wing");
同时还提供了⼀些其他的访问操作,如:HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及AddExtension()等。
6.2 嵌套扩展
嵌套扩展即为可以在另外的类中添加扩展。
// base.proto
package base;
message BaseProto{
optional int32 id = 1;
extensions 1000 to 2000;
}
message OtherProto {
extend BaseProto {
optional string name = 1000;
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。