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 TypeNotesC++ TypeJava Typedouble doubledoublefloat floatfloatint32使用可变长度编码, 对负数编码效率低下如果您的字段可能有负值, 则使用sint32代替.int32intint64使用可变长度编码, 对负数编码效率低下如果您的字段可能有负值, 则使用sint64代替.int64longuint32使用可变长度编码uint32intuint64使用可变长度编码uint64 longsint32使用可变长度编码有符号的int值这些编码比常规int32更有效地编码负数uint32intsint64使用可变长度编码有符号的int值这些编码比常规int64更有效地编码负数int64longfixed32四个字节, 如果值通常大于2的28次方, 则比uint32更有效uint32intfixed64四个字节, 如果值通常大于2的56次方, 则比uint64更有效uint64longsfixed32四个字节int32intsfixed64四个字节int64longbool boolbooleanstring字符串必须始终包含UTF-8编码或7位ASCII文本stringStringbytes字符串必须始终包含UTF-8编码或7位ASCII文本stringByteString默认值对于字符串, 默认值是空字符串.对于字节, 默认值为空字节.对于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<key_type, value_type> map_field = N;map<string, Project> projects = 3;key_type 可以是任何 int 或者 string 类型(任何的标量类型, 具体可以见上面标量类型对应表格, 但是要除去 float、double 和 bytes)枚举值也不能作为 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);}总结message SubscribeReq { int32 subReqID = 1; string userName = 2; string productName = 3; string address = 4;}默认值比如我们创建了上面的消息类型, 我们在代码中设置 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; 注释, 或者删除.保留后我们无法对其设置或序列化和反序列化.