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可以定义一组键值对。
map projects = 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: "jdoe@example.com",
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 List getPhoneList();
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
参考资料
修订记录
2016年5月3日 建立文档。
2016年8月11日 修订。
2017年07月28日 改为rst格式。
2017年07月28日 增加gradle部分。
2017年08月04日 修订例子。
2017年08月07日 增加在Go中使用Any消息的例子。