Protocol Buffers
proto3
Proto3是谷歌发布的一种数据序列化格式,用于在不同系统之间传输和存储结构化数据。
它是Protocol Buffers的第三个版本,比起proto2有很多改进。
特点
- 更简洁易读的语法。proto3语法更加简洁清晰,去掉了一些不常用的语法,使得用户更容易上手。
- 更强的向后兼容性。proto3遵循“向前兼容,向后不兼容”的原则,可以更好地应对数据结构和协议的变化,保证了数据的完整性和稳定性。
- 更好的API设计。proto3的API更加友好和直观,让用户更容易使用。
- 更好的标准化。proto3采用了更加严格的标准,使得不同厂商之间的数据交换更加顺畅。
应用场景
Proto3广泛应用于分布式系统中的数据交换和存储,可以用于网络通信、存储数据、RPC调用等。
它可以提高系统的性能和稳定性,减少数据传输时的带宽消耗和存储空间占用。
.proto 文件语法
.proto文件是定义数据结构的文件,用于生成应用程序代码。
使用.proto文件可以方便地定义消息类型、服务、RPC方法等。
基本语法结构
注意事项
message 的兼容
message语句定义了消息类型,格式为:
其中,MessageName为消息名称,field_type为字段类型,field_name为字段名,field_number为字段编号, 字段编号一旦被使用就不应该更改, 因为这些字段编号在二进制格式中标识了字段。
如果要废弃某个字段, 应该使用reserved语法保持向前的兼容, 注意不能在一个reserved语句中混合使用字段名和字段编号:
enum的默认值和别名
enum语句定义了枚举类型,格式为:
其中,EnumName为枚举类型名称,ENUM_VALUE为枚举值,0为枚举值的编号。
枚举的第一个常量映射到零:每个枚举定义都必须包含一个映射到零的常量作为其第一个元素。这是因为:
- 必须有一个零值,这样我们就可以使用 0 作为数字默认值
- 零值需要是第一个元素,以便与 第一个枚举值始终是默认值的proto2语义兼容。
可以通过将相同的值分配给不同的枚举常量来定义别名。为此,需要将allow_alias选项设置为true,否则协议编译器会在发现别名时生成一条警告消息。 尽管在反序列化期间所有别名值都有效,但在序列化时始终使用第一个值。
service的使用
service语句定义了服务类型,格式为:
其中,ServiceName为服务类型名称,MethodName为RPC方法名称,RequestType为请求消息类型,ResponseType为响应消息类型。
比如一个车辆相关的demo:
编译器编译后得到:
在java中实现这个service rpc接口:
整合websocket
proto 后端工程实践
.proto文件是定义数据结构的文件,使用.proto文件可以方便地定义消息类型、服务、RPC方法等。它具有简洁明了、易于扩展、跨语言支持等特点,是一种非常优秀的数据交换格式。
对于Java,编译器生成一个.java文件,其中包含每种消息类型的类,以及Builder用于创建消息类实例的特殊类。
maven模块文件结构
参考: https://github.com/protocolbuffers/protobuf/tree/main/java
核心proto
protobuf.js 实践
环境配置
protobuf在它的readme中提到了一个protobuf-javascript的实现,
但是我测试的时候它报了 3221225781
相关的异常, 根据grpc-web/issues/697的提示操作不起作用后放弃了官方推荐的实现
根据npm的数据, protobuf.js可能是一个很好的实现;
-
npm install protobufjs
注意: 不是protocbuf.js
-
npm install protobufjs-cli
提供了编译工具, 可以根据proto定义文件生成a.js, a.t.js等
-
在npm脚本中添加protoc, 用于编译.proto文件为es6语法, 生成d.ts文件
分析demo
demo中的{aInt: 152} 会被序列化为 08 98 01
第一个字节08的二进制0000 1000
为: field tag(前5位) 和 type(后3位)
故: tag = 00001, type = 000
由于aInt是数字类型: 后面的字节标识具体的值, 字符串类型还会有一个字节标识字符串长度
98 01 = 1001 1000, 0000 0001
每个字节的第一位(MSB, most significant bit)标识后面有没有了, 后7位才是具体的数据
数据= 001 1000, 000 0001 ≠ 152
看起来不对啊, 因为用的是小端顺序, 转换为大端顺序即可
数据其实= 000 0001, 001 1000 === 152
注意
- 注意proto3 默认全部都是optional的(包括oneOf本身也是), 没有required和optional之分了;
- oneOf 会带一个typeOf字段, 可以用typeOf字符串找到解码器;