2019独角兽企业重金招聘Python工程师标准>>>
Protobuf3笔记
文件后缀
定义Proto的文件应以.proto为后缀。
语法版本
Proto文件的首行应指定语法版本:
syntax = "proto3"; // "proto2"
定义字段
在消息中,每个字段以下列方式定义:
type filed "=" tag ";"
如:
message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
标签数字
出于性能考虑,在消息中,常用字段的标签最好小于15。这样可以降低消息序列化后的体积。
多消息
一个Proto文件中,可以定义多个消息。如:
message SearchRequest { // ... } message SearchResponse { // ... }
注释
Proto使用C风格的注释。
编译输出
对于C++,protobuf为每个消息生成一个类,为每个proto文件生成一个.h头文件和一个.cc源代码文件。
对Java,protobuf为每个消息生成一个类和一个Builder类。
对于Go,protobuf为每个消息生成一个.pb.go源代码文件和一个结构体。
对Objective-C,protobuf为每个proto文件生成一个pbobjc.h头文件和一个pbobjc.m文件,为每个消息生成一个类。
常用标量类型
bool string bytes int32 int64 uint32 uint64 double float
默认值
类型 | 默认值 |
bool | false |
bytes | [] |
numeric | 0 |
enum | FirstElement |
Field | null |
枚举类型
message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 4; } message EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; }
引用其他消息类型
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; repeated string snippets = 3; }
引用其他proto文件
import "myproject/other_protos.proto";
protoc参数
-I/–proto_path 指定proto文件目录。
内置类型
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; } message SomeOtherMessage { SearchResponse.Result result = 1; }
更新消息
字段标签
不得改动已存在的字段标签。新生成的代码可以解析旧消息。新增的字段会被设置为默认值。旧代码也可以解析新消息,新增的字段会被忽略。
删除字段
字段可以被删除。但已使用过的标签不得重复使用。
bytes和string
当字符串是UTF-8编码时,bytes和string可以兼容。
Any消息
Any消息是一个占位符,表示任意类型。使用Any消息时,需要引用google/protobuf/any.proto。
import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2; } NetworkErrorDetails details = ...; ErrorStatus status; status.add_details()->PackFrom(details); ErrorStatus status = ...; for (const Any& detail : status.details()){ if (detail.Is()){ NetworkErrorDetails network_error; detail.UnpackTo(&network_error); ... } }
OneOf消息
OneOf提供了一种类似C语言union结构体的机制,来降低存储体积。
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } } SampleMessage message; message.set_name("Joe"); assert(message.has_name());
Map消息
Map可以定义一组键值对。
mapprojects = 3;
声明包。
package foo.bar;
定义服务
service SearchService { rpc Search (SearchRequest) returns (SearchResponse); }
在Go中使用protobuf
示例proto
package tutorial; option java_package = "com.example.tutorial"; option java_outer_classname = "AddressBookProtos"; message Person { required string name = 1; required int32 id = 2; string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; }
生成的代码
调用方法
import ( "github.com/golang/protobuf/proto" pb "path/to/generated/pb/file" ) // ... p := &pb.Person { Id: 1234, Name: "John Doe", Email: "[email protected]", Phones: []*pb.Person_PhoneNumber { {Number: "555-4321", Type: pb.Person_HOME}, }, } out, err := proto.Marshal(p) q := &pb.Person{} err := proto.Unmarshal(in, q) proto.MessageType(name string) reflect.Type proto.Clone(pb Message) Message
Any消息在Go中的用法
Any消息可以表示任意类型的消息。在Go中使用Any消息的示例如下:
import "github.com/golang/protobuf/ptypes" import "github.com/golang/protobuf/proto" import "path/to/generated/pb" // message Foo { // google.protobuf.Any bar = 1; // } // message Bar { // uint32 x = 1; // } bar := &pb.Bar{ X: 1, } body, err := ptypes.MarshalAny(bar) if err != nil { log.Fatal(err) } foo := &pb.Foo{ Bar: body, }
注意事项
-
在使用proto.Unmarshal(buf, message)对消息进行反序列化时,缓冲区buf的长度应当等于消息的实际长度。否则会报告如下错误消息:
proto: protocol.Message: illegal tag 0 (wire type 0)
在Java中使用protobuf
示例proto
package tutorial; option java_package = "com.example.tutorial"; option java_outer_classname = "AddressBookProtos"; message Person { required string name = 1; required int32 id = 2; string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; }
生成的代码
// Person public boolean hasName(); public String getName(); public boolean hasId(); public int getId(); public boolean hasEmail(); public String getEmail(); public ListgetPhoneList(); public int getPhoneCount(); public PhoneNumber getPhone(int index); // Person.Builder public boolean hasName(); public java.lang.String getName(); public Builder setName(String value); public Builder clearName(); public List getPhoneList(); public int getPhoneCount(); public PhoneNumber getPhone(int index); public Builder setPhone(int index, PhoneNumber value); public Builder addPhone(PhoneNumber value); public Builder addAllPhone(iterable value); public Builder clearPhone();
调用方法
Person john = Person.newBuilder() .setId(1234) .setName("John") .addPhone( Person.PhoneNumber.newBuilder() .setNumber("555-4321") .setType(Person.PhoneType.HOME)) .build(); john.writeTo(outputStream); Person walliam = Person.parseForm(inputStream);
使用Gradle生成protobuf
google提供了生成protobuf的gradle插件,名称是com.google.protobuf。在使用时,需要在build.gradle中加入:
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1' } } apply plugin: 'java' apply plugin: 'com.google.protobuf' protobuf { generatedFilesBaseDir = '$projectDir/src' protoc { // use local protoc // path = '/usr/local/bin/protoc' // or, get from repo artifact = 'com.google.protobuf:protoc:3.3.0' } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.4.0' } } generateProtoTasks { all()*.plugins { grpc {} } } } repositories { mavenCentral() } dependencies { compile 'com.google.protobuf:protobuf-java:3.3.1' compile 'io.grpc:grpc-netty:1.4.0' compile 'io.grpc:grpc-protobuf:1.4.0' compile 'io.grpc:grpc-stub:1.4.0' /* for Android client, use compile 'io.grpc:grpc-okhttp:1.4.0' compile 'io.grpc:grpc-protobuf-lite:1.4.0' compile 'io.grpc:grpc-stub:1.4.0' */ } sourceSets { main { proto { srcDir 'src/main/protobuf' include '**/*.proto' } } }
然后执行:
gradle build
如果只需要生成java源代码文件,可以执行:
gradle generateProto
参考资料
- https://developers.google.com/protocol-buffers/docs/javatutorial
- http://www.cnblogs.com/resentment/p/6715124.html
- https://github.com/grpc/grpc-java/tree/master/compiler
- https://github.com/grpc/grpc-java
修订记录
- 2016年05月03日 建立文档。
- 2016年08月11日 修订。
- 2017年07月28日 改为rst格式。
- 2017年07月28日 增加gradle部分。
- 2017年08月04日 修订例子。
- 2017年08月07日 增加在Go中使用Any消息的例子。
- 2018年08月06日 修正错别字;修改日期格式。