基本语法
syntax = "proto3"; message MessageName { FieldType fieldName = fieldNumber; }
syntax指定使用的Protobuf版本。在这个示例中,使用的是Protobuf 3版本。
代码定义了一个Message,名称为MessageName,可以根据需要自定义Message的名称。
在Message中定义一个或者多个字段,FieldType是字段的数据类型,可以是基本类型(如int32、string、bool等)或其他定义的Message类型。fieldName是字段的名称,可以根据需求自定义。fieldNumber是字段的唯一标识号,用于在消息的二进制编码中标识字段。
例如:
syntax = "proto3"; message Email{ int32 id = 1; string name = 2; string emails = 3; }
标识号
在Protobuf中,每个字段都需要一个唯一的标识号(field number),用于在消息的二进制编码中标识该字段。标识号的作用是确保在消息的编解码过程中能够准确地识别每个字段。
以下是关于标识号的一些重要点:
标识号是一个正整数。
标识号的范围是1到2^29 - 1(Protobuf 3版本中是1到536,870,911)。
在同一个Message中,每个字段的标识号必须是唯一的。
标识号的选择是自由的,但应保持一致性和稳定性,避免频繁变更标识号。
在编码过程中,每个字段都会与其标识号一起被序列化到二进制数据中。接收方在解码时,通过读取二进制数据中的标识号来正确识别和解析每个字段的值。
[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的字段预留一些标识号。
包
在Protobuf
中,包package
用于对消息类型进行组织和命名空间管理。包的作用是确保消息类型的唯一性,并避免命名冲突。
示例:
syntax = "proto3"; package com.example.mypackage; message Email{ int32 id = 1; string name = 2; string emails = 3; }
通过使用包,可以在一个大型的Protobuf项目中组织消息类型,避免不同消息类型之间的命名冲突。同时,包还可以在生成的代码中生成对应的命名空间,以便在编程语言中进行访问和引用。
选项
java_package:单独为java定义包名字。
java_outer_classname:单独为java定义,protobuf编译器生成的类名。
syntax = "proto3"; package com.example.mypackage; option java_package = "com.example.mypackage"; option java_outer_classname = "Email"; message Email{ int32 id = 1; string name = 2; string emails = 3; }
字段类型
枚举、数组、Map
如下代码:
syntax = "proto3"; message Staff { int32 id = 1; string name = 2; string email = 3; // 枚举示例 enum PhoneType { MOBILE = 0; TELEPHONE = 1; } // 嵌套示例 message PhoneNumber { string number = 1; PhoneType type = 2; } // list示例 // 只要使用repeated标记类型定义,就表示数组类型。 repeated PhoneNumber phone = 4; message Map { string key = 1; int32 value = 2; } // map示例 Map map = 5; }
消息嵌套的几种写法
1、引用写法
// 定义Result消息 message Result { string url = 1; string title = 2; repeated string snippets = 3; // 字符串数组类型 } // 定义SearchResponse消息 message SearchResponse { // 引用上面定义的Result消息类型,作为results字段的类型 repeated Result results = 1; // repeated关键词标记,说明results字段是一个数组 }
2、嵌套写法
message SearchResponse { // 嵌套消息定义 message Result { string url = 1; string title = 2; repeated string snippets = 3; } // 引用嵌套的消息定义 repeated Result results = 1; }
3、import
写法
在开发一个项目的时候通常有很多消息定义,都写在一个proto
文件,不方便维护,通常会将消息定义写在不同的proto
文件中,在需要的时候可以通过import
导入其他proto
文件定义的消息。
result.proto
syntax = "proto3"; // Result消息定义 message Result { string url = 1; string title = 2; repeated string snippets = 3; // 字符串数组类型 }
search_response.proto
syntax = "proto3"; // 导入Result消息定义 import "result.proto"; // 定义SearchResponse消息 message SearchResponse { // 使用导入的Result消息 repeated Result results = 1; }
消息嵌套的调用
注意在调用消息时,要先调用嵌套消息:
消息定义:
syntax = "proto3"; message Staff { int32 id = 1; string name = 2; string email = 3; // 枚举示例 enum PhoneType { MOBILE = 0; TELEPHONE = 1; } // 嵌套示例 message PhoneNumber { string number = 1; PhoneType type = 2; } // list示例 // 只要使用repeated标记类型定义,就表示数组类型。 repeated PhoneNumber phone = 4; message Map { string key = 1; int32 value = 2; } // map示例 Map map = 5; }
调用写法:
public static void main(String[] args) { // 序列化 // 创建Result的Builder Staffbuf.Staff.Builder staffBuilder = Staffbuf.Staff.newBuilder(); staffBuilder.setId(1); staffBuilder.setName("张三丰"); //staffBuilder.setEmail("zhangsanfeng@wudang.org"); // 构建引用消息(import message)PhoneNumber List list = new ArrayList(); PhoneNumberBuf.PhoneNumber.Builder phoneBuilder = PhoneNumberBuf.PhoneNumber.newBuilder(); phoneBuilder.setType(PhoneNumberBuf.PhoneNumber.PhoneType.TELEPHONE); phoneBuilder.setNumber("010-12345678"); PhoneNumberBuf.PhoneNumber phoneNumber = phoneBuilder.build(); list.add(phoneNumber); phoneBuilder.clear(); phoneBuilder.setType(PhoneNumberBuf.PhoneNumber.PhoneType.MOBILE); phoneBuilder.setNumber("13912345678"); list.add(phoneBuilder.build()); staffBuilder.addAllPhone(list); // 完成staff的构建 Staffbuf.Staff zhangsanfeng = staffBuilder.build(); // 序列化,byte[]可以被写到磁盘文件,或者通过网络发送出去。 byte[] data = zhangsanfeng.toByteArray(); System.out.println("serialization end."); // 反序列化,byte[]可以读文件或者读取网络数据构建。 System.out.println("deserialization begin."); try { Staffbuf.Staff staff = Staffbuf.Staff.parseFrom(data); System.out.println(staff.getId()); System.out.println(staff.getName()); staff.getPhoneList().forEach(x -> System.out.println(x.toString())); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); } }