protocl buffers
Protocol Buffers
protocol buffers are a language-neutral, platform-neutral extensible mechnism for serializing structed data.
协议缓冲文件是google定义的类似于xml的简洁高效的结构化数据存储格式,可以用于结构数据的序列化(串行化).方便文件的存储与网络传输 .XML是众所周知的空间密集型,编码/解码可能会对应用程序造成巨大的性能损失,protobuff 更小,快速,简单,只需要定义相应的数据结构,然后可以生成相应的源文件.通过建立.proto
文件描述符,协议缓冲符编译器会创建一个类,该类使用有效的二进制格式实现数据有效的自动编码和解码.生成的类为组成协议缓冲区字段提供getter()
setter()
,并将协议缓冲区的读写细节作为一个单元来处理.
协议缓冲区格式支持随着时间的推移而扩展的想法,即代码仍然可以读取使用旧格式编码的数据
是一种序列化对象框架(或者说是编解码框架),其他功能相似的有Java自带的序列化、Facebook的Thrift和JBoss Marshalling等;
通过proto文件定义结构化数据,其他功能相似的比如XML、JSON等;
自带代码生成器,支持多种语言;
Base syntax
1.定义一个消息类型
在此例中,所有的数据都是标量,也可以采用其他的复杂类型作为数据域,包含枚举型(enumerations), 其他message类型.
明确字段(field)的编号:
在message的定义中,每一项都被赋予特定的唯一编号,并且在protobuff文件一旦使用后该编号即不可改变.注意域的编号一般要保留一定空间用于未来可能的扩充,并且不可以使用已经标记为reserved
的编号.
1-15:采用一个字节编码,用于标识使用频率较高的message元素
16-2047:采用两个字节编码
最小的编号可以为1,最大可以为2**29-1
编号19000到19999被作为协议缓冲区的实现,所以不可以作为字段编号
消息字段类型以是以下两种之一:
singular:一个格式正确(well-formed)的message可以至多包含一个该字段
repeated: 该字段可以被重复任意次
保留字段:(researed fields)
考虑到升级时的问题,如果升级时完全移除一个字段,之后的用户可能会重用该字段的编号,这与字段编号的唯一性不符(如果用户调用旧版本的proto文件时会造成十分严重的错误)
而避免这种错误的方式可以是将需要删除的字段号或者名称定义为reserved
,之后的用户如果尝试再使用此字段时,编译器会报错
注意不可以将保留的 names 和 field number 放在同一个reserved 下
2.标量值的类型
You can find out more about how these types are encoded when you serialize your message in Protocol Buffer Encoding.
默认值
在解析消息时,如果编码的消息不包含特定的singular元素,则解析对象中的对应的字段将设置为该字段的默认值:
string:默认为""
bytes: 空
bool: false
numeric type: 0
enums: 默认值是要保证第一个定义的值一定要是0
message: 依赖于具体语言
对于标量消息字段,一旦解析了消息,就无法判断字段是否明确设置为默认值(例如布尔值是否设置为false)或者根本没有设置:需要牢记定义的消息类型。例如,如果不希望该行为在默认情况下也发生,请将其设置为false时打开某些行为的布尔值。如果标量消息字段被设置为其默认值,则该值不会在连线上序列化
3.Enumerations 枚举类型
为了在定义消息类型时,将某个字段的域明确在某些预定义的值.可以使用枚举类型
保留值:
注意,枚举类型中的值也不可以造成未来的隐患,所以需要保留字,将需要删除的值进行reserved标识
4.其他消息类型的引入
对于同一个protobuff文件中的message可以直接引入并在其他message的定义中使用,对于其他文中的定义可以使用import语句进行引入
通常情况下.只可以从直接import的proto文件中使用定义.但是,有时需要将.proto文件移至新位置,这时不需要直接移动文件并且在这次变换中更新所有的调用点;可以在原来的位置上放置一个虚拟的.proto
文件,然后使用import public
调用,将所有的导入转移到新位置.
proto编译器使用-I/- proto_path
标志在命令行中的指定目录中搜索导入的文件,如果没有给出标志,将在调用编译器的目录中查找,原则上讲,应该将 --proto_path 设置为项目的根目录,并且所有的导入均使用完全限定名称.
5. Nested Types 嵌套类型
可以再一个message中定义嵌套的message 并且可以进行多层嵌套
如果需要调用内部的嵌套子类,可以使用parent.child
进行调用
当然,嵌套层数可以任意
Other syntax
1. Any 类型嵌套
any message 类型允许使用消息嵌套而不需要借助其.proto
定义,any 包含一个二进制序列化的messge 并作为bytes
类型,与一个url一起作为message type一个全局的统一定位符
使用前需要导入"google/protobuf/any.proto" 文件
默认的类型URL为: type.googleapis.com/packagename.messagename
2. oneof
如果有一个包含多字段的消息,并且最多只能同时设置一个字段,则可以使用此功能强制执行此操作可以节省内存
oneof字段与普通字段的一样除了所有被定义在oneof
中的字段共享一段内存资源,并且每次仅可以设置一个字段.设置oneof中的一个成员会自动清除其他成员.
现在可以添加oneof字段,可以添加到任何类型,但是不可以使用repeated
关键字
c++实例:
设置oneof字段会自动清除其他字段,因此只有最后的设置有效
如果解析器在连线上遇到同一成员,则只有最后一个成员在解析消息时有用
以下代码会导致memory crash因为sub_message已经被删除
如果你用oneofs交换两个消息,每个消息都会以另一个case为结尾:在下面的例子中,msg1将有 一个sub_message,而msg2将有一个名字
3. Maps
可以再protobuf中使用 map<>
来创建两个相关联的二元组(字典),包含了key_type
,value_type
分别指定键值和实际值的类型,键值可以是string 或者数字,但是不可以是其他的map,比如说enum
map 字段不可以
repeated
线格式排序和map迭代排序的map值是不能确定的,所以不能认为map类型的每一项会有一个特定的顺序
当为.proto生成文本格式时,map按照键值排序
从线路解析或者合并时,如果有重复的键值,则使用的是所看到的最后一个键值;从文本格式解析映射时,如果有重复的键值,可能解析失败.
如果对于同一个map只提供键值而不提供值,则根据使用的语言来自动填补如:java c++ python会填补默认值
组织构造
Packages
在.proto文件中可以指定一个包名,指明message定义所属的组织结构,然后可应通过 packagename.message_name 进行调用.
在不同的编程语言的表现:
c++: 产生的类将会被包装在
namespace
本例中的Open将会被保证在foo::bar
java: 作为java文件的包
python: 被忽略,因为python的模块的组织架构根据文件系统
定义服务
可以用proto文件定义一RPC系统,可以再 .proto文件中定义RPC服务的所有接口,然后可以生成所需语言的RPC脚本.
JOSN 转换
//TODO
OPTION
可以再.proto文件中声明option语句进行可用性设定,这些设定包含
file:即可以作用在最高层次的命名域,
message:在一个message的定义的作用域起作用,
field:在一个字段声明中起作用
java_package
:指定生成java类的包,如果未指明,默认会在package
语句定义的路径,若不生成java文件,该语句则不起作用java_mutable_files
: 导致在包级别定义顶级消息,枚举和服务,而不是在以.proto文件命名的外部类中定义java_outer_classname
: 要生成的最外面的Java类(以及因此的文件名)的类名称。如果没有在.proto文件中指定明确的java_outer_classname,则通过将.proto文件名称转换为camel-case[驼峰模式](因此foo_bar.proto变为FooBar.java)来构造类名称。如果不生成Java代码,则此选项不起作用。optimize_for
: 可以对java c++起作用有三个选项SPEED
(default): 编译器将生成用于序列化,解析和执行消息类型的其他常见操作的代码。这段代码是高度优化的。CODE_SIZE
: 编译器将生成最少的类,并将依靠共享的基于反射的代码来实现序列化,解析和各种其他操作。生成的代码因此比SPEED要小得多,但操作会更慢。类仍将实现与SPEED模式中完全相同的公共API。此模式在包含大量.proto文件的应用程序中非常有用,并且不需要所有这些文件都快速地闪烁LITE_RUNTIME
: 编译器将生成仅取决于“lite”运行时库(libprotobuf-lite而不是libprotobuf)的类。 lite运行时比整个库小得多(大约小一个数量级),但省略了描述符和反射等特定功能。这对于在移动电话等受限平台上运行的应用程序特别有用。编译器仍然会像在SPEED模式下那样生成所有方法的快速实现。生成的类将仅实现每种语言的MessageLite接口,该接口仅提供完整的Message接口的一部分方法。
deprecated
:如果设置为true,则表示该字段已弃用且不应被新代码使用。在大多数语言中,这没有 实际影响。 在Java中,这变成了@Deprecated注释。将来,其他语言特定的代码生成器可能会在字段的访问器上生成弃用注释,这会在编译试图使用该字段的代码时导致发出警告。如果该字段未被任何人使用,并且您想阻止新用户使用该字段,请考虑用保留语句替换该字段声明。
产生相应的代码
Last updated