gRPC:以 C++为例

2023-11-04


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

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

附: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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gRPC:以 C++为例 的相关文章

随机推荐

  • vue 按钮 路由

    APP vue 在已有的按钮上加上路由功能 这里的按钮和布局容器使用了 elementui 的但无关原理 按下按钮即可跳转页面
  • 雷达测高知识点总结

    1 激光和雷达的区别 雷达 radar radio detection and ranging 无线电探测和测距 雷达波段 雷达发射电波的频率范围 大多数雷达工作在超短波及微波波段 其频率范围在30 300000兆赫 相应波长为1mm 10
  • 利用PyTorch自己动手从零实现YOLOv3(详细注释)

    学习一个算法最好的方式就是自己尝试着去实现它 因此 在这片博文里面 我会为大家讲解如何用PyTorch从零开始实现一个YOLOv3目标检测模型 参考源码请在这里下载 模型实现总共会分为以下六部分 一 配置文件以及解析 二 搭建YOLO模型框
  • 哈希结构(图文详解)【哈希表,哈希桶,位图,布隆过滤器】

    哈希结构 哈希概念 常见的K V结构 实现了元素关键码与元素值的映射关系 但没有实现元素关键值与元素存储位置的映射关系 在遍历过程中 一般的顺序表或搜索二叉树要进行关键值的多次比较 其中顺序表的时间复杂度为O n 二叉搜索树的时间复杂度O
  • phantomjs实现html生成pdf

    phantomjs实现html生成pdf 实现比较简单 同时能够实现对页面的完全展示成pdf 但是生成的时间比较长且并发很差 很容易直接挂掉 以下是实现 1 下载phantomjs 2 1 1 windows并解压到本地路劲 2 实现jav
  • php接口post数据接收不到参数原因

    1 检查头信息content type是不是为 content type application x www form urlencoded 这种传输是以表单的方式提交数据php使用 POST方式接受 2 如果头信息content type
  • python与mongodb交互-->pymongo

    from pymongo import MongoClient 创建数据库连接对象 client MongoClient ip 27017 选择一个数据库 db client admin db authenticate python pyt
  • c++学习——类和对象

    类和对象 类和对象的基本概念 类的封装 尽量把成员属性设置为私有的 小练习 结构体和类的区别 圆的周长类案例 学生类的案例 汽车案例 立方体案例 点和圆案例 类和对象的基本概念 类是自定义数据类型 是C语言的结构体进化而成的 对象是类实例化
  • 硬件笔记(一)——DCDC典型电路分析

    此次小记分析的电路为SIM7600技术手册的推荐外部电源电路 LM2596内部包含150KHZ振荡器 输入电压范围最高可达40V 4 5V 40V 最高可提供3A的直流负载电流 输出电压可调范围1 23V 37V 现对此电路进行分析 1 D
  • 程序获取

    程序获取 机器学习 深度学习程序和数据获取方式 目录 程序获取 机器学习 深度学习程序和数据获取方式 程序获取方式1 程序获取方式2 程序获取方式3 程序获取方式4 程序获取方式5 程序获取方式6 程序获取方式1 私信博主或者博客底部联系博
  • jmeter线程组内的接口顺序执行解决办法

    一 压力测试场景分析 测试人员在使用jmeter对一个场景进行压力测试 在一个线程组内有多个接口时 测试人员想要测试在特定的时间段内一直循环该线程以测试服务器压力 但是jmeter在run线程组的时候 经常不会按照该线程组内的接口顺序执行
  • Ubuntu torch.cuda.is_available() 返回 False情况

    如果Ubuntu20 04 出现torch cuda is available 返回 False情况 解决方法 重新安装Pytorch Ubuntu20 04 CUDA 11 4 Pytorch配置安装 conda conda create
  • Hibernate 自动创建表

    前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到教程 1 在 hibernate cfg xml 添加这句话 可以自动生成数据表
  • 开放集识别的最新进展总结(源于Recent Advances in Open Set Recognition: A Survey)

    摘要 在现实的识别 分类任务中 由于受到各种客观因素的限制 在训练一个识别器或分类器摘 0 摘要 原因与场景 在现实的识别 分类任务中 训练模型的时候可能并没有所有类别的训练集 因此 这样训练出来的模型在没有出现过的类出现时 一般会失效 解
  • 中国科学院大学工程管理与信息技术学院 2014年招收以下八个领域在职工程硕...

    中国科学院大学工程管理与信息技术学院2014年招收以下八个领域在职工程硕士 欢迎广大考生报考 一 专业领域介绍 招生领域 研究方向 学费 报考条件 学位 证书 学习方式
  • 数据结构与算法——栈的实现及模拟

    目录 一 栈的原理 二 栈的实现 1 栈的定义 2 栈的初始化 3 入栈 4 出栈 5 获取栈顶元素 6 栈的大小 7 判断栈是否为空 8 栈的销毁 一 栈的原理 堆栈 英语 stack 又称为栈或堆叠 是计算机科学中的一种抽象资料类型 只
  • Kafka核心设计与实践原理总结:进阶篇

    kafka作为当前热门的分布式消息队列 具有高性能 持久化 多副本备份 横向扩展能力 我学习了 深入理解Kafka 核心设计与实践原理总结 一书后 对其中主要的知识点进行了总结 便于理解和掌握kafka的原理和应用 在这里分享出来 希望也能
  • es常用curl命令

    说明 仅记录实验室测试过程 不作为官方文档使用 可能会有很多地方未能验证 因此无法进行技术兜底 需使用方多加验证测试 涉及到高危需走变更 目前测试版本均为651及以前版本 命令样例基于安全模式 如果是在非安全模式下 将命令中的参数 tlsv
  • .Net Core下简单的JWT黑名单中间件

    自从JWT认证方式在互联网上蔓延后 Session认证方式就被挤掉了一大半的生存空间 这里我们不讲JWT与Session两种方式的优缺点 我们只讲如何通过JWT的黑名单来阻止某些Token的登录 设置黑名单 也就是说要将Token写入某个存
  • gRPC:以 C++为例

    文章目录 1 gRPC 环境搭建 1 1 安装 cmake 1 2 安装 gcc gdb 1 3 安装 gRPC 1 4 protobuf 安装 1 5 测试环境 2 1 grpc 同步 2 1 定义服务 2 2 gRPC 服务端 2 3