gRPC:以 C++为例

简介: gRPC:以 C++为例

RPC 远程过程调用协议 Remote Procedure Call Protocol,客户端就像调用本地方法一样发起远程调用,用于分布式系统进程间通信。

gRPC 是一个基于 HTTP2 协议设计,语言无关的通用 RPC 框架。借助服务定义,可以生成服务器端骨架(服务器代理)。同时,生成客户端存根(客户端代理)。抽象简化了底层的通信框架,客户端就像调用本地方法那样,远程调用服务接口定义的方法。

grpc 微服务

附:HTTP 发展

  • http 1.0
  • http 1.1:Pipeline,无法分清数据归属,只能串行排队发送请求。
  • http 2.0:Duplexing,并行发送。每个请求对应一个流,每个请求的数据分为多个帧,数据帧按流 id 分组,分离出不同的请求。

1、gRPC 环境搭建

安装 gRPC 1.45.2 版本

安装必要的依赖工具

sudo apt-get install autoconf automake libtool

1.1、安装 cmake

cmake 最低版本 3.15,这里安装 3.23 版本。

# 卸载原有的 cmake
 sudo apt-get autoremove cmake
 # 下载解压 cmake 3.23
 wget https://cmake.org/files/v3.23/cmake-3.23.0-linux-x86_64.tar.gz
 tar xvzf cmake-3.23.0-linux-x86_64.tar.gz
 # 创建软链接
 sudo mv cmake-3.23.0-linux-x86_64 /opt/cmake-3.23.0
 sudo ln -sf /opt/cmake-3.23.0/bin/*  /usr/bin/
 # 测试
 cmake -version

1.2、安装 gcc/gdb

gcc/g++ 版本 6.3,这里安装 7.5

# 安装 gcc/g++ 7
 sudo apt-get install -y software-properties-common
 sudo add-apt-repository ppa:ubuntu-toolchain-r/test
 sudo apt update
 sudo apt install g++-7 -y
 # 创建软链接
 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 \
                          --slave /usr/bin/g++ g++ /usr/bin/g++-7 
 sudo update-alternatives --config gcc
 # 测试
 gcc -v
 g++ -v

1.3、安装 gRPC

# 下载源码
 git clone https://github.com/grpc/grpc
 # 选择版本 v1.45.2
 git tag
 git checkout v1.45.2
 # 下载第三方依赖
 git submodule update --init
 # 编译安装: tar -jxvf grpc-v1.45.2.tar.bz2
 mkdir -p cmake/build
 cd cmake/build
 cmake ../..
 make
 sudo make install

1.4、protobuf 安装

编译 third_party/protobuf 里面编译安装对应的 protobuf

cd third_party/protobuf/
 ./autogen.sh 
 ./configure --prefix=/usr/local
 make
 sudo make install
 sudo ldconfig  # 使得新安装的动态库能被加载
 protoc --version # 3.19.4

1.5、测试环境

编译 helloworld

cd grpc/examples/cpp/helloworld/
 mkdir build
 cd build/
 cmake ..
 make登录后复制

启动服务和客户端

# 启动服务端,监听在50051端口
 ./greeter_server
 Server listening on 0.0.0.0:50051
 # 启动客户端,服务端返回Hello world
 ./greeter_client 
 Greeter received: Hello world

2.1、grpc 同步


2.1、定义服务

构建 grpc 服务首先要定义服务接口。服务就是可以被远程调用的一组方法。

grpc 使用 pb (protocol buffers) 作为 IDL(接口定义语言,interface definition language),来定义服务接口。pb 是一种语言无关、平台无关、可扩展的结构化数据序列化机制。rpc 服务接口在 .proto 文件中定义,并将 rpc 方法参数和返回类型指定为 pb 消息。可以借助 grpc 插件来根据 pb 文件生成代码。

例:

syntax = "proto3";  // 语法
 package IM.Login;   // 包名
 // 定义服务:远程调用方法,参数 Request,返回值 Reply
 // pb 规定只能有一个参数,并只能返回一个值,想传多个,定义消息类型。
 service ImLogin {
    rpc Regist(IMRegistReq) returns (IMRegistRes) {} 
    rpc Login(IMLoginReq) returns (IMLoginRes) {}
 }
 // 注册账号
 message IMRegistReq{
     string user_name = 1; // 用户名
     string password = 2;  // 密码
 }
 // 注册返回
 message  IMRegistRes{
     string user_name = 1;   // 用户名
     uint32 user_id = 2;     // 用户 id
     uint32 result_code = 3; // 返回0,正常注册
 }
 // rpc 请求
 message IMLoginReq{
     string user_name = 1; // 用户名
     string password = 2;  // 密码
 }
 // rpc 返回
 message  IMLoginRes{
     uint32 user_id = 1; 
     uint32 result_code = 2; // 返回0的时候注册注册
 }

生成 C++ 代码

# 生成 simple.h 和 simple.cc 文件
 protoc -I ./ --cpp_out=. IM.Login.proto
 # 生成 simple.grpc.pb.h 和 simple.grpc.pb.cpp 文件,服务框架
 protoc -I ./ --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` IM.Login.proto
 protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=/usr/local/bin/grpc_cpp_plugin IM.Login.proto

2.2、gRPC 服务端

在服务端,需要实现服务定义,实现远程调用方法;并运行 grpc 服务器绑定该服务。具体来说,服务端需要做好两件事:

  • 重载服务:重载服务器基类的远程调用方法,实现 pb 中定义的 rpc。
  • 启动服务:ServerBuilder 工厂类创建并启动 grpc 服务

例:C++ 流程

  • 命名空间:引入 grpc 命名空间和自定义 pb 文件的命名空间
  • 重载服务
  • 启动服务
#include <iostream>
 #include <string>
 // grpc 头文件
 #include <grpcpp/ext/proto_server_reflection_plugin.h>
 #include <grpcpp/grpcpp.h>
 #include <grpcpp/health_check_service_interface.h>
 // 自定义 proto 文件生成的.h
 #include "IM.Login.pb.h"
 #include "IM.Login.grpc.pb.h"
 // 1、命名空间
 // grcp 命名空间
 using grpc::Server;
 using grpc::ServerBuilder;
 using grpc::ServerContext;
 using grpc::Status;
 // 自定义 proto 文件的命名空间
 using IM::Login::ImLogin;
 using IM::Login::IMRegistReq;
 using IM::Login::IMRegistRes;
 using IM::Login::IMLoginReq;
 using IM::Login::IMLoginRes;
 // 2、重写服务
 // 1、定义服务端的类:继承 .grpc.pb.h 文件定义的 grpc 服务
 // 2、重写 grpc 服务定义的方法
 class IMLoginServiceImpl : public ImLogin::Service {
     // 注册
     virtual Status Regist(ServerContext* context, const IMRegistReq* request, IMRegistRes* response) override {
         std::cout << "Regist user_name: " << request->user_name() << std::endl;
         response->set_user_name(request->user_name());
         response->set_user_id(10);
         response->set_result_code(0);
         return Status::OK;
     }
     // 登录
     virtual Status Login(ServerContext* context, const IMLoginReq* request, IMLoginRes* response) override {
         std::cout << "Login user_name: " << request->user_name() << std::endl;
         response->set_user_id(10);
         response->set_result_code(0);
         return Status::OK;
     }
 };
 // 3、启动 grpc 服务
 void RunServer() {
     std::string server_addr("0.0.0.0:50051");
     // 创建一个服务类
     IMLoginServiceImpl service;
     // 创建工厂类
     ServerBuilder builder;
     // 监听端口地址
     builder.AddListeningPort(server_addr, grpc::InsecureServerCredentials());
     // 心跳探活
     builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIME_MS, 5000);
     builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 10000);
     builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
     // 多线程:动态调整 epoll 线程数量
     builder.SetSyncServerOption(ServerBuilder::MIN_POLLERS, 4);
     builder.SetSyncServerOption(ServerBuilder::MAX_POLLERS, 8);
     // 注册服务
     builder.RegisterService(&service);
     // 创建并启动 rpc 服务器
     std::unique_ptr<Server> server(builder.BuildAndStart());
     std::cout << "Server listening on " << server_addr << std::endl;
     // 进入服务事件循环
     server->Wait();
 }
 int main(int argc, const char** argv) {
     RunServer();
     return 0;
 }

2.3、gRPC 客户端

在客户端,由服务定义 pb 生成客户端存根 stub(客户端代理),使用通道 channel 连接特定的 grpc 服务端;stub 在 channel 基础上创建而成,通过 stub 真正调用 rpc 请求。

核心代码

class ImLoginClient {
 public:
     // 使用通道 channel 初始化阻塞式存根 stub
     ImLoginClient(std::shared_ptr<Channel> channel)
     :stub_(ImLogin::NewStub(channel)) 
     {}
     // 使用阻塞式存根调用远程方法
     void Regist(const std::string &user_name, const std::string &password) {
         // 调用 rpc 接口
         Status status = stub_->Regist(&context, request, &response);
     }
 private:
     std::unique_ptr<ImLogin::Stub> stub_;   // 存根,客户端代理
 };

例:C++ 流程

  • 命名空间:引入 grpc 命名空间和自定义 pb 文件的命名空间
  • 定义客户端:实现远程调用的方法。
#include <iostream>
 #include <memory>
 #include <string>
 // grpc 头文件
 #include <grpcpp/grpcpp.h>
 // 自定义 proto 文件生成的.h
 #include "IM.Login.pb.h"
 #include "IM.Login.grpc.pb.h"
 // 命名空间
 // grcp 命名空间
 using grpc::Channel;
 using grpc::ClientContext;
 using grpc::Status;
 // 自定义 proto 文件的命名空间
 using IM::Login::ImLogin;
 using IM::Login::IMRegistReq;
 using IM::Login::IMRegistRes;
 using IM::Login::IMLoginReq;
 using IM::Login::IMLoginRes;
 class ImLoginClient {
 public:
     ImLoginClient(std::shared_ptr<Channel> channel)
     :stub_(ImLogin::NewStub(channel)) 
     {}
     void Regist(const std::string &user_name, const std::string &password) {
         IMRegistReq request;
         request.set_user_name(user_name);
         request.set_password(password);
         IMRegistRes response;
         ClientContext context;
         std::cout <<  "-> Regist req" << std::endl;
         // 调用 rpc 接口
         Status status = stub_->Regist(&context, request, &response);
         if(status.ok()) {
             std::cout <<  "user_name:" << response.user_name() << ", user_id:" << response.user_id() << std::endl;
         } 
         else {
             std::cout <<  "user_name:" << response.user_name() << "Regist failed: " << response.result_code()<< std::endl;
         }
     }
      void Login(const std::string &user_name, const std::string &password) {
         IMLoginReq request;
         request.set_user_name(user_name);
         request.set_password(password);
         IMLoginRes response;
         ClientContext context;
         std::cout <<  "-> Login req" << std::endl;
         // 调用 rpc 接口
         Status status = stub_->Login(&context, request, &response);
         if(status.ok()) {
             std::cout <<  "user_id:" << response.user_id() << " login ok" << std::endl;
         } 
         else {
             std::cout <<  "user_name:" << request.user_name() << "Login failed: " << response.result_code()<< std::endl;
         }
     }
 private:
     std::unique_ptr<ImLogin::Stub> stub_;   // 存根,客户端代理
 };
 int main()  {
     // 服务器的地址
     std::string server_addr = "localhost:50051";
     // 创建请求通道 
     ImLoginClient im_login_client(
         grpc::CreateChannel(server_addr, grpc::InsecureChannelCredentials())
     );
     // 测试
     std::string user_name = "Jim Hacker";
     std::string password = "123456";
     im_login_client.Regist(user_name, password);
     im_login_client.Login(user_name, password);
     return 0;
 }

2.4、消息流

当调用 grpc 服务时,客户端的 grpc 库会使用 pb,并将 rpc 的请求编排 marshal 为 pb 格式,然后将其通过 HTTP/2 进行发送。在服务器端,请求会解排 unmarshal,对应的过程调用会使用 pb 来执行。

3、gRPC stream

grpc 根据消息的数量,将通信模式分为以下四种:

  • 一元 RPC 模式:简单 RPC 模式,请求-响应式 RPC(1请求-1返回)
  • 服务端流 RPC 模式:客户端发送一个请求,服务端回发响应序列(流)
  • 客户端流 RPC 模式:客户端发送请求序列(流),服务端回发一个响应
  • 双向流 RPC 模式:客户端发送请求流,服务器端回发响应流

以官方范例 examples/cpp/route_guide/ 为例:pb 定义的服务如下,stream 关键字来定义流

service RouteGuide {
   // A simple RPC.
   rpc GetFeature(Point) returns (Feature) {}
   // A server-to-client streaming RPC.
   rpc ListFeatures(Rectangle) returns (stream Feature) {}
   // A client-to-server streaming RPC.
   rpc RecordRoute(stream Point) returns (RouteSummary) {}
   // A Bidirectional streaming RPC.
   rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
 }

3.1、服务端:RPC 实现

服务端需要实现 pb 中定义的 rpc,每种 rpc 的实现都需要 ServerContext 参数。

其他参数则与 grpc 通信模式有关。

非流模式:Request 请求,Reply 响应。

// rpc ListFeatures(Rectangle) returns (stream Feature) {}
 Status ListFeatures(ServerContext* context, const routeguide::Rectangle* rectangle, ServerWriter<Feature>* writer);

流模式:单向流

ServerReader:读 client 流,通过 Reader->Read() 返回的 bool 型状态,判断流的结束。

// rpc RecordRoute(stream Point) returns (RouteSummary) {}
 Status RecordRoute(ServerContext* context, ServerReader<Point>* reader, RouteSummary* summary) {
     // 读取请求
     while (reader->Read(&point)) {
         ...
     }
 }

ServerWriter:写 server 流,通过结束 rpc 函数并返回状态码的方式结束流

// rpc ListFeatures(Rectangle) returns (stream Feature) {}
 Status ListFeatures(ServerContext* context, const routeguide::Rectangle* rectangle, ServerWriter<Feature>* writer) {
     // 发送响应
     writer->Write(f);
     ...
 }

流模式:双向流

ServerReaderWriter:只需要 1 个参数

// rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
 // 注意线程同步
 Status RouteChat(ServerContext* context, ServerReaderWriter<RouteNote, RouteNote>* stream) {
     // 读取数据
     while (stream->Read(&note)) {
         // 写回数据
         stream->Write(n);
     }
 }

3.2、客户端:RPC 调用

客户端均需要传入 ClientContext 参数。

其他参数则与 grpc 通信模式有关。

非流模式:Request 请求,Reply 响应。

// rpc GetFeature(Point) returns (Feature) {}
 Status GetFeature(ClientContext* context, const Point& request, Feature* response);

流模式:单向流

ClientReader:读 server 流,通过 Reader->Read() 返回的 bool 型状态,判断流的结束。

// rpc ListFeatures(Rectangle) returns (stream Feature) {}
 unique_ptr<ClientReader<Feature>> ListFeatures(ClientContext* context, const Rectangle& request) {
     // 创建 reader,读取响应
     // 参数:rpc 的 Context, Request
     std::unique_ptr<ClientReader<Feature> > reader(stub_->ListFeatures(&context, rect));
     // 读取响应
     while (reader->Read(&feature)) {
         ...
     }
     // 等待返回状态
     Status status = reader->Finish();
     ...
 }

ClientWriter:写 client 流,流的结束

  • writer->WritesDone():发送结束
  • writer->Finish():等待对端返回状态
// rpc RecordRoute(stream Point) returns (RouteSummary) {}
 void RecordRoute() {
     // 创建 writer
     std::unique_ptr<ClientWriter<Point> > writer(stub_->RecordRoute(&context, &stats));
     // 发送请求
     writer->Write(f.location()))
     // 发送结束
     writer->WritesDone();
     // 等待返回状态
     Status status = writer->Finish();
 }

流模式:双向流

ClientReaderWriter:对于 rpc 调用,都是 client 请求后 server 响应,即双向流需要 client 先发送完数据,server 才能结束 rpc。流的结束

  • stream->WriteDone()
  • stream->Finish()
// rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
 // client 需要开启发送线程和接收线程
 void RouteChat() {
     // 创建 readerwriter,读取写入都是它
     std::shared_ptr<ClientReaderWriter<RouteNote, RouteNote> > stream(
         stub_->RouteChat(&context));
     // 子线程发送请求
     std::thread writer([stream]() {
         // 发送请求
         stream->Write(note);
         // 发送结束
         stream->WritesDone();
     });
     ...
     // 主线程读取响应
     // 读取响应
     while (stream->Read(&server_note)) {
     }
     writer.join();
     // 等待返回状态
     Status status = stream->Finish();
     ...
 }

3.3、流的结束

这里,总结流的结束方式:

  • Client 发送流:通过 Writer->WritesDone() 结束流
  • Server 发送流:通过结束 rpc 调用并返回状态码status code的方式来结束流
  • 读取流:通过 Reader->Read() 返回的 bool 型状态,来判断流是否结束

4、gRPC 异步

官方文档:Asynchronous-API tutorial

grpc 通过完成队列 CompletionQueue 来进行异步操作,其通用流程为:

  • 绑定完成队列 cq 到 rpc 请求
  • void* Tag 唯一标识请求该 rpc 请求
  • 调用 cq->Next()阻塞读取 cq 队列中的下个 rpc 请求

4.1、异步 server

异步 server 的逻辑

  • 创建 CallData 类实例,记录一个 rpc 事件的逻辑和状态。将其加入 cq 队列,并通过将 CallData 实例 this 指针作为 tag 唯一标识该 CallData 实例。
  • 在服务器事件循环中,异步处理 rpc 事件。事件到来时,从 cq 队列取出事件cq->Next(),处理事件CallData->Proceed(),处理后等待对端返回结果 responder_.Finish(类型:ServerAsyncResponseWriter


创建 CallData 类:实现 rpc 请求的逻辑和状态。每个 rpc 请求对应一个 CallData 实例。若要实现不同类型的 rpc 请求,可以构造对应的 CallData 子类,子类继承基类 CallData 的通用部分,并实现自己的差异化部分。

例如:文章第 1 部分的案例

class ServerImpl final {
     // 实现 rpc 请求的逻辑和状态
     class CallData {
         public:
         // 创建 CallData 类,
         // 1、绑定 cq 队列到 rpc 调用
         CallData(ImLogin::AsyncService* service, ServerCompletionQueue* cq)
             : service_(service), cq_(cq), status_(CREATE) {
                 Proceed();  // 业务逻辑处理
         }
         virtual ~CallData(){}
         // 虚函数:业务逻辑接口
         virtual void Proceed() {}
         // 基类部分
         // rpc 提供的异步服务
         ImLogin::AsyncService* service_;
         // 完成队列
         ServerCompletionQueue* cq_;
         // rpc 上下文
         ServerContext ctx_;
         // 状态机:描述业务逻辑处理时的状态
         enum CallStatus { CREATE, PROCESS, FINISH };
         // 当前 rpc 服务的状态
         CallStatus status_; 
     };
     // rpc:注册服务
     class RegistCallData : public CallData {
         ...
         // 实现注册 rpc 服务的业务逻辑过程处理
         void Proceed() override {...}
         // 子类成员
         IMRegistReq request_;
         IMRegistRes reply_;
         ServerAsyncResponseWriter<IMRegistRes> responder_;
     };
     // rpc:登录服务
     class LoginCallData : public CallData {
         ...
         void Proceed() override {...}
         IMLoginReq request_;
         IMLoginRes reply_;
         ServerAsyncResponseWriter<IMLoginRes> responder_;
     };
     ...
 };

以官方范例 examples/cpp/helloworld 为例,完整代码如下:

#include <iostream>
 #include <memory>
 #include <string>
 #include <thread>
 #include <grpc/support/log.h>
 #include <grpcpp/grpcpp.h>
 #include "examples/protos/helloworld.grpc.pb.h"
 using grpc::Server;
 using grpc::ServerAsyncResponseWriter;
 using grpc::ServerBuilder;
 using grpc::ServerCompletionQueue;
 using grpc::ServerContext;
 using grpc::Status;
 using helloworld::Greeter;
 using helloworld::HelloReply;
 using helloworld::HelloRequest;
 class ServerImpl final {
     public:
     ~ServerImpl() {
         server_->Shutdown();
         cq_->Shutdown();
     }
     void Run() {
         std::string server_address("0.0.0.0:50051");
         // 创建工厂类
         ServerBuilder builder;
         // 监听端口地址,不验证
         builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
         // 注册服务
         builder.RegisterService(&service_);
         // 创建完成队列 cq:把要监听的 rpc 对象放入到队列
         cq_ = builder.AddCompletionQueue();
         // 启动服务
         server_ = builder.BuildAndStart();
         std::cout << "Server listening on " << server_address << std::endl;
         // 启动服务器事件循环:处理 rpc 请求
         HandleRpcs();
     }
     private:
     // 实现 rpc 请求的逻辑和状态
     class CallData {
         public:
         // 创建 CallData 类
         // 1、绑定 cq 队列到 rpc 调用
         CallData(Greeter::AsyncService* service, ServerCompletionQueue* cq)
             : service_(service), cq_(cq), responder_(&ctx_), status_(CREATE) {
                 // 调用业务逻辑处理
                 Proceed();
             }
         // 业务逻辑过程处理函数:状态机
         void Proceed() {
             // 创建状态:把 CallData 实例放入 cq 队列后进入该状态
             if (status_ == CREATE) {
                 // 该 CallData 实例状态推进到 PROCESS
                 status_ = PROCESS;
                 // 处理 rpc 请求:CallData 实例的 this 指针作为唯一标识该 rpc 请求的 tag,实现异步返回
                 service_->RequestSayHello(&ctx_, &request_, &responder_, cq_, cq_, this);
             } 
             // 处理状态
             else if (status_ == PROCESS) {
                 // 创建一个新的 calldata 实例,用于处理新的 rpc 请求
                 new CallData(service_, cq_);
                 // 业务逻辑处理
                 std::string prefix("Hello ");
                 reply_.set_message(prefix + request_.name());
                 // 业务逻辑处理结束
                 // 该 calldata 实例状态推进到 FINISH,并将会在 FINISH 状态中释放其占用的资源
                 status_ = FINISH;
                 // 2、等待对端返回状态:this 指针作为 tag 唯一标识 calldata 实例
                 responder_.Finish(reply_, Status::OK, this);
             } 
             else {
                 GPR_ASSERT(status_ == FINISH);
                 // 释放 calldata 内存,即本次 rpc 请求的资源
                 delete this;
             }
         }
         private:
         // rpc 提供的异步服务
         Greeter::AsyncService* service_;
         // 完成队列
         ServerCompletionQueue* cq_;
         // rpc 上下文
         ServerContext ctx_;
         // What we get from the client.
         HelloRequest request_;
         // What we send back to the client.
         HelloReply reply_;
         // The means to get back to the client.
         ServerAsyncResponseWriter<HelloReply> responder_;
         // 状态机:描述业务逻辑处理时的状态
         enum CallStatus { CREATE, PROCESS, FINISH };
         // 当前 rpc 服务的状态
         CallStatus status_;  
     };
     // 服务器事件循环:处理 rpc 请求,可运行在多线程
     void HandleRpcs() {
         // 创建 calldata 类维护 rpc 请求的逻辑和状态
         new CallData(&service_, cq_.get());
         // 每个 calldata 请求的唯一标识,指向上面 new calldata 类的地址 
         void* tag;  
         bool ok;
         while (true) {
             // 3、阻塞读取 cq 队列中的下个 rpc 请求
             // 通过返回值判断是否有请求到来还是 cq 队列正在关闭      
             GPR_ASSERT(cq_->Next(&tag, &ok));
             GPR_ASSERT(ok);
             // 处理业务,可以自定义 proceed
             // 改进:扔给线程池去做异步处理
             static_cast<CallData*>(tag)->Proceed();
         }
     }
     // 完成队列
     std::unique_ptr<ServerCompletionQueue> cq_;
     // rpc 异步服务
     Greeter::AsyncService service_;   
     // rpc 服务器
     std::unique_ptr<Server> server_; 
 };
 int main(int argc, char** argv) {
     ServerImpl server;
     server.Run();
     return 0;
 }

4.2、异步 client

异步 client 的逻辑

  • 绑定 CompletionQueue 到 rpc 请求。
  • 调用 rpc_.Finish等待对端返回状态
  • 调用 cq->Next() 阻塞读取 cq 队列中的下个 rpc 事件

以官方范例 examples/cpp/helloworld 为例,完整代码如下

#include <iostream>
 #include <memory>
 #include <string>
 #include <grpc/support/log.h>
 #include <grpcpp/grpcpp.h>
 #include "examples/protos/helloworld.grpc.pb.h"
 using grpc::Channel;
 using grpc::ClientAsyncResponseReader;
 using grpc::ClientContext;
 using grpc::CompletionQueue;
 using grpc::Status;
 using helloworld::Greeter;
 using helloworld::HelloReply;
 using helloworld::HelloRequest;
 class GreeterClient {
  public:
   explicit GreeterClient(std::shared_ptr<Channel> channel)
       : stub_(Greeter::NewStub(channel)) {}
   std::string SayHello(const std::string& user) {
     HelloRequest request;
     request.set_name(user);
     HelloReply reply;
     ClientContext context;
     CompletionQueue cq;
     Status status;
     // 1、绑定 cq 到 rpc 请求
     std::unique_ptr<ClientAsyncResponseReader<HelloReply> > rpc(
         stub_->PrepareAsyncSayHello(&context, request, &cq));
     // 初始化 rpc 调用
     rpc->StartCall();
     // 2、等待对端返回状态
     rpc->Finish(&reply, &status, (void*)1);
     // 3、阻塞读取 cq 队列中的下个 rpc 事件
     void* got_tag;
     bool ok = false;
     GPR_ASSERT(cq.Next(&got_tag, &ok));
     GPR_ASSERT(got_tag == (void*)1);
     GPR_ASSERT(ok);
     if (status.ok()) {
       return reply.message();
     } else {
       return "RPC failed";
     }
   }
  private:
   std::unique_ptr<Greeter::Stub> stub_;
 };
 int main(int argc, char** argv) {
   GreeterClient greeter(grpc::CreateChannel( "localhost:50051", grpc::InsecureChannelCredentials()));
   std::string user("world");
   std::string reply = greeter.SayHello(user);  
   std::cout << "Greeter received: " << reply << std::endl;
   return 0;
 }

5、参考

  • Kasun Indrasiri, Danesh Kuruppu. gRPC: Up and Running[M]. O'Reilly Media, Inc. 2020.
  • gRPC C++ API
相关文章
|
7月前
|
NoSQL Ubuntu 测试技术
GRPC C++开发环境搭建
GRPC C++开发环境搭建
667 0
|
25天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
38 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
83 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
80 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
88 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
31 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
26 1
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)