你真的了解缓冲区溢出吗
协议缓冲区是二进制协议,它是由Google开发并公开提供的。 第一个公开可用的版本是Protocol Buffers第2版。在撰写本文时,最新的实现是Protocol Buffers第3版。从来没有公开过版本1。
协议缓冲区是Google的语言无关,平台无关的可扩展机制,用于序列化结构化数据。 协议缓冲区当前支持Java,Python,Objective-C,C ++,C#,JS等生成的代码。 您定义要一次构造数据的方式,然后可以使用生成的特殊源代码轻松地使用各种语言在各种数据流中写入和读取结构化数据。
查看协议缓冲区的原因
您可能会问自己为什么我什至会考虑或考虑协议缓冲区,所以让我们看看一些原因。
数据量
实际上,Protobuf是二进制协议,因此具有一些非常好的特性。 默认情况下,与文本协议(例如JSON和XML)相比,它的吞吐量要高得多。 这不足为奇。 但是,例如,通过压缩使用文本协议发送时的数据,可以缩小协议缓冲区和JSON之间的吞吐量差异,并且可以使用其他技巧。 如果做到这一点,协议缓冲区在吞吐量上仍然会更好,但幅度不会那么大,所以我的建议是始终这样做,以防您使用文本格式传输数据。
CPU负载
需要考虑的另一重要事项是CPU的密集程度如何封送和拆封数据。 默认情况下,文本协议将占用更多的CPU资源,而且这不容易解决,例如吞吐量差异。
如果您认为这不是一件大事,请考虑一下您的用例。 您传输多少数据,以及谁在使用和产生您的数据。 如果您将服务器托管在某个云中并由CPU付款,那么这可能会影响您的账单。 移动设备不像我们的配备CPU的计算机那样强大,并且任何繁重的CPU工作量都会耗尽电池。 在这两种情况下,作为Protobuf的二进制协议都是获得更好性能和用户体验的不错选择。
用法
现在我们知道了为什么您应该了解更多关于协议缓冲区的原因,让我们看看它的外观以及如何使用它。
在协议缓冲区的情况下,所有内容都以模式或更精确的原型文件开头。 如果将此原始文件用作gRPC的一部分,则可以使用maven和gradle插件自动为我们生成类和类似类。 如果协议缓冲区将被单独使用,那么我们可以使用协议缓冲区编译器手动生成所有类和类似的类。
初始化原型文件
让我们看一下简单的原始文件
syntax = "proto3";
package xyz.itshark.blog.protobuf;
option java_package = "xyz.itshark.blog.protobuf.generated";
option java_multiple_files = true;
在这里,我们将语法定义为proto3 –协议缓冲区版本3。
在第二行中,我们说所有生成的类和其余类都应该是“ xyz.itshark.blog.protobuf”包的一部分。
接下来的两行添加了一些特定的行为,以防我们生成Java代码。 在使用Java代码的情况下,我们将使用不同的包“ xyz.itshark.blog.protobuf.generation”,也就是说我们希望生成多个文件,而不是包含包含在proto文件中定义的所有类的单个类。
第一条讯息
让我们将其添加到我们的原始文件中
message Example {
int32 id = 1;
string first_name = 2;
string last_name = 3;
}
这是protobuf中消息的简单示例。 名称为Example,由三个字段组成:id,first_name和last_name。 如果您知道C或C ++,这可能与struct非常相似。
第一个字段的类型为int32 ,名称为id 。 此外,结尾处有些奇怪,请在符号“ =”后跟数字1 。 所有字段都有此编号,只有不同的编号。 该数字是“标签”,它们用于在二进制消息中定位值,以便更快,更轻松地对数据进行序列化和反序列化。 重要的是要记住,一旦某些消息中使用了某个数字,就永远无法更改或重用它。 例如,如果一段时间后我们决定不想在消息中使用id,则可以将消息定义更改为如下所示
message Example {
string first_name = 2;
string last_name = 3;
Int32 new_id = 4;
}
但是,我们不能做这样的事情
message Example {
string not_valid = 1;
string first_name = 2;
string last_name = 3;
}
因为它将产生带有相同标签的不同消息,并且将使仍在使用旧原型文件的任何应用程序崩溃。
示例消息中的第二个字段为string类型,名称为first_name 。 如果您来自Java世界,您会注意到这不是驼峰式案例,不是Java中命名数据的正确方法。 这是在协议缓冲区中命名字段的标准方法。 协议缓冲区编译器将解析原始文件,并使用适用于手头语言的语法生成代码。 不要忘记protobuf是语言不可知的,并且用在不同的语言中,这些语言具有用于命名类,属性等的不同语法逻辑。
消息示例中的所有字段都是可选的。 更准确地说,在协议缓冲区版本3的情况下,所有字段都是可选的。 对于版本2,需要将字段显式标记为可选或强制性。 必填字段的问题是向后兼容以及它们永远无法删除的事实。 在分析了许多用例之后,从长远来看,决定强制字段造成的弊大于利,并且在版本3中,默认情况下所有字段都变为可选字段。
生成初始代码
您需要从以下位置https://github.com/google/protobuf/releases为您的系统下载protobuf编译器,将其安装在系统上后,只需运行此命令,即可生成生成的Java代码,准备在您的系统中使用项目
$ protoc example.proto --java_out=~\code\blog\
进阶讯息
现在让我们看一些更高级的消息
列表/数组
您可能会问自己的一件事是如何在消息中定义元素的列表或数组。 为此,您只需要在字段定义前面添加重复
message Advanced {
repeated string text = 4;
}
枚举
许多编程语言都有枚举,您也可以在协议缓冲区中定义它们。 要定义枚举,您只需要添加如下内容
enum Status {
SUCCESS = 0;
FAIL = 1;
RANDOM = 2;
}
之后,您可以将其用作任何其他类型
message Advanced {
repeated string text = 4;
Status my_status = 3;
}
留言中的留言
您也可以在其他消息中添加消息。 例如,在我们的示例中,我们定义了消息Example,因此我们可以在类型为Example的消息Advanced中添加字段
message Advanced {
repeated string text = 4;
Status my_status = 3;
Example message_example = 5;
}
您可以在消息内部定义消息,并以此方式使其仅在消息内部使用“私有”。
message Example2 {
message Internal {
string text = 1;
}
Internal valid_message = 1;
}
Message Invalid {
Internal can_not_be_used_here = 1;
}
此示例中的第一个消息(示例2)为有效消息,而第二个消息(无效)如其名称所示无效,因为内部消息只能在示例2内使用。
完整的原始文件
syntax = "proto3";
package xyz.itshark.blog.protobuf;
option java_package = "xyz.itshark.blog.protobuf.generated";
option java_multiple_files = true;
enum Status {
SUCCESS = 0;
FAIL = 1;
RANDOM = 2;
}
message Example {
int32 id = 1;
string first_name = 2;
string last_name = 3;
}
message Advanced {
repeated string text = 4;
Status my_status = 3;
Example message_example = 5;
}
message Example2 {
message Internal {
string text = 1;
}
Internal valid_message = 1;
}
在Java代码中使用协议缓冲区
准备好原始文件并生成Java代码后,下一步就是将其与代码结合使用。 如果您使用协议来生成代码,则只需将其复制到项目中的适当位置即可。 完成此操作后,您可以具有一些类似的代码来创建Example对象的实例
Example example = Example.newBuilder()
.setId(1)
.setFirstName("First")
.setLastName("Last")
.build();
如您所见,我们正在使用构建器来生成Protocol Buffers消息的实例。 要生成高级消息,我们可以使用类似以下的代码。
Advanced advanced = Advanced.newBuilder()
.setMyStatus(Status.RANDOM)
.addText("some text")
.addText("other text")
.setMessageExample(example)
.build();
这就是全部。
资源资源
- 该博客文章的完整代码示例https://github.com/vladimir-dejanovic/protocol-buffers-blog-entry
- 使用协议缓冲区的简单REST API https://github.com/vladimir-dejanovic/springboot-protobuffer-jersey-sample
- 协议缓冲区中的标量类型https://developers.google.com/protocol-buffers/docs/proto3#scalar
翻译自: https://www.javacodegeeks.com/2019/02/protocol-buffers-basic-stuff-need-know.html
你真的了解缓冲区溢出吗