1.前言
最近因为学习需要,了解了当前一些基于C++20协程的Rpc实现方案,但现有的框架基本上代码量都比较大、框架比较复杂难懂、文档介绍较少。在一个Rpc调用过程中,涉及到多个协程的层层嵌套,影响了整体性能和可读性。因此,自己动手设计并实现了一个轻量级、简单、易于理解的Rpc实现方案。框架基于C++20无栈协程与protobuf,代码量较小但实现了Rpc的所有的基础功能,适合用来学习或以此为基础进行无栈协程的Rpc开发。该框架每一个Rpc调用只会创建一个协程,避免了层层创建,提高性能。框架所有代码均已开源,见cpp20coroutine-protobuf-rpc。
2.使用示例
话不多说,先看使用示例
2.1 rpc方法定义
根据pb的格式定义req和rsp协议、rpc服务EchoService、Echo和RelayEcho两个rpc方法,定义如下。使用Google的protoc即生成对应的给客户端调用的stub和给服务器调用的service代码。
message EchoRequest {
optional string msg = 1;
}
message EchoResponse {
optional string msg = 2;
}
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse);
rpc RelayEcho(EchoRequest) returns (EchoResponse);
}
2.2 客户端rpc调用示例:
// 协程方法定义
RpcCoro CallMeathod() {
// 1.获取 or 创建rpc channel
RpcChannel *channel = s_ConnMgr->GetRpcChannel("127.0.0.1", 6688);
// 2.创建rpc req、rsp
echo::EchoRequest req;
req.set_msg("Hello, Echo.");
echo::EchoResponse rsp;
// 3.获取当前协程handle并构造proto controller
MyController cntl(co_await GetHandleAwaiter{});
// 4.构造proto rpc stub并调用其生成的rpc方法Echo,然后挂起协程等待返回
echo::EchoService_Stub stub(channel);
stub.Echo(cntl, req, rsp, nullptr);
co_await std::suspend_always{};
// 5.收到rsp包或出现超时等错误时,协程被唤醒,从proto controller中可以读取rpc调用状态,如果调用成功,此时可以读取到协程返回值rsp,打印协程返回结果
LLOG("Recv Echo Rsp, status:%s, rsp:%s", cntl.Failed() ? cntl.ErrorText().c_str() : "success",
rsp.msg().c_str());
// 协程内可再进行其他rpc调用并获取返回...
stub.RelayEcho(cntl, req, rsp, nullptr);
co_await std::suspend_always{};
LLOG("Recv RelayEcho Rsp, status:%s, rsp:%s", cntl.Failed() ? cntl.ErrorText().c_str() : "success",
rsp.msg().c_str());
co_return;
}
代码的详细流程及介绍如上所示,在第4步的调用protobuf生成的stub.Echo方法完成rpc的调用,内部rpc的调用过程中不会再产生额外的协程,stub.Echo方法只是完成数据包发送并记录上下文信息即返回了。返回后在co_await std::suspend_always{}处挂起等待,直到收到rpc返回包或者出现超时等错误时才会被唤醒,整个Rpc调用流程只会创建这一个协程。
2.3 服务端使用示例:
// 继承并实现pb生成的echo::EchoService,重写rpc方法Echo、RelayEcho
class MyEchoService : public echo::EchoService {
public:
void Echo(::google::protobuf::RpcController *controller,
const ::echo::EchoRequest *request, ::echo::EchoResponse *response,
::google::protobuf::Closure *done) override;
void RelayEcho(::google::protobuf::RpcController *controller,
const ::echo::EchoRequest *request, ::echo::EchoResponse *response,
::google::protobuf::Closure *done) override;
};
// 在Echo方法中,根据rpc请求rsp填写response,然后return即可,框架内部会完成回包逻辑
void MyEchoService::Echo(::google::protobuf::RpcController *controller,
const ::echo::EchoRequest *request,
::echo::EchoResponse *response,
::google::protobuf::Closure *done) {
// 填充协程返回值
response->set_msg(std::string(" Echo >>>>>>> ") + request->msg());
done->Run();
}
// RelayEcho方法中,涉及到内嵌rpc调用,所以创建新协程InnerCallMeathod来完成
void MyEchoService::RelayEcho(::google::protobuf::RpcController *controller,
const ::echo::EchoRequest *req,
::echo::EchoResponse *rsp,
::google::protobuf::Closure *done) {
LLOG(nullptr, nullptr, LLBC_LogLevel::Trace, "RECEIVED MSG: %s",
req->msg().c_str());
InnerCallMeathod(controller, req, rsp, done);
}
// InnerCallMeathod协程
RpcCoro InnerCallMeathod(::google::protobuf::RpcController *controller,
const ::echo::EchoRequest *req,
::echo::EchoResponse *rsp,
::google::protobuf::Closure *done) {
// 初始化内部 rpc innerReq innerRsp
echo::EchoRequest innerReq;
innerReq.set_msg("Relay Call >>>>>>" + req->msg());
echo::EchoResponse innerRsp;
// 获取 rpc channel
RpcChannel *channel = s_ConnMgr->GetRpcChannel("127.0.0.1", 6699);
// 内部 rpc 调用后挂起
MyController cntl(co_await GetHandleAwaiter{});
cntl.SetPtrParam(handle);
echo::EchoService_Stub stub(channel);
stub.Echo(cntl, innerReq, innerRsp, nullptr);
co_await std::suspend_always{};
// 收到rsp包或出现超时等错误时,此协程被唤醒&#