gRPC系列(二) 异步服务使用
相关文章:gRPC系列(一)安装和入门
异步的实现主要围绕的是grpc提供的队列:grpc::CompletionQueue
。
客户端代码
异步客户端代码相对于同步客户端来说并没有复杂多少,简单来说,就是同步rpc调用是调用完不会立刻返回,而是可以异步从队列中获得返回结果,实现调用的解耦,我们来看代码。
#include <iostream>
#include <grpcpp/completion_queue.h>
#include <grpcpp/security/credentials.h>
#include <grpcpp/support/async_unary_call.h>
#include <grpcpp/grpcpp.h>
#include "../protos/simple/simple.grpc.pb.h"
using grpc::Status;
using grpc::Channel;
using grpc::CompletionQueue;
using grpc::ClientContext;
using grpc::ClientAsyncResponseReader;
using Simple::EchoRequest;
using Simple::EchoResponse;
int main()
{
std::shared_ptr<Channel> chan = grpc::CreateChannel("localhost:12345",grpc::InsecureChannelCredentials());
std::unique_ptr<Simple::Server::Stub> stub(Simple::Server::NewStub(chan));
ClientContext context;
EchoRequest req;
req.set_msg("hello world!");
EchoResponse resp;
CompletionQueue cq;
// 完成rpc调用会将tag添加到cq队列中
std::unique_ptr<ClientAsyncResponseReader<EchoResponse>> rpc(stub->AsyncEcho(&context, req, &cq));
Status status;
// 第三个参数是一个上下文标签,用于帮我们标识这个请求
// grpc框架只会将其保存起来
rpc->Finish(&resp, &status, (void*)1);
void* got_tag;
bool ok = false;
// 从队列中获取,请求的标签以及状态
cq.Next(&got_tag, &ok);
if(ok && got_tag == (void*)1){
// check一下结果
std::cout << resp.msg() << std::endl;
}
return 0;
}
服务端代码
异步服务端不方便理解,可以参考:grpc使用记录(三)简单异步服务实例
这里主要涉及到的类包括grpc::ServerCompletionQueue
、grpc::ServerAsyncResponseWriter
、grpc::ServerAsyncResponseWriter
、Simple::Server::AsyncService
。
主要的处理流程是,
- 注册一个请求,传入上下文内容,包括context、req、resp以及你自己定义的上下文数据对象(可以作为tag)。
- 主循环消费队列,取出一个数据对象,调用处理逻辑。如果是还未处理,则进行rpc逻辑处理,然后用
grpc::ServerAsyncResponseWriter
异步写响应,并且分配一个新的数据对象(构造的时候会调用Proceed函数注册处理的请求);如果是以及处理好的请求,释放数据对象的空间。
可以这么说,cq的作用就是存放CallData,然后主循环不断读取CallData,然后根据其中的上下文信息,做出相应的处理。
RequestXXX()其实就是注册一个rpc请求,然后我们会传入CallData的地址参数,是为了再接受到指定rpc之后写入数据对象到消息队列中。
Finish()是异步写回响应,这里的传入this指针也是为了让其完成写回后讲this添加到消息队列中。
代码如下:
#include <grpcpp/security/server_credentials.h>
#include <grpcpp/support/async_unary_call.h>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <grpc/support/log.h>
#include <grpcpp/grpcpp.h>
#include "../protos/simple/simple.grpc.pb.h"
using grpc::Server;
using grpc::ServerAsyncResponseWriter;
using grpc::ServerBuilder;
using grpc::ServerCompletionQueue;
using grpc::ServerContext;
using grpc::Status;
using grpc::ServerBuilder;
using Simple::EchoRequest;
using Simple::EchoResponse;
class ServerImpl final {
public:
~ServerImpl(){
_server->Shutdown();
_cq->Shutdown();
}
void Run(){
std::string server_address("localhost:12345");
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&_service);
_cq = builder.AddCompletionQueue();
_server = builder.BuildAndStart();
std::cout << "Serfer listening on" << server_address << std::endl;
// main loop
HandleRpcs();
}
private:
class CallData {
public:
CallData(Simple::Server::AsyncService* service, ServerCompletionQueue* cq)
:_service(service), _cq(cq), _responder(&_ctx), _status(CREATE) {
Proceed();
}
void Proceed() {
if (_status == CREATE) {
_status = PROCESS;
// 注册请求处理
// RequestEcho其实就三确定注册方法Echo
// 然后传入处理请求的数据对象
// ctx、req、responder_、_cq、this
// this对于队列来说是标签
_service->RequestEcho(&_ctx, &_req,&_responder,_cq,_cq,this);
} else if (_status == PROCESS) {
// 已经开始处理一个请求了,生成一个新对象供下一个使用
new CallData(_service, _cq); // 会调用proceed注册请求
_resp.set_msg(_req.msg());
_status = FINISH;
_responder.Finish(_resp, Status::OK, this);
} else {
GPR_ASSERT(_status == FINISH);
delete this;
}
}
private:
Simple::Server::AsyncService* _service;
ServerCompletionQueue* _cq;
ServerContext _ctx;
EchoRequest _req;
EchoResponse _resp;
ServerAsyncResponseWriter<EchoResponse> _responder;
enum CallStatus { CREATE, PROCESS, FINISH};
CallStatus _status;
};
void HandleRpcs() {
new CallData(&_service, _cq.get());
void* tag;
bool ok;
while(true){
GPR_ASSERT(_cq->Next(&tag, &ok));
GPR_ASSERT(ok);
static_cast<CallData*>(tag)->Proceed();
}
}
std::unique_ptr<ServerCompletionQueue> _cq;
Simple::Server::AsyncService _service;
std::unique_ptr<grpc::Server> _server;
};
int main()
{
ServerImpl server;
server.Run();
return 0;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。