以proto3为例,proto2语法:[看这里](Language Guide | Protocol Buffers | Google Developers)
基本语法
一个最简单的Message定义
syntax = "proto3";
package model;
option go_package = "protos/model";
message Student {
int64 id = 1;
string name = 2;
int32 age = 3;
}
- sytax语句必须在第一个非空非注释行,不写的话默认为
"proto2"
- 每个字段后的唯一不重复数字是在二进制格式中准确辨识各字段所需要的,该字段一经确认就不应该再修改, 这个数字最小为1
字段的规则
- 默认的规则是
singular
,不需要显式地指出,它相当于proto2的optional
+required
,即该字段可以不出现,或者仅出现一次 repeated
表明该字段可以重复出现容易次数,但是保证有序
- 在一个
.proto
文件中可以定义多个Message- 注释规则和C/Java相同
Message的更新
-
需要方法确保后续的更新不会导致旧Message的兼容性问题,这里引入的
reserved
关键词:message Student { reserved 1,2,3, 5 to 10; reserved "id", "name", "age"; }
-
这里告诉编译器保留
1,2,3,5,6,7,8,9,10
和"id", "name", "age"
这些值虽然现在被移除了,但是不可以被后续的更新再次指定,这确保了老的Message类型的兼容性 -
比如这里后面再更新添加一个
int64 grade = 1
编译器就会报错
数据类型
默认值
如果解析的时候某个字段没有被指定特定的值,则会赋予其特定类型的默认值
- 对于字符串类型,默认值为空串
""
- 对于bytes类型,在Go中就是字节数组,默认值为空字节数组
- 对于布尔值,默认值为
false
- 对于数字类型,默认值为0
- 对于枚举类型,默认值为第一个定义的枚举值
- 对于内嵌的Message类型,默认值却决于不同的语言实现
- 对于repeated的字段,默认值一般是一个空的列表
枚举类型的定义
message Student {
reserved 10 to 15;
int64 id = 1;
string name = 2;
int32 age = 3;
/**枚举定义**/
enum Gender {
MALE = 0;
FEMALE = 1;
OTHER = 2;
}
Gender gender = 4;
}
枚举的第一项必须也只能是0
若需要对同一个枚举变量定义相同的值,需要在前面加上如下的选项
enum Gender {
option allow_alias = true;
MALE = 0;
BOY = 0;
FEMALE = 1;
GIRL = 1;
OTHER = 2;
}
这里必须有option这一行,要不然编译器会报错
引入另一个Message Type
message Student {
reserved 10 to 15;
int64 id = 1;
string name = 2;
int32 age = 3;
enum Gender {
option allow_alias = true;
MALE = 0;
BOY = 0;
FEMALE = 1;
GIRL = 1;
OTHER = 2;
}
Gender gender = 4;
// 内嵌别的Message Type
repeated Pet pet = 5;
}
message Pet {
string name = 1;
enum PetType {
DOG = 0;
CAT = 1;
BUNNY = 2;
}
PetType type = 2;
}
在一个.proto文件中引入另一个.proto文件中的定义
这在Java里无效
内嵌的类型
message Pet {
string name = 1;
enum PetType {
DOG = 0;
CAT = 1;
BUNNY = 2;
}
PetType type = 2;
// 内嵌的栖息地类型
message Habitat {
string name = 1;
double area = 2;
}
Habitat habitat = 3;
}
这里内嵌类型并不局限于一层的内嵌,可以有多层内嵌
更新一个Message Type的注意事项
- 不要改变已有字段的数字
- 移除一个字段时要么指定reserved要么只是在这个字段前面加上
OBSOLETE
前缀以防出现兼容性问题
未知字段
所谓未知字段,就是更新后新添加的字段对于旧的MessageType而言是未知的
- 在proto2和proto3.5之后的版本未知字段在序列化过程中是保留的
- 在proto3 - proto3.5之间的版本,未知字段直接被忽略
Any类型的字段
可以在没有Message定义的情况下加入内嵌的自定义对象,类似于Java中实体包含了一个JSONObject类型的字段
使用前需要导入google/protobuf/any.proto
这个.proto
文件
import "google/protobuf/any.proto";
message Property {
int64 id = 1;
string name = 2;
google.protobuf.Any detail = 3;
}
Oneof
oneof
用来确保多个字段同时只有一个被赋值
message Property {
oneof oneof_id_name {
int64 id = 1;
string name = 2;
}
google.protobuf.Any detail = 3;
}
这样只能同时设置id或者name中的一个字段的值
Maps
message Property {
int64 id = 1;
string name = 2;
//Map定义
map<string, string> detail = 3;
}
- 键的类型不可以是浮点类型或者bytes类型
- 值的类型可以是任意类型除了map类型本身
包
package protos.model;
- 在C++中表现为命名空间的嵌套
- 在Java中指定
option java_package
以生成对应Java包 - 在Go中与Java类似,指定项为
option go_package
package 指定的包是防止多个.proto之间互相引入产生命名冲突问题,option xx_package 则是指定生成特定语言的包结构
定义RPC服务
在Go中的使用
安装配置
-
下载对应的
protoc
:[链接](Releases · protocolbuffers/protobuf (github.com)) -
解压将
bin
目录配置到环境变量 -
在当前模块下获取
protoc-gen-go
go get google.golang.org/protobuf/cmd/protoc-gen-go
包的声明
- 必须指定包名,可以在
.proto
文件中通过option go_package
指定,也可以通过命令行的方式指定
编译.proto
protoc --go_out=$OUT_DIR $INPUT_DIR_OR_FILE
// For example:
protoc --go_out=. protos/model/*.proto
使用生成的结构
-
这里有一个简单的
.proto
文件,位于protobuf
模块syntax = "proto3"; package model; option go_package = "protos/model"; message Student { int64 id = 1; string name = 2; int32 age = 3; enum Gender { option allow_alias = true; MALE = 0; BOY = 0; FEMALE = 1; GIRL = 1; OTHER = 2; } Gender gender = 4; message Phone { string model = 1; string brand = 2; } repeated Phone phones = 5; map<string,string> detail = 6; }
-
编译生成对应go文件后,引入对应的包
package main import ( pb "protobuf/protos/model" // 生成的文件 "google.golang.org/protobuf/proto" // protobuf的工具包 )
-
实例化一个定义的Message:Student
stu := pb.Student{ Id: 1001, Name: "Alice", Age: 22, Gender: pb.Student_FEMALE, Phones: []*pb.Student_Phone{ {Model: "iPhone12", Brand: "Apple.Inc"}, {Model: "Mate40", Brand: "Huawei"}, {Model: "S21", Brand: "Samsung"}, }, Detail: map[string]string{ "身高": "180CM", "体重": "75KG", "爱好": "无", }, }
-
将
stu
序列化为字节数组bufs, err := proto.Marshal(&stu)
-
从字节序列中读取Student
var stu1 pb.Student proto.Unmarshal(bufs, &stu1) fmt.Println(&stu1)
-
输出结果