以前对protobuf的使用仅限于将其作为C/S之间的数据传输的序列化工具,而对于基于protobuf实现rpc框架的使用较少了解,这两天通过一个开源的简单RPC框架对这部分的实现原理进行学习,并做相关记录,如存在理解有误之处,欢迎指正。
开源简单RPC框架:https://github.com/goyas/goya-rpc
使用Protobuf实现RPC框架大致可以分为以下几步:
- 1、编写proto文件定义服务,并使用Protoc工具生成RPC对象接口模型。
- 2、编写RPC服务处理主逻辑(继承框架代码中的接口类实现服务的主逻辑)
- 3、编写RPC服务框架
- 作用:其作用主要包括注册的RPC Service(第1步定义、第2步实现),接收RPC客户端请求交由对应的RPC Service处理,处理完毕后对客户端进行回应
- 4、编写RPC客户端代码
- 本质上就是实现RpcChannel和RpcController两个类(实际客户端调用逻辑proto工具已经帮我们在stub类中封装好了,Stub与RpcChannel和RpcController交互的逻辑在后面会有讲解)。
- RpcChannel本质上是用于客户端与服务端之间交互的一条通道,其负责实现客户端向服务端请求时的一些数据和网络处理。一般情况下这个类中都会至少包括序列化和发包到服务端这两个操作。
- RpcController这个类主要用于记录一次rpc调用的上下文,包括这次调用的方法以及执行结果等,在客户端和服务端都要用到这个类,最常见的就是服务端设置failed状态,客户端读取服务端执行的状态。
- 5、编写客户端使用样例。
本文将会对上述每一个步骤进行讲解,并会在分模块讲解完之后将所有的涉及到的模块进行串联起来进行理解,最后为了对实现的RPC框架有一个直观的理解,还会给出RPC客户端请求RPC服务端时候的整个调用链路。
本文中使用到的实例均来自开源goya-rpc库:https://github.com/goyas/goya-rpc
文章目录
1、Proto定义RPC服务
1.1 编写proto文件
package goya.rpc.echo;
option cc_generic_services = true;
message EchoRequest {
optional string message = 1;
}
message EchoResponse {
optional string message = 1;
}
service EchoServer {
rpc Echo(EchoRequest) returns(EchoResponse);
}
上面都是Proto基本的语法,这里就不再赘述,对这块不熟的可以参考我的另外一篇博客:https://blog.csdn.net/u014630623/article/details/105985944
1.2 使用protoc工具生成RPC对象接口模型
$ protoc --cpp_out=./ echo_service.proto
$ ls
echo_service.pb.cc echo_service.pb.h echo_service.proto
class EchoServer_Stub;
class EchoServer : public ::google::protobuf::Service {
// 声明为protected,不允许显式实例化
protected:
// This class should be treated as an abstract interface.
inline EchoServer() {
};
public:
virtual ~EchoServer();
typedef EchoServer_Stub Stub;
// 获取当前service的属性descriptor,service的属性中又存在其中的method属性
// 其中属性包括service->name(), service->method(), service->methmod(0)->name()的等
static const ::google::protobuf::ServiceDescriptor* descriptor();
// 与proto文件中对应的调用方法Echo
virtual void Echo(::google::protobuf::RpcController* controller,
const ::goya::rpc::echo::EchoRequest* request,
::goya::rpc::echo::EchoResponse* response,
::google::protobuf::Closure* done);
// implements Service ----------------------------------------------
// 获取当前Service的descriptor
const ::google::protobuf::ServiceDescriptor* GetDescriptor();
// 实际调用方法,对于每一个远程调用方法,实际上都是通过调用该函数实现,后面后介绍
void CallMethod(const ::google::protobuf::MethodDescriptor* method,
::google::protobuf::RpcController* controller,
const ::google::protobuf::Message* request,
::google::protobuf::Message* response,
::google::protobuf::Closure* done);
// 获取调用方法method的请求类型(如Echo方法的请求类型为EchoRequest)
const ::google::protobuf::Message& GetRequestPrototype(
const ::google::protobuf::MethodDescriptor* method) const;
// 获取调用方法method的应答类型(如Echo方法的请求类型为EchoResponse)
const ::google::protobuf::Message& GetResponsePrototype(
const ::google::protobuf::MethodDescriptor* method) const;
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(EchoServer);
};
class EchoServer_Stub : public EchoServer {
public:
EchoServer_Stub(::google::protobuf::RpcChannel* channel);
EchoServer_Stub(::google::protobuf::RpcChannel* channel,
::google::protobuf::Service::ChannelOwnership ownership);
~EchoServer_Stub();
inline ::google::protobuf::RpcChannel* channel() {
return channel_; }
// implements EchoServer ------------------------------------------
void Echo(::google::protobuf::RpcController* controller,
const ::goya::rpc::echo::EchoRequest* request,
::goya::rpc::echo::EchoResponse* response,
::google::protobuf::Closure* done);
private:
// 客户端与服务端交互的通道
::google::protobuf::RpcChannel* channel_;
bool owns_channel_;
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(EchoServer_Stub);
};
生成的echo_service.pb.h文件中,包含两个主要和RPC相关的类,分别是抽象接口EchoServer和该抽象接口的一个实现EchoServer_Stub。
其中EchoServer类是一个抽象接口,它继承::google::protobuf::Service类,不允许实例化(默认构造函数声明为protected),这个接口中生成了一个与proto文件中的方法Echo对应的函数。
EchoServer的作用有两个:
- 1、作为RPC服务端逻辑处理类(EchoServerImpl)的基类,服务逻辑处理类通过实现RPC定义的方法来处理相关逻辑,即实现Echo()函数。
- 2、作为EchoServer_Stub类(上面已实现)的基类,EchoServer_Stub是客户端访问RPC服务的一个包装类
EchoServer_Stub作用:
EchoServer_Stub通过封装RpcChannel,RpcChannel是客户端与服务通信的通道,由使用者自定义完成,将RPC调用本地化。
2、编写RPC服务逻辑
从1.2 中可以知道protobuf为我们生成了EchoServer抽象基类用于完成服务端处理的实际逻辑。所以我们通过编写EchoServer的实现类即可实现服务端方法的处理逻辑。