首先看下下面这个proto文件,我们后面的proto基本用法都是基于这个proto进行讲解

package pkgName;

option java_package = "test1.test2";
option java_outer_classname = "TestClass";

message mmData {
    optional int32 num = 1;
    optional int32 def_num = 2 [default=10];
    required string str = 3;
    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中的class关键字,使用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规则,参考:为什么 proto3 移除了 required 和 optional?

(2)在proto2版本中,默认配置下,一个optional没有被设置或者被显示的设置为默认值,在序列化二进制格式的时候,这个字段将会被去掉,导致反序列化之后,无法区分当初没有设置还是设置了默认值,即使使用hasXXX()方法,对于设置的默认值的字段,也是返回false。解决方法:区分 Protobuf 中缺失值和默认值

3.3 标识号

​ 在消息体的定义中,每个字段都必须要有一个唯一的标识号。这些标识号是用来在消息的二进制格式中识别各个字段的,一旦使用就不能再改变,否则会导致原有消息编解码出现异常。

​ 标识号是[0,2^29 - 1]范围内的一个整数,其中[19000,19999)之间的标识号在protobuf协议的实现中被预留了,所以特写注意不要使用这个范围内的标识号,若使用进行编译的时候也会告警.

Field numbers 19000 through 19999 are reserved for the protocol buffer library implementation.

1

注意:[1,15]内的标识号在编码的时候占用一个字节,[16,2047]之内的标识符占用两个字节,所以尽量为频繁使用的字段分配[1,15]内的标识号,另外预留出来一部分给未来可能频繁使用的字段。

3.4 数据类型

3.4.1 基本数据类型

​ 消息体中的每个字段都必须指定字段类型,可选的字段类型和语=与其对应的C++/Java中的类型如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KTrq0uIB-1588870734147)(/Users/wingwei/workspace/wx_workspace/文档/image/proto数据类型.png)]

图片资源来源:https://blog.csdn.net/m15927408113/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要使用则需要通过messageType.enumType来进行引用。

​ 默认情况下,枚举类型中的字段值不可重复,但是通过对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;
    }
}

嵌套扩展在c++中的访问方式类似,即在访问扩展字段的时候在字段前加上作用域空间。

base::BaseProto test;
test.SetExtension(OtherProto::name, "wing");

参考链接:

protobuf语法详解

Protobuf3语法详解

为什么 proto3 移除了 required 和 optional?

[译]Protobuf 语法指南

基于protobuf的RPC实现
————————————————
版权声明:本文为CSDN博主「langzi989」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014630623/article/details/105985944/

最后编辑: Simon  文档更新时间: 2021-09-01 23:56   作者:Simon