3 protocol buf 语法

syntax

第一个非注释的语句必须是syntax ,它表示使用什么版本的语法,如果没有使用默认是proto2版本语法

Message

Message含义

  Mesage就像c++的一个类,其列(field)变量就像它的成员,当列变量是整形(内置类型)时,它就像有个相应变量名的函数,当列变量是Message类型时,它就像有个成员变量,这个成员变量也是一个类。

Message语法
message Foo{
关键字 类型 变量名 = field_number;
enum Color{
   BLACK=0;
   RED=1;
}
oneof test_oneof{
类型 变量名 = field_number;
}
map<KeyType,ValueType> object \
= field_number;
required关键字

  在使用protobuf编译器生成的文件中,必须去设置这个值

optional关键字

  这个在使用这个关键字的时候,可以设置一次或者不设置它的值,不能设置多次。
  对于optional 关键字,它还有一个独特的功能就是设置默认值

message Foo{
 optional string name = 1[default = "YuTian"];
 }
repeated关键字

这个关键字表示可以设置多个值,它的使用就像数组一样,大家可以试试看看生成代码的接口。还有一点就是对于这个接口大家使用的时候,对于标量类型的时候,其后面应该加上[packed=true]来增加其编码性能

repeated int32 samples = 4 [packed=true];   
reserved Values用法

当我们使用枚举类型在protobuf中的时候,发现以前的设计不合理我们可能会屏蔽或者删除掉标识符或者变量,这个时候可能会发生一些bug。

old.proto
message Car{
   optional int32  far = 1 [default = 2];
 }
  now.proto
 message Car{
   // optional int32 far =1  [default = 2];
 }
  future.proto
  message Car {
    optional int32 far = 1 [default = 10];
 }
 正确姿势 
 message Car   {
    reserved "far";
    reserved 1,2,3 to10 ; 
 }

使用reserved 修饰后未来在使用这个标识符就会编译报错,所以它的语义表示被保留的我们不能随意使用

field_number

field_number是用Varint编码的,所以它的值越大越占用编码,最小为1,最大为2^29 - 1 ,其中19000 - 19999 是不能定义的,因为这个是保留给编译器的

Message可定义的类型

标量

这里写图片描述

枚举

1.它的成员必须是 int32位,它是采用Varint编码的,所以负数很浪费空间。
2.第二个它可以在一个Message内定义,在另一个message内使用,方法是Message.enum
3.它支持不同的常量赋同一个值,但是前提是设置了allow_alias选项

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
枚举的reserved使用

这个跟 reserved的含义一样。如果我们删除某个枚举成员,那么未来的开发者可能重用这个枚举成员,这个时候可能就造成 bug , 所以我们可以使用reserved 修饰。

enum Old_Color{
  BLACK = 0;
  RED = 1;
}
enum Now_Color{
  RED = 1;
}
enum New_Color{
  RED = 1;
  BLACK = 10;
}

当未来的开发者写的新代码读到老的 protoc文件就会出现 bug…

Message 本身

  Message本身也可以定义成一个内部变量

Message teacher{
}
Message school{
optional teacher Peter = 1;
}
生成的代码大概是这样的:
class school{
private:
  class teacher Peter_;
}
嵌套定义

嵌套定义的语法就很像c++ 的内部类

message SearchResponse { 
    message Result {
       required string url = 1;
       optional string title = 2;
       repeated string snippets = 3;
    }
}
message SomeOtherMessage {
   optional SearchRespons.Result result = 1;
}
message Zoo {
    message Cat {
    }
}
message Home {
     message Cat {
     }
}

oneof 类型

它很类似于Union类型,其次需要注意的点:

  1. 需要注意的点是它内部定义的成员不能有关键字修饰
  2. 当多次设置该内容的时候,会自动清理前面已经设定过
  3. 可以用 CHECK宏查看是否设置值
  4. 在c++中可以使用swap 来交换同一 message 类型内定义的 Oneof变量的内容
    Message 对于oneof 类型生成的代码,在生成的时候它首先定义一个相应Union的成员变量,然后通过Message的成员函数来设置这个成员变量Union的内容。
message SampleMessage{
oneof test_oneof{
string name = 1;
int id = 2;
}
}
main.cc 
SampleMessage message;
SampleMessage message2;
message.set_name("name");
message.set_id(1);// message 的test_oneof内容变为int 1 
message2.swap(&message); 

Map 类型

map < key_type,value_type> dict = 1;
  1. key_type 只能是标量类型(但是浮点数和 bytes除外)
  2. value_type 可以是任何类型除了map自己
  3. 不能被关键字修饰
  4. 在c++中map的使用方法跟std::map很像,甚至可以做转化
message Test{
map<int32,int32> weight = 1;
}


main.cc :
Test mymessage;
std::map<int32_t,int32_t> standard_map(mymessage.weight.begin(),mymessage.weight.end());  //深拷贝
google::protobuf::Map<int32,int32> StdmaptoProtoMap(standard_map.begin(),standard_map.end());  

5 Map生成的c++代码

template<typename Key, typename T> {
class Map {
  // Member types
  typedef Key key_type;
  typedef T mapped_type;
  typedef MapPair< Key, T > value_type;

  // Iterators
  iterator begin();
  const_iterator begin() const;
  const_iterator cbegin() const;
  iterator end();
  const_iterator end() const;
  const_iterator cend() const;
  // Capacity
  int size() const;
  bool empty() const;

  // Element access
  T& operator[](const Key& key);
  const T& at(const Key& key) const;
  T& at(const Key& key);

  // Lookup
  int count(const Key& key) const;
  const_iterator find(const Key& key) const;
  iterator find(const Key& key);

  // Modifiers
  pair<iterator, bool> insert(const value_type& value);
  template<class InputIt>
  void insert(InputIt first, InputIt last);
  size_type erase(const Key& Key);
  iterator erase(const_iterator pos);
  iterator erase(const_iterator first, const_iterator last);
  void clear();

  // Copy
  Map(const Map& other);
  Map& operator=(const Map& other);
}

packages

packages使用场景

一般用于当import其他 .proto文件的时候。这个时候可能import的文件跟当前文件有message 的命名冲突的时候,protoc编译会报错说有重复命名message,这个时候如果我们在其中一个.proto文件前加 package xxx.xxx 即可。被添加package的文件内的message是这样生成代码的:

test.protc
package foo.bar;
message Open{}
test.pb.h :
namespace foo{
namespace bar{
class Open;
}
}

可见把包的命字都当做成了一个个作用域

package 的用法

test1.protoc
syntax="proto2";
package foo.bar;
message Open{
}

test2.protoc
syntax="proto2";
message Foo{
foo.bar.Open object = 1;
}

package 的一些规则

  1. 一个.proto文件只能定义一个package
  2. 子包的定义方法是import父包文件,然后再这样定义 package parent.child
test.proto: 
package Parent;
test2.proto:
import "test.proto";
package Parent.child;
package 中的变量命名解析

  它遵循于c++的命名解析即局部作用域绑定,也就是不同作用域出现相同的标识符,它绑定离它最近的。那么 packages 的命名解析(绑定)的时候也是这个原理,它先从子包查找相应的变量名绑定,如果找到了就不在父包查找。

test.proto :
package foo;
message Open{
}
test.proto :
package foo.bar;
message Open{
}
message Close{
.foo.bar.Open object = 1;
}

Extensions

它是messages里面的占符位。一般是如下格式,使用extensions的时候要求这些数字不能是原有message已定义的field_number。它一般用于当一个 .proto文件 import 一个其他.proto文件的时候,可以扩展其他.proto文件中的message里extensions 声明的那些field_number(field)。所以我们 extensions 的这些数字都是用于其他第三方.proto文件的扩展。不直接扩展是为了防止在扩展的时候,写了重复的field_number而发生错误。

A.proto
message Foo{
  extensions 100 to 199;
 }
B.proto 
extend Foo {
optional int32 bar = 199;
}
#include"B.pb.h"
int main()
{
    Foo object;
    object.SetExtension(bar,20);
    return 0;
}

使用extension生成的c++代码和普通的message生成的c++代码使用方法有些不同

如上面定义
Foo object;
object.SetExtension(bar,20);
嵌套Extensions

扩展也可以在类内部进行扩展,但是使用的时候得加上相应类的作用域

test.proto :
message Foo{
  extensions 100 to 199;
 }

test2.proto:
message Baz {
   extend Foo{
   optional int 32 bar = 126;
  }
}
Foo foo;
foo.SetExtension(Baz::bar , 15); 

options

它分为 file-level , message-level ,field-level 。file-level 只能放入文件顶部,不可放入message/service/enum等内。

file-level

optimize_for 它可被设置以下值
1. SPEED : 默认这个选项,它生成高度优化的代码
2. CODE_SIZE : 生成的代码量少于SPEED,但速度慢于SPEED,适用场景一个app有大量的.proto文件,但它不要求所有的.proto文件都生成最快的代码,就可以设置这个选项以此节约硬盘
3. LITE_RUNTIME : 选择这个选项,protobuf 的编译器将会使用 libprotobuf-lite.so ,它比原本的动态库更小而且它的速度跟原本SPEED下的一样,但是它会缺少一些属性像 reflection(反射) 和 descriptors 。

option optimize_for = CODE_SIZE;

4.cc_enable_arenas 这个只针对c++,表示是否打开protobuf的空间配置器选项

message-level

1.message_set_wire_format 一般用户用不到,它只是Google内部为了兼容旧的格式(MessageSet)

field-level
  1. packed : 它用于一个被repeated修饰的数字变量,当打开这个选项的时候,编码的效率会更高。但是在 2.3.0之前不安全,在2.3.0后很安全。
repeated int32 a = 1;[packed = true]
import

这个是导入其他.proto文件的时候使用的语法

syntax="proto2";
import "other.proto";
message Teacher {

} 
import public

有的时候被导入的文件如果想换一个位置,那么我们需要一个一个更新使用到这个被导入文件的文件

//client.proto
syntax = "proto2";
import "old.proto";
.....
cp  old.proto  New_Dir/old.proto

如上面这个列子,我们可以看出是需要一个一个更新使用到这个文件的文件的。
那 import public 的作用就出来了,它就是专门为了来处理这个场景的。import public 可以用来转发到新的位置

//client.proto
syntax = "proto2";
import "old.proto";
.....
cp  old.proto  New_Dir/old.proto
//old.proto
import public "New_Dir/old.proto";
....
如果 protobuf 想向后兼容需要做的一些事情
  1. 别修改已存在的列变量
  2. 新添加新的列变量,可以用 optional or repeated 修饰。
  3. 非required 字段可以用reservered 删除
  4. int32, uint32, int64, uint64, 和 bool 可以互相兼容,这个就像c++的类型转换
  5. sint32 , sint64 可以互相转换但是和其他类型不能转换
proto2 与 proto3
  1. proto2 与 proto3 的message 类型可以互相import 来使用,但是 proto2版本的枚举类型不能使用在proto3中
  2. proto3只有俩个关键字修饰符,singular 与 repeated(默认packed = true 在proto3中) 。singular 和 optional 是一样的,required 被取消了。
  3. proto3中定义的列变量不是必须得使用关键字修饰符去修饰,这个在proto2中是必须的
  4. proto3中关键字修饰符只是附加的属性,如果想组成一个复杂的类型,可以使用它
  5. 5.

猜你喜欢

转载自blog.csdn.net/sdoyuxuan/article/details/80915375