Protocol Buffer详解

本文介绍了一种高效的数据序列化机制——ProtocolBuffer。它支持多种平台和语言,并可通过自定义数据结构提高序列化效率。文章详细解释了消息定义、数值类型、默认值等核心概念。
部署运行你感兴趣的模型镜像

protobuf简介

Protocol Buffer是一种支持多平台、多语言、可扩展的的数据序列化机制,相较于XML来说,protobuf更小更快更简单,支持自定义的数据结构,用protobu编译器生成特定语言的源代码,如C++、Java、Python,目前protoBuf对主流的编程语言都提供了支持,非常方便的进行序列化和反序列化。

一、Message定义

这里简单的给出一个例子,是一个搜索请求的message格式

syntax = "proto3”;//指定proto版本,默认为proto2,而且改行必须是第一行

//message包含多个种类的fields
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

上述例子中fields的种类都是数值型的(string和int32),当然也可以指定更加复杂的fields,比如枚举类型enum,或者是嵌套的message类型

1、分配field编号

上述例子中每个field都被分配了一个编号,这个编号是这个field的唯一标识,其中需要注意的是,标识1-15在编码的时候只占用一个字节,16-2047占用两个字节,所以为了进一步优化我们的程序,对于那种经常出现的element,建议使用1-15来作为他们的唯一标识,对那些不被经常使用的element,建议使用16-2047,编号的范围是1-2^29(19000-19999是系统预留的,不要使用)

2、field类型

message当中的field类型包含以下两种(proto3):

(1)singular

(2)repeated:该类型的field可以在message中重复使用(类似于数组),他们的顺序会被保存,通过索引进行检索,数值类型的repeated默认使用packed编码方式

3、多message结构

在一个proto文件中可以定义多个protobuf

4、reserved field类型

在开发过程中可能会涉及到对proto文件中message各个fields的修改,可能是更新、删除某个field及其表示,这样可能会导致调用的服务失败。其中一个防止这种问题的方式是,确保你要删除的field的标识(或是名字)是reserved,具体protobuf的编译器会决定未来这个field表示能否被使用

//数字标识和命名不能在同一条语句中混合声明

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

二、数值类型

具体proto类型对应生成类中的类型可以参考官方文档

https://developers.google.com/protocol-buffers/docs/proto3#scalar
​developers.google.com/protocol-buffers/docs/proto3#scalar

三、默认值

对于string和byte类型,默认值为空;对于bool类型,默认值是false;对于数值类型,默认值是0;对于枚举类型,默认值是第一个枚举值,默认为0;对于message类型,默认值由编程语言决定;对于repeated field,默认值为空

四、枚举类型

当采用枚举类型的之后,枚举中的值都是预先定义好的,对于上述例子,我们可以再额外增加一个枚举类型corpus,具体如下

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

通常枚举类型的第一个值初始化为0,而且在message中使用枚举类型,必须要给定一个为0的值,而且这个为0的值应该为第一个元素

也可以给不同的元素以相同的alias,但是需要指定option allow_alias = true;具体如下

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

初次之外枚举类型不仅可以定义在message内部,也可以定义在message外部,而且在不同message中可以重用enum。

在更改枚举类型field时,为保证系统运行正常,同样可以指定reserved 数字标识和命名

五、使用其他message类型

1、同文件引用

具体如下:

message SearchResponse {
  repeated Result results = 1;
}
message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

2、不同文件引用

引用其他proto文件中定义好的message类型,具体如下

import “myproject/other_protos.proto”;
有时我们会对引用的proto文件进行更改,比如将其内容移动到另外一个地方,这样我们就需要对调用方import路径进行更改,当调用方非常多的时候,这种方法是非常低效的,protobuf提供一种机制,我们可以在原有位置提供一个新位置proto文件的“副本”,通过使用import public表示来实现,具体可以参考如下例子

// new.proto
// All definitions are moved here

// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";

// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

这时编译器就会在某些固定目录下查询import的proto文件(具体在命令行编译的时候,由-I/—proto_path指定),如果上述路径找不到,编译器会在调用路径进行查找。通常将—proto_path设置为项目的根目录,然后import的时候使用完整的路径名

3、使用proto2中的message类型

proto2中的枚举类型无法直接使用

六、嵌套类型

具体如下

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

在parent message之外调用嵌套的message可以用如下方式:SearchResponse.Result,嵌套结构可以更加复杂,具体如下:


```java
message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

# 七、更新message类型
当现有的message已经无法满足现有业务需要,你需要更新你的message类型以支持更复杂的业务,这就涉及到向后兼容的问题了,为保证已有服务不受影响,需要遵守以下的一些规定

1、不要更改已经存在的fields的数字标识

2、如果添加新的field,利用旧代码序列化得到的message可以使用新的代码进行解析,你需要记住各个元素的默认值。新代码创建的field同样可以由旧代码进行加解析

3、field可以被删除,但是需要保证其对应的数字标识不再被使用,你可以通过加前缀的方式来重新使用这个field name,或者指定数字标识为reserved来避免这种情况

4、int32、int64、uint32、uint64、bool这些类型都是互相兼容的,并不会影响前向、后向兼容性

5、sint32和sint64之间是互相兼容的,但是和其他数字类型是不兼容的

6、string和bytes是互相兼容的,只要使用的是UTF-8编码

7、如果byte包含message的编码版本,则嵌套的message和bytes兼容

8、flexed32兼容sfixed32,fixed64,sfixed64

9、enum兼容 int32, uint32, int64, and uint64。对于这个值在转化时,不同语言的客户端处理方式会有所不同


您可能感兴趣的与本文相关的镜像

Python3.11

Python3.11

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值