protocol-buffers简述

Protocol Buffers(简称:ProtoBuf)是一种序列化数据结构的协议。对于透过管道(pipeline)或存储资料进行通信的程序开发上是很有用的。这个方法包含一个接口描述语言,描述一些数据结构,并提供程序工具根据这些描述产生代码,用于将这些数据结构产生或解析资料流。

简述就是:一种序列化的协议。可配合GRPC使用传输数据。

文档结构

  • protobuf版本
  • package包,用法同其他语言的包
  • import导入其他prot file,同py的import
  • message定义请求响应的数据结构
  • service定义grpc的请求方法
  • 注释,同C语言

数据类型

基本数据类型

.proto 说明 Python
double float
float float
int32 使用变长编码,对负数编码效率低, 如果你的变量可能是负数,可以使用sint32 int
int64 使用变长编码,对负数编码效率低,如果你的变量可能是负数,可以使用sint64 int/long
uint32 使用变长编码 int/long
uint64 使用变长编码 int/long
sint32 使用变长编码,带符号的int类型,对负数编码比int32高效 int
sint64 使用变长编码,带符号的int类型,对负数编码比int64高效 int/long
fixed32 4字节编码, 如果变量经常大于2^{28} 的话,会比uint32高效 int
fixed64 8字节编码, 如果变量经常大于2^{56} 的话,会比uint64高效 int/long
sfixed32 4字节编码 int
sfixed64 8字节编码 int/long
bool bool
string 必须包含utf-8编码或者7-bit ASCII text str
bytes 任意的字节序列 str

枚举类型

如:网络请求的状态码,注意从0开始定义。

1
2
3
4
5
6
enum HttpStatus {     
StatusOK = 0;
StatusBadRequest = 1;
StatusForbidden = 2;
StatusBadGateway = 3;
}

消息类型

等同于Java的类,如一个用户信息消息。

1
2
3
4
5
6
7
message User {
int32 userid = 1;
string username = 2;
string password = 3;
int32 age = 4;
bool deleted = 5;
}

字段编号

消息定义中的每个字段都有唯一的编号。这些字段编号用于以消息二进制格式标识字段,并且在使用消息类型后不应更改。

1到15范围内的字段编号需要一个字节进行编码,包括字段编号和字段类型16到2047范围内的字段编号占用两个字节。因此,您应该为非常频繁出现的消息元素保留数字1到15。请记住为将来可能添加的常用元素留出一些空间。

字段规则

  • singular:格式良好的消息可以包含该字段中的零个或一个(但不超过一个)。

  • repeated:此字段可以在格式良好的消息中重复任意次数(包括零)。将保留重复值的顺序。相当于Python的列表

  • reserved :指定删除该字段。

  • oneof :指定很多可选字段,最后最多只有一个值被设置。使用oneof可以节省内存。

    1
    2
    3
    4
    5
    6
    message SampleMessage {   
    oneof weight{
    double weight_kg = 10;
    double weight_lb = 11;
    }
    }

Map类型

map映射类型,同样类似java。

1
2
3
map< key_type, value_type> map_field = N ; 

map<string, Project> projects = 3 ;

书写实例

以一个用户信息请求为例,客户端会请求用户ID,服务端返回用户信息,如:姓名,密码,年龄等信息。除了必要的结构体,我们还实现了一个服务GetUserInfo获取用户信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// protobuf 版本
syntax="proto3";

// go 包名
option go_package = "grpc-sample/pb";
// protobuf文件 包名
package proto;

// 客户端请求结构体
message UserInfoRequest {
int32 userid = 1;
}
// 服务端响应结构体
message UserInfoResponse {
int32 userid = 1;
string username = 2;
string password = 3;
int32 age = 4;
bool deleted = 5;
}
// GRPC的方法
service ServerFunction {
rpc GetUserInfo (UserInfoRequest) returns (UserInfoResponse) {}
}
  • 该文件的第一行指定您正在使用proto3语法,默认proto2
  • 当运行Protocol Buffers的编译器时,编译器会以您选择的语言生成代码,您需要使用文件中描述的消息类型,包括获取和设置字段值、将消息序列化到输出流,并从输入流解析您的消息。

  • 对于Go,编译器会为.pb.go文件中的每种消息类型生成一个类型的文件。

更复杂的使用,参考官方文档

编译

在使用之前,我们需要安装编译器

协议编译器的调用方式如下:

1
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为例:

1
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto
  • IMPORT_PATH指定.proto解析import指令时查找文件的目录。如果省略,则使用当前目录。
  • --go_out生成 Go 代码DST_DIR。有关更多信息,请参阅Go 生成的代码参考
  • 必须提供一个或多个.proto文件作为输入。.proto可以一次指定多个文件。尽管文件是相对于当前目录命名的,但每个文件都必须驻留在IMPORT_PATHs之一中,以便编译器可以确定其规范名称。

结合GRPC

安装protoc-gen-go插件:

1
go get github.com/golang/protobuf/protoc-gen-go 

安装grpc:

1
go get google.golang.org/grpc

生成grpc的protobuf文件:

1
protoc --proto_path=proto proto/*.proto --go_out=plugins=grpc:pb

以上述的用户信息请求为例,生成的Go-grpc代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// protobuf 版本

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: test.proto

// protobuf文件 包名

package pb

import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)

const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

// 客户端请求结构体
type UserInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Userid int32 `protobuf:"varint,1,opt,name=userid,proto3" json:"userid,omitempty"`
}

func (x *UserInfoRequest) Reset() {
*x = UserInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_test_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}

func (x *UserInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}

func (*UserInfoRequest) ProtoMessage() {}

func (x *UserInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_test_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}

// Deprecated: Use UserInfoRequest.ProtoReflect.Descriptor instead.
func (*UserInfoRequest) Descriptor() ([]byte, []int) {
return file_test_proto_rawDescGZIP(), []int{0}
}

func (x *UserInfoRequest) GetUserid() int32 {
if x != nil {
return x.Userid
}
return 0
}

// 服务端响应结构体
type UserInfoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Userid int32 `protobuf:"varint,1,opt,name=userid,proto3" json:"userid,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
Age int32 `protobuf:"varint,4,opt,name=age,proto3" json:"age,omitempty"`
Deleted bool `protobuf:"varint,5,opt,name=deleted,proto3" json:"deleted,omitempty"`
}

func (x *UserInfoResponse) Reset() {
*x = UserInfoResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_test_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}

func (x *UserInfoResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}

func (*UserInfoResponse) ProtoMessage() {}

func (x *UserInfoResponse) ProtoReflect() protoreflect.Message {
mi := &file_test_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}

// Deprecated: Use UserInfoResponse.ProtoReflect.Descriptor instead.
func (*UserInfoResponse) Descriptor() ([]byte, []int) {
return file_test_proto_rawDescGZIP(), []int{1}
}

func (x *UserInfoResponse) GetUserid() int32 {
if x != nil {
return x.Userid
}
return 0
}

func (x *UserInfoResponse) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}

func (x *UserInfoResponse) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}

func (x *UserInfoResponse) GetAge() int32 {
if x != nil {
return x.Age
}
return 0
}

func (x *UserInfoResponse) GetDeleted() bool {
if x != nil {
return x.Deleted
}
return false
}

var File_test_proto protoreflect.FileDescriptor

var file_test_proto_rawDesc = []byte{
0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x29, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x69, 0x64, 0x22, 0x8e,
0x01, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75,
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75,
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05,
0x52, 0x03, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64,
0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x32,
0x52, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x40, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66,
0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x00, 0x42, 0x10, 0x5a, 0x0e, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x73, 0x61, 0x6d, 0x70,
0x6c, 0x65, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
file_test_proto_rawDescOnce sync.Once
file_test_proto_rawDescData = file_test_proto_rawDesc
)

func file_test_proto_rawDescGZIP() []byte {
file_test_proto_rawDescOnce.Do(func() {
file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
})
return file_test_proto_rawDescData
}

var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_test_proto_goTypes = []interface{}{
(*UserInfoRequest)(nil), // 0: proto.UserInfoRequest
(*UserInfoResponse)(nil), // 1: proto.UserInfoResponse
}
var file_test_proto_depIdxs = []int32{
0, // 0: proto.ServerFunction.GetUserInfo:input_type -> proto.UserInfoRequest
1, // 1: proto.ServerFunction.GetUserInfo:output_type -> proto.UserInfoResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}

func init() { file_test_proto_init() }
func file_test_proto_init() {
if File_test_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserInfoResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_test_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_test_proto_goTypes,
DependencyIndexes: file_test_proto_depIdxs,
MessageInfos: file_test_proto_msgTypes,
}.Build()
File_test_proto = out.File
file_test_proto_rawDesc = nil
file_test_proto_goTypes = nil
file_test_proto_depIdxs = nil
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6

// ServerFunctionClient is the client API for ServerFunction service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ServerFunctionClient interface {
GetUserInfo(ctx context.Context, in *UserInfoRequest, opts ...grpc.CallOption) (*UserInfoResponse, error)
}

type serverFunctionClient struct {
cc grpc.ClientConnInterface
}

func NewServerFunctionClient(cc grpc.ClientConnInterface) ServerFunctionClient {
return &serverFunctionClient{cc}
}

func (c *serverFunctionClient) GetUserInfo(ctx context.Context, in *UserInfoRequest, opts ...grpc.CallOption) (*UserInfoResponse, error) {
out := new(UserInfoResponse)
err := c.cc.Invoke(ctx, "/proto.ServerFunction/GetUserInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}

// ServerFunctionServer is the server API for ServerFunction service.
type ServerFunctionServer interface {
GetUserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error)
}

// UnimplementedServerFunctionServer can be embedded to have forward compatible implementations.
type UnimplementedServerFunctionServer struct {
}

func (*UnimplementedServerFunctionServer) GetUserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserInfo not implemented")
}

func RegisterServerFunctionServer(s *grpc.Server, srv ServerFunctionServer) {
s.RegisterService(&_ServerFunction_serviceDesc, srv)
}

func _ServerFunction_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UserInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ServerFunctionServer).GetUserInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.ServerFunction/GetUserInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ServerFunctionServer).GetUserInfo(ctx, req.(*UserInfoRequest))
}
return interceptor(ctx, in, info, handler)
}

var _ServerFunction_serviceDesc = grpc.ServiceDesc{
ServiceName: "proto.ServerFunction",
HandlerType: (*ServerFunctionServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetUserInfo",
Handler: _ServerFunction_GetUserInfo_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "test.proto",
}

去除冗长的实现,我们只需要关注于:type UserInfoResponse structtype UserInfoRequest struct 结构体。以及type ServerFunctionClient interface需要服务端实现并返回结果。

参考:

https://developers.google.com/protocol-buffers/docs/proto3

https://developers.google.com/protocol-buffers/docs/reference/go-generated#package