protobuf解析(一)

328 阅读6分钟

1.syntax = "proto2";选择  proto2 或者 proto3 的语法,这里指定了 proto2 的语法

结构化数据被称为 Message

  1. package haomo.pos; 包名。在 C++ 里表现为 namespace,避免命名冲突

包名用于组织类和接口,以便更好地管理和组织代码。haomo 是包的顶级名称,pos 是子包的名称。

3.message 是消息体,它就是一个结构体/类.

字段格式:限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤]

包含name和type。

  • Scalar Value Types

fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多。因此一个属于时间效率高,一个属于空间效率高。根据项目的实际情况,一般选择fixed32,如果遇到对传输数据量要求比较苛刻的环境,可以选择int32.

Protobuf Type说明C++ TypeJava TypePython Type[2]Go Type
float固定4个字节floatfloatfloatfloat32
double固定8个字节doubledoublefloatfloat64
int32varint编码int32intintint32
uint32varint编码uint32intint/longuint32
uint64varint编码uint64longint/longuint64
sint32zigzag 和 varint编码int32intintint32
sint64zigzag 和 varint编码int64longint/longint64
fixed32固定4个字节uint32intintuint32
fixed64固定8个字节uint64longint/longuint64
sfixed32固定4个字节int32intintint32
sfixed64固定8个字节int64longint/longint64
bool固定一个字节boolbooleanboolbool
stringLenth-Delimiteduint64Stringstr/unicodestring
bytesLenth-DelimitedstringByteStringstr[]byte
bytesLenth-DelimitedstringByteStringstr[]byte
  • Specifying Field Types

type可以自定义(specify enumerations and composite types like other message types for your field.)

4.Assigning Field Numbers

为每个字段分配一个介于1 - 536,870,911的数字:

  1. 给定的数字必须在该消息的所有字段中是唯一的。
  2. 19,000 - 19,999被保留给Protocol Buffers实现。如果在消息中使用这些保留的字段号,Protocol Buffers编译器会报错。
  3. 你不能使用先前保留的字段号或已分配给扩展的字段号。
  4. 一旦消息类型在使用中,就不能更改字段号,因为它标识了消息的字段在消息的二进制格式中的位置。"更改"字段号相当于删除该字段,并创建一个具有相同类型但具有新编号的新字段。

最常设置的字段使用 1 - 15 的字段编号。较低的字段编号值在线格式中占用更少的空间。

5.Deleting Fields(谨慎)

不能删除required fields;

如果不再需要 non-required field, 并且所有客户端代码中的引用都已删除时,可删除该字段。必须保留该字段编号(reserve the deleted field number),防止以后再使用该字段;保留该字段名称(reserve the field name),确保JSON和FormatText实例的消息类型继续正确解析。

6.Reserved Fields

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

 (9 to 11 is the same as 9, 10, 11).

7.Adding More Message Types

建议在每个.proto文件里包含较少的Message Types(message, enum, and service))

8.Specifying Field Labels

  • optional字段有两种状态: 

1. 字段被设置,并且包含一个显式设置或从线路上解析的值。它将被序列化到线路上。 

2. 字段未设置,并且将返回默认值。它不会被序列化到线路上。

可选相对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。---因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。

  • repeated字段类型可以在一个良好格式的消息中重复零次或多次。重复值的顺序将被保留。可以看作是在传递一个数组的值。其实protobuf处理这个字段的时候,也是optional字段一样,另外加了一个count计数变量,用于标明这个字段有多少个,这样发送方发送的时候,同时发送了count计数变量和这个字段的起始地址,接收方在接受到数据之后,按照count来解析对应的数据即可。
  • map配对的键/值字段类型。特别的缩短版的repteated。

message 消息名 {

   map<key, value> name = n;

}

message Test6 {
  map<string, int32> g = 7;
}

等同于

message Test6 {
  message g_Entry {
    optional string key = 1;
    optional int32 value = 2;
  }
  repeated g_Entry g = 7;
}
  • required 字段尽量不要使用。被从 proto3 中删除。required 字段表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。

 

repeated 可以将重复的基本类型字段(任何不是字符串或字节的标量类型)声明为“packed”。在proto2中,可以使用字段选项[packed=true]来实现。在 proto3 里是default. 相反,它们被编码为一个包含每个元素连接在一起的单个 LEN 记录。要解码,元素从 LEN 记录中逐个解码,直到有效负载用完。下一个元素的开始由前一个元素的长度决定,而前一个元素的长度又取决于字段的类型。

message Test5 {
  repeated int32 f = 6 [packed=true];
}

只有基本数值类型的重复字段可以声明为“packed”。这些类型通常使用VARINT、I32或I64的编码类型。

重要提示: 如前所述,不应该在新字段中使用required关键字。必需字段的语义应该在应用层面实现。现有的必需字段应该被视为消息定义的永久、不可变的元素。将字段从required更改为optional几乎是不可能的。如果存在任何过时的读取器,它将认为没有该字段的消息是不完整的,并且可能会拒绝或丢弃这些消息。 另一个与required字段相关的问题出现在某人向枚举中添加一个值时。在这种情况下,未识别的枚举值被视为缺失,这也导致了必需值检查失败。