Protocol Buffer 中文入门使用文档

Protocol buffers 是 google 创造的一种系列化的数据格式,它语言无关,平台无关和可扩充的特点,类似 xml,json,但是更小,更快,更简单。您定义要一次构造数据的方式,然后可以使用生成的特殊源代码轻松地使用各种语言在各种数据流中写入和读取结构化数据。
 

定义一个消息


syntax = "proto3";

message Article {
  string title = 1;
  int32 visited = 2;
  int64 last_update = 3;
}
 
消息被保存为*.proto,再看看消息内容,proto3指定支持的版本,消息体 Article,包含字段,字段类型,还有一个字段数字,这个数字是从 1 开始,最大 229 - 1, or 536,870,911。数字 19000 ~ 19999 为保留数字,不可以使用。不然会报错:
Field numbers 19000 through 19999 are reserved for the protocol buffer library implementation.
 

数据类型映射


.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type C# Type PHP Type Dart Type
double   double double float float64 Float double float double
float   float float float float32 Float float float double
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32 int int int32 Fixnum or Bignum (as required) int integer int
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long int/long[3] int64 Bignum long integer/string[5] Int64
uint32 Uses variable-length encoding. uint32 int[1] int/long[3] uint32 Fixnum or Bignum (as required) uint integer int
uint64 Uses variable-length encoding. uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5] Int64
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int int int32 Fixnum or Bignum (as required) int integer int
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64 long int/long[3] int64 Bignum long integer/string[5] Int64
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int[1] int/long[3] uint32 Fixnum or Bignum (as required) uint integer int
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5] Int64
sfixed32 Always four bytes. int32 int int int32 Fixnum or Bignum (as required) int integer int
sfixed64 Always eight bytes. int64 long int/long[3] int64 Bignum long integer/string[5] Int64
bool   bool boolean bool bool TrueClass/FalseClass bool boolean bool
string A string must always contain UTF-8 encoded or 7-bit ASCII text, and cannot be longer than 232. string String str/unicode[4] string String (UTF-8) string string String
bytes May contain any arbitrary sequence of bytes no longer than 232. string ByteString str []byte String (ASCII-8BIT) ByteString string List<int>
你可以在上面找到你使用的开发语言的类型对应的 .proto 类型,然后编写你的消息文件 *.proto
 

类型默认值


  • 对于 string,默认值为空字符串。
  • 对于 bytes,默认值为空字节。
  • 对于 bool,默认值为false。
  • 对于 数字类型,默认值为零。
  • 对于 枚举,默认值为第一个定义的枚举值,必须为0。
  • 对于 消息字段,未设置该字段。 它的确切值取决于语言。 
 

枚举类型


enum PhoneType {
  WORK = 0;
  HOME = 1;
  MOBILE = 2;
}
枚举的第一个值必须是 0 值,如上 WORK = 0 
 

其他类型修饰符


message ArticleResponse {
  repeated Article results = 1;
}

message Article {
  string url = 1;
  string title = 2;
  repeated string images = 3;
}

repeated 即重复,就是生成一个类数组比如 go 语言 []string

 

import 定义


import "myapp_path/other.proto";

message Article {
//......
}
我们可以使用已经定义好的 .proto, 无需重复定义,在编译生成代码时,使用 -I 或者 --proto_path 来指定 import 导入文件的目录位置,比如 -I ../../
 

package


package user.link;
message Pager { ... }
您可以将可选的包说明符添加到.proto文件中,以防止协议消息类型之间的名称冲突。以上定义了一个包名 user.link,限定这个文件里面的消息都在此包下。
message Item {
  ...
  user.link.Pager pager = 1;
  ...
}
使用上面包定义的类型 Pager 
包说明符影响生成的代码的方式取决于您选择的语言:
  • 在 C ++中,生成的类包装在 C++ 名称空间中。例如,Pager 将位于名称空间 foo::bar 中。
  • 在 Java 中,除非您在 .proto 文件中明确提供选项 java_package,否则该包将用作 Java 包。
  • 在 Python 中,package 指令将被忽略,因为 Python 模块是根据其在文件系统中的位置进行组织的。
  • 在 Go 中,除非您在 .proto 文件中明确提供了 go_package 选项,否则该包将用作 Go 包名称。
  • 在 Ruby 中,生成的类包装在嵌套的 Ruby 名称空间中,转换为所需的 Ruby 大写样式(首字母大写;如果首字符不是字母,则使用PB_作为前缀)。例如,Pager 将位于命名空间 Foo::Bar 中。
  • 在 C# 中,除非转换为 .proto 文件中明确提供选项 csharp_namespace,否则在转换为 PascalCase 之后,该程序包将用作命名空间。例如,Pager 将位于命名空间 Foo.Bar 中。
 

定义服务


service BlogService {
  rpc ListMyBlog (BlogRequest) returns (BlogResponse);
}
如果我们需要使用消息在 RPC 系统中,上面就定义了一个 RPC 接口。比如当下比较流行的 gRPC,它就需要定义服务接口,这时生成代码时,需要加上 --go_out=plugins=grpc:.,要了解 gRPC,请阅读:手把手教你写一个 go gRPC 的入门教程
 

产生代码


protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto

上述为生成多语言的代码,在生产环境通常只需要生成一种两种,比如生成 go 代码
protoc -I../../../ -I=. --go_out=. ./helloworld/helloworld.proto

--proto_path=IMPORT_PATH 可以简单的写成 -I IMPORT_PATH,其中中间的空格可有可无,上述的语句中 -I../../../ 就没有空格,它能自动识别
../../../ 是搜索 import 语句导入的 .proto 文件所在的目录
可以多次使用 -I 来指向多个目录,它将按顺序查找,直到找到 import 的 .proto 文件为止,上述就用到了两个 -I
--go_out=. 表示生成的文件生成到当前目录
 

Protocol Buffer 例子


article.proto
syntax = "proto3";
package helloworld;

import "github.com/iissy/gogrpc/times/timestamp.proto";
option java_package = "cn.hrefs";
option java_outer_classname = "blog";
option csharp_namespace = "hrefs.cn.blog";

message Article {
  string title = 1;
  int32 visited = 2;
  times.Timestamp last_update = 3;
}

enum ArticleType {
  TECH = 0;
  ART = 1;
  HIS = 2;
}
timestamp.proto
syntax = "proto3";

package times;

option csharp_namespace = "hrefs.cn.blog";
option cc_enable_arenas = true;
option go_package = "github.com/iissy/gogrpc/times";
option java_package = "cn.hrefs.blog";
option java_outer_classname = "TimestampProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";

message Timestamp {
  int64 seconds = 1;
  int32 nanos = 2;
}
  1. article.proto 文件 import 了一个 timestamp.proto,注意 github.com/iissy/gogrpc/times/ 在这里是目录,它与 import_path (../../../)配合生成到预期的位置
  2. timestamp.proto 文件有个 go_package,在两个生成的包中,引用它的包将引用这个包路径。注:这个项目被初始化为 go mod init github.com/iissy/gogrpc
  3. 这样做目的就是将公共的 .proto 提出来,方便其他 .proto 调用,同时确保包的引用关系正确
执行生成 
protoc -I../../../ -I=. --go_out=../../../ ./times/timestamp.proto
protoc -I../../../ -I=. --go_out=. ./article/article.proto 
Posted by 何敏 on 2020-02-26 03:43:33