grpc笔记
什么是.proto文件
protocol buf是适应grpc框架的IDL(Interface descripition language)语言,使用其编写的文件后缀为.proto
如何编译.proto文件
- 使用protoc编译器
- protoc编译器是专门用来将proto文件转化为c++动态库的编译器,它将每个proto文件转化为后缀为.pb.h的头文件和后缀为.pb.cc的实现文件
- protoc编译器的常用选项包括:
- –proto_path : 指定proto文件的路径
- –cpp_out : 指定.h和.cc文件的生成路径
- 使用cmake生成protoc命令
- cmake提供了FindProtobuf模块,可以通过find_package命令查找Protobuf进行使用
- protobuf_generate_cpp : 该命令将自定义命令添加到处理.proto文件的C++代码中,语法如下:
protobuf_generate_cpp (<SRCS> <HDRS> [DESCRIPTORS <DESC>] [EXPORT_MACRO <MACRO>] [<ARGN>...])
SRCS
和HDRS
均为自定义变量,用于定义生成的源文件或头文件,ARGN
为要添加的.proto文件,使用示例如下:protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS foo.proto)
add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS})
protobuf_generate_cpp 和 protobuf_generate_python 函数以及 add_executable() 或 add_library() 调用只能在同一目录中正常工作。
- protobuf_generate : 官方缺少该命令的实际使用文档,但是已经有人自己向官方提交了请求
- protocol buffer内置的cpp code generator只能生成普通消息类型的头文件和源文件,需要另外的插件来处理grpc客户端和服务端的代码生成
.proto文件各类别(type)含义
首先感谢李文周先生对protocol buffer v3 官方文档的翻译
package
设置当前proto文件的命名空间
field(字段)
字段由字段标签/消息字段,字段类型,字段名和字段编号组成,使用message foo生成的set_foo()可以设置字段值,使用foo()可以获取字段值
字段标签
option
: 开启或者关闭某选项, 作用域限制在message类中repeated
:作为消息字段时表示生成类似于vector<foo>
的数组optional
: 作为消息字段时可以用于追踪是否设置该字段。一般情况下(即未指定optional时)若未设置字段或者将字段值设为默认值时编译器不会将其序列化,反序列化时虽然缺少字段但不会报错,在反序列化后即使没有设置该字段使用foo()仍会获得默认值,这样就会无法判断是否在另一端设置了该字段,这种情况叫做implicit presence,可以通过设置optional来解决问题,使用optional后,只要设置了该字段,无论是否为默认值,编译器都会序列化该字段,并且还会生成 has_foo() 函数来检查是否设置了该字段
字段类型
int32
:32位整数,使用变长编码(Varint),这种编码方式对于小的数字非常高效,但对于大的数字或负数则不然。因为Varint编码方式是将整数编码为一个或多个字节,每个字节的最高位用于表示是否还有更多的字节。如果最高位是0
,那么这就是最后一个(或唯一的)字节。如果最高位是1
,那么表示后面还有更多的字节。sint32
: 如果你的数据中包含很多负数,你可能会想使用sint32
类型,它对负数进行了优化。sint32
类型使用的是 ZigZag 编码,这种编码方式将负数映射到正整数,使得-1
映射为1
,-2
映射为3
,以此类推。这样,小的负数就可以用较少的字节表示,提高了编码效率。enum
: 不是基本数据类型,设置枚举类型,第一个值必须被设置为0
官方文档关于field的详细介绍: 🔗️
service
给出以下定义:protoc会生成Foo类并生成其内定义的虚函数,如代码所示,protoc会生成一个名为Bar的虚函数,它接受FooRequest类型的参数并返回FooResponse类型的结果1
2
3service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}除了set_foo, foo, has_foo等成员函数外,还会生成以下成员函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19virtual void Bar(RpcController* controller, const FooRequest* request, FooResponse* response, Closure* done);
```
RpcController类用来**调停**一个函数调用,它的主要目的是用来是提供一种方法来操作特定于RPC实现的设置并找出RPC级别的错误。比如其可以调用setfailed()方法,在客户端调用failed()方法
> failed()如果RPC调用失败,则返回true。失败的可能原因取决于RPC实现。failed()大多数情况下只能在客户端调用,并且在调用完成之前不得调用。setfailed()导致failed()在客户端返回true。reason将被合并到返回的消息中errorText()。如果您发现需要返回有关失败的机器可读信息,则应将其合并到响应协议缓冲区中,并且不应调用setFailed().
Foo类继承自Service类
## protobuf生成的c++代码
- 每个message字段都会生成字段名类并且该类继承自`google::protobuf::Message`父类,该父类不包括纯虚函数并且子类默认情况下会重写所有父类虚函数,该父类接口定义了以下函数:
- `bool ParseFromString(const string& data)` : 反序列化二进制字符串data,注意此时data是以引用形式传入
- `bool SerializeToString(string* output) const` : 从output指向的字符串中序列化字符串,注意此时output是以指针形式传入
- 同时子类还实现了以下函数:
- `const UnknownFieldSet& unknown_fields() const` : 返回解析字段时发现的未知字段集
- 对于以下字段定义:
```proto
optional string foo = 1;
required string foo = 1;
optional bytes foo = 1;
required bytes foo = 1;string* mutable_foo()
: 返回指向存储字段值的可变字符串对象的指针。由于foo()函数返回foo值的常量引用,所以使用该函数可以修改foo的值set_allocated_foo(string* fo)
: 将fo指向的字符串设为字段的值,并清除之前设置过的值,若fo为空,则相当于调用了clear_foo来取消设置字段对numeric,enum,embedded message 生成的函数类似
- 对于以下字段定义:生成的函数还包括:
1
repeated int32 foo = 1;
int foo_size()
: 返回repeated列表中元素的数量int32 foo(int index) const
: 返回列表中索引为index的元素的值void add_foo(int32 value)
: 类似于vector的push_back方法对string,enum,embedded message 生成的函数类似
rpc
refletcion
在gRPC中,Reflection是一种服务,它使远程客户端能够查询服务器以获取有关其公开服务的信息。这些信息包括服务名称、方法(包括其输入/输出类型)等。这使得客户端可以动态地(在运行时)理解和调用服务,而无需在编译时知道服务的确切定义。
metadata
元数据是与RPC相关的客户端和服务器之间提供信息的侧通道。gRPC元数据是一对键值对数据,与初始或最终的gRPC请求或响应一起发送。它用于提供有关调用的附加信息,如身份验证凭据、跟踪信息或自定义标头。gRPC元数据使用HTTP/2标头实现,键是ASCII字符串,值可以是ASCII字符串或二进制数据。元数据可用于身份验证、跟踪和自定义标头。