资讯专栏INFORMATION COLUMN

Google Protobuf 编解码

Eric / 879人阅读

摘要:优点在谷歌内部长期使用产品成熟度高跨语言支持多种语言包括和编码后的消息更小更加有利于存储和传输编解码的性能非常高支持不同协议版本的前向兼容支持定义可选和必选字段的入门是一个灵活高效结构化的数据序列化框架相比与等传统的序列化工具它更小更快更简

Google Protobuf 优点:

在谷歌内部长期使用, 产品成熟度高.

跨语言、支持多种语言, 包括 C++、Java 和 Python.

编码后的消息更小, 更加有利于存储和传输.

编解码的性能非常高.

支持不同协议版本的前向兼容.

支持定义可选和必选字段.

Protobuf 的入门

Protobuf 是一个灵活、高效、结构化的数据序列化框架, 相比与 xml 等传统的序列化工具, 它更小、更快、更简单.

Protobuf 支持数据结构化一次可以到处使用, 甚至跨语言使用, 通过代码生成工具可以自动生成不同语言版本的源代码, 甚至可以在使用不同版本的数据结构进程间进行数据传递, 实现数据结构前向兼容.

定义消息类型
syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

该文件的第一行指定使用 proto3 语法, 如果不写的话表示 proto2.

分配字段编号

string query = 1; 1 就是字段编号, 字段号主要用来标识二进制格式字段的. 1 到 15 字段号占一个字节. 16 到 2047 字段号需要两个字节.

我们将对象转换为报文的时候, 是按照字段编号进行报文封装的; 我们接收到数据之后框架会帮我们按照字段号进行赋值.

不能使用数字19000到19999, 因为它们是为 Google Protobuf 保留的.

字段类型对应
.proto Type Notes C++ Type Java Type
double double double
float float float
int32 使用可变长度编码, 对负数编码效率低下
如果您的字段可能有负值, 则使用sint32代替.
int32 int
int64 使用可变长度编码, 对负数编码效率低下
如果您的字段可能有负值, 则使用sint64代替.
int64 long
uint32 使用可变长度编码 uint32 int
uint64 使用可变长度编码 uint64 long
sint32 使用可变长度编码
有符号的int值这些编码比常规int32更有效地编码负数
uint32 int
sint64 使用可变长度编码
有符号的int值这些编码比常规int64更有效地编码负数
int64 long
fixed32 四个字节, 如果值通常大于2的28次方, 则比uint32更有效 uint32 int
fixed64 四个字节, 如果值通常大于2的56次方, 则比uint64更有效 uint64 long
sfixed32 四个字节 int32 int
sfixed64 四个字节 int64 long
bool bool boolean
string 字符串必须始终包含UTF-8编码或7位ASCII文本 string String
bytes 字符串必须始终包含UTF-8编码或7位ASCII文本 string ByteString
默认值

对于字符串, 默认值是空字符串.

对于字节, 默认值为空字节.

对于bool, 默认值为false.

对于数字类型, 默认值为零.

对于枚举, 默认值是第一个定义的枚举值, 必须为0.

还请注意, 如果消息字段设置为默认值, 则该值将不会序列化.
允许嵌套

Protocol Buffers 定义 message 允许嵌套组合成更加复杂的消息

message SearchResponse {
  repeated Result results = 1;
}

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

更多的例子:

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

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}
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;
    }
  }
}
导入定义

可以在文件的顶部添加一个import语句:

import "myproject/other_protos.proto";
未知字段

未知字段就是解析器无法识别的字段. 例如, 当服务端使用新消息发送数据, 客户端使用旧消息解析数据, 那么这些新字段将成为旧消息中的未知字段.

在3.5和更高版本中, 未知字段在解析过程中被保留, 并包含在序列化中输出.

Map 类型

repeated 类型可以用来表示数组, Map 类型则可以用来表示字典.

map map_field = N;

map projects = 3;

key_type 可以是任何 int 或者 string 类型(任何的标量类型, 具体可以见上面标量类型对应表格, 但是要除去 floatdoublebytes)

枚举值也不能作为 key.

key_type 可以是除去 map 以外的任何类型.

需要特别注意的是:

map 是不能用 repeated 修饰的.

map 迭代顺序的是不确定的, 所以你不能确定 map 是一个有序的.

.proto 生成文本格式时, map 按 key 排序. 数字的 key 按数字排序.

从数组中解析或合并时, 如果有重复的 key, 则使用所看到的最后一个 key(覆盖原则).从文本格式解析映射时, 如果有重复的 key, 解析可能会失败.

Protocol Buffer 虽然不支持 map 类型的数组, 但是可以转换一下, 用以下思路实现 maps 数组:

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

上述写法和 map 数组是完全等价的,所以用 repeated 巧妙的实现了 maps 数组的需求.

Protocol Buffer 命名规范

message 采用驼峰命名法. message 首字母大写开头. 字段名采用下划线分隔法命名.

message SongServerRequest {
  required string song_name = 1;
}

枚举类型采用驼峰命名法. 枚举类型首字母大写开头. 每个枚举值全部大写, 并且采用下划线分隔法命名.

enum Foo {
  FIRST_VALUE = 0;
  SECOND_VALUE = 1;
}

每个枚举值用分号结束, 不是逗号.

服务名和方法名都采用驼峰命名法. 并且首字母都大写开头.

service FooService {
  rpc GetSomething(FooRequest) returns (FooResponse);
}
常用方法

getDefaultInstance(): 返回单例实例, 它与 newBuilder().build() 实例相同
getDescriptor(): 返回类型的描述符. 包括具有哪些字段以及类型. 这可以与 Message 的反射方法一起使用, 例如getField().
parseFrom(...): 返回反序列化后的 Message. 注意不会抛出 UninitializedMessageExceptionInvalidProtocolBufferException 异常.
Message.Builder: 中的 mergeFrom() 放会将数据解析为此类型的消息, 并进行消息合并.
newBuilder(): 创建一个新的构建器.

Any

Any类型允许包装任意的message类型:

import "google/protobuf/any.proto";

message Response {
    google.protobuf.Any data = 1;
}
总结
message SubscribeReq {
  int32 subReqID = 1;
  string userName = 2;
  string productName = 3;
  string address = 4;
}

可以通过 pack()unpack()(方法名在不同的语言中可能不同)方法装箱/拆箱,以下是Java的例子:

People people = People.newBuilder().setName("proto").setAge(1).build();
// protoc编译后生成的message类
Response r = Response.newBuilder().setData(Any.pack(people)).build();
// 使用Response包装people

System.out.println(r.getData().getTypeUrl());
// type.googleapis.com/example.protobuf.people.People
System.out.println(r.getData().unpack(People.class).getName());
// proto
Oneof

如果你有一些字段同时最多只有一个能被设置, 可以使用 oneof 关键字来实现, 任何一个字段被设置, 其它字段会自动被清空(被设为默认值):

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}
默认值

比如我们创建了上面的消息类型, 我们在代码中设置 builder.setSubReqID(0); 为 0, 零是数值类型的默认值; 所以我们会看到序列化后的数据中, 没有对此字段进行序列化.

byte[] arry = builder.build().toByteArray();

arry 长度为 0. 对于字段类型是 string 类型的也是一样的; 也就是说显示赋值默认值也不会对其进行序列化.

保留字段
message SubscribeReq {
  
  reserved 2;
  
  int32 subReqID = 1;
  string userName = 2;
  string productName = 3;
  string address = 4;
}

顾名思义, 就是此字段会被保留可能在以后会使用此字段. 使用关键字 reserved 表示我要保留字段数 2.

上面代码我们在生成 Java 文件的时候会出现 ubscribeReqPeoro.proto: Field "userName" uses reserved number 2 错误信息, 所以我们需要将 string userName = 2; 注释, 或者删除.

保留后我们无法对其设置或序列化和反序列化.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72796.html

相关文章

  • 解码技术

    摘要:基于提供的对象输入输出流和可以直接把对象作为可存储的字节数组写入文件也可以传输到网络上序列化的目的主要有两个网络传输对象持久化当进行远程跨进程服务调用时需要把被传输的对象编码为字节数组或者对象而当远程服务读取到对象或字节数组时需要将其解码为 基于 Java 提供的对象输入/输出流 ObjectInputStream 和 ObjectOutputStream, 可以直接把 Java 对象...

    fobnn 评论0 收藏0
  • Netty(三) 什么是 TCP 拆、粘包?如何解决?

    摘要:是一个面向字节流的协议,它是性质是流式的,所以它并没有分段。可基于分隔符解决。编解码的主要目的就是为了可以编码成字节流用于在网络中传输持久化存储。 showImg(https://segmentfault.com/img/remote/1460000015895049); 前言 记得前段时间我们生产上的一个网关出现了故障。 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送...

    YanceyOfficial 评论0 收藏0
  • RPC框架原理及从零实现系列博客(一):思路篇

    摘要:等之所以支持跨语言,是因为他们自己定义了一套结构化数据存储格式,如的,用于编解码对象,作为各个语言通信的中间协议。 前段时间觉得自己一直用别人的框架,站在巨人的肩膀上,也该自己造造轮子了 一时兴起 就着手写起了RPC框架 这里写了系列博客拿给大家分享下 这篇是开篇的思路篇 项目最终的代码放在了我的github上https://github.com/wephone/Me... 欢迎sta...

    tracy 评论0 收藏0
  • netty 基于 protobuf 协议 实现 websocket 版本的简易客服系统

    摘要:结构作为服务端作为序列化数据的协议前端通讯演示地址服务端实现启动类长连接示例主线程组从线程组请求的解码和编码把多个消息转换为一个单一的或是,原因是解码器会在每个消息中生成多个消息对象主要用于处理大数据流,比如一个大小的文件如果你直接传输肯定 结构 netty 作为服务端 protobuf 作为序列化数据的协议 websocket 前端通讯 演示 GitHub 地址 showImg(...

    wua_wua2012 评论0 收藏0
  • netty 基于 protobuf 协议 实现 websocket 版本的简易客服系统

    摘要:结构作为服务端作为序列化数据的协议前端通讯演示地址服务端实现启动类长连接示例主线程组从线程组请求的解码和编码把多个消息转换为一个单一的或是,原因是解码器会在每个消息中生成多个消息对象主要用于处理大数据流,比如一个大小的文件如果你直接传输肯定 结构 netty 作为服务端 protobuf 作为序列化数据的协议 websocket 前端通讯 演示 GitHub 地址 showImg(...

    Shihira 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<