Linux C++ gPRC使用说明

一、基础概念

1.RPC

RPC 代指远程过程调用(Remote Procedure Call),它的调用包含了传输协议和编码(对象序列号)协议等等。允许运行于一台计算机的程序调用另一台计算机的子程序,而开发人员无需额外地为这个交互作用编程。)

1)RPC 框架

一个完整的 RPC 框架,应包含负载均衡、服务注册和发现、服务治理等功能,并具有可拓展性便于流量监控系统等接入。

常见 RPC 框架:

\ 跨语言 多 IDL 服务治理 注册中心 服务管理
gRPC × × × ×
Thrift × × × ×
Rpcx ×
Dubbo ×

2)RPC优点

简单、通用、安全、效率。

3)RPC 生命周期

  • 一元RPC

    首先考虑客户端发送单个请求并返回单个响应的最简单的 RPC 类型。

    • 一旦客户端调用了存根方法,服务器就会收到通知:RPC 已经被调用,同时包含客户端的元数据 、方法名称和指定的截止日期(如果适用)。
    • 然后服务器可以立即发送回它自己的初始元数据(必须在任何响应之前发送),或者等待客户端的请求消息。首先发生的是特定于应用程序的。
    • 一旦服务器收到客户端的请求消息,它就会做任何必要的工作来创建和填充响应。然后将响应连同状态详细信息(状态代码和可选状态消息)和可选的尾随元数据一起返回(如果成功)到客户端。
    • 如果响应状态为OK,则客户端得到响应,在客户端完成调用。
  • 服务器流式RPC

    服务器流式 RPC 类似于一元 RPC,不同之处在于服务器返回消息流以响应客户端的请求。发送完所有消息后,服务器的状态详细信息(状态代码和可选状态消息)和可选的尾随元数据将发送到客户端。这样就完成了服务器端的处理。一旦客户端拥有服务器的所有消息,它就完成了。

  • 客户端流式 RPC

    客户端流式 RPC 类似于一元 RPC,不同之处在于客户端向服务器发送消息流而不是单个消息。服务器用一条消息(连同它的状态详细信息和可选的尾随元数据)进行响应,通常但不一定是在它收到所有客户端的消息之后。

  • 双向流式RPC

    在双向流式 RPC 中,调用由调用方法的客户端和接收客户端元数据、方法名称和截止日期的服务器发起。服务器可以选择发回其初始元数据或等待客户端开始流式传输消息。

    客户端和服务器端流处理是特定于应用程序的。由于两个流是独立的,客户端和服务器可以按任意顺序读写消息。例如,服务器可以等到收到所有客户端的消息后再写入消息,或者服务器和客户端可以玩“乒乓”——服务器收到请求,然后发回响应,然后客户端发送基于响应的另一个请求,依此类推。

  • 截止日期/超时

    gRPC 允许客户端指定在 RPC 因DEADLINE_EXCEEDED错误终止之前他们愿意等待 RPC 完成多长时间。在服务器端,服务器可以查询特定的 RPC 是否超时,或者还剩下多少时间来完成 RPC。

    指定截止日期或超时是特定于语言的:一些语言 API 根据超时(持续时间)工作,而某些语言 API 根据截止日期(固定时间点)工作,可能有也可能没有默认截止日期。

  • RPC 终止

    在 gRPC 中,客户端和服务器都对调用的成功做出独立和本地的判断,它们的结论可能不一致。这意味着,例如,您可能有一个 RPC 在服务器端成功完成(“我已经发送了我所有的响应!”)但在客户端失败(“响应在我的截止日期之后到达!”)。服务器也有可能在客户端发送所有请求之前决定完成。

  • 取消 RPC

    客户端或服务器都可以随时取消 RPC。取消会立即终止 RPC,以便不再进行任何工作。

  • 元数据

    元数据是有关特定 RPC 调用(例如身份验证详细信息)的信息,其形式为键值对列表,其中键是字符串,值通常是字符串,但也可以是二进制数据。元数据对 gRPC 本身是不透明的——它允许客户端提供与服务器调用相关的信息,反之亦然。

    对元数据的访问取决于语言。

  • 频道

    gRPC 通道提供到指定主机和端口上的 gRPC 服务器的连接。它在创建客户端存根时使用。客户端可以指定通道参数来修改 gRPC 的默认行为,例如打开或关闭消息压缩。通道具有状态,包括connectedidle

    gRPC 如何处理关闭通道取决于语言。某些语言还允许查询通道状态。

4)同步与异步

  • 同步RPC调用:block until a response arrives from the server are the closest approximation to the abstraction of a procedure call that RPC aspires to.

  • 异步RPC调用:networks are inherently asynchronous and in many scenarios it’s useful to be able to start RPCs without blocking the current thread.

2.Protobuf

Protocol Buffers 是一种与语言、平台无关,可扩展的序列化结构化数据的方法,用.proto文件表示,常用于通信协议,数据存储等等。相较于 JSON、XML,它更小、更快、更简单,因此也更受开发人员的青眯。

protoc 是 protobuf 协议的编译器,一个.proto文件会生成一个.h和一个.cc文件。

1)语法

详细介绍参考: Language Guide (proto3)

syntax ="proto3";
service SearchService{
    rpc Search(SearchRequest) returns (SearchResponse);
}
message SearchRequest{
  string query =1;
  int32 page_number =2;
  int32 result_per_page =3;
}
message SearchResponse{
...
}
  • 第一行(非空的非注释行)声明使用 proto3 语法。如果不声明,将默认使用 proto2 语法。
  • 定义 SearchService RPC 服务,其包含 RPC 方法 Search,入参为 SearchRequest 消息,出参为 SearchResponse 消息
  • 定义 SearchRequestSearchResponse 消息,前者定义了三个字段,每一个字段包含三个属性:类型、字段名称、字段编号。
  • Protobuf 编译器会根据选择的语言不同,生成相应语言的 Service Interface Code 和 Stubs

2)数据类型

.proto Type C++ Type Java Type Go Type PHP Type
double double double float64 float
float float float float32 float
int32 int32 int int32 integer
int64 int64 long int64 integer/string
uint32 uint32 int uint32 integer
uint64 uint64 long uint64 integer/string
sint32 int32 int int32 integer
sint64 int64 long int64 integer/string
fixed32 uint32 int uint32 integer
fixed64 uint64 long uint64 integer/string
sfixed32 int32 int int32 integer
sfixed64 int64 long int64 integer/string
bool bool boolean bool boolean
string string String string string
bytes string ByteString []byte string

3)Protobuf对比 XML的优势

  • 更简单
  • 数据描述文件只需原来的 1/10 至 1/3
  • 解析速度是原来的 20 倍至 100 倍
  • 减少了二义性
  • 生成了更易使用的数据访问类

3.gRPC

gRPC(gRPC Remote Procedure Calls) 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计,它采用了 Protobuf 作为 IDL(Interface description language)。

1)特点

  • HTTP/2

  • Protobuf

  • 客户端、服务端基于同一份 IDL

  • 移动网络的良好支持

  • 支持多语言

2)通信过程

image
  • 客户端(gRPC Sub)调用 A 方法,发起 RPC 调用

  • 对请求信息使用 Protobuf 进行对象序列化压缩(IDL)

  • 服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回

  • 对响应结果使用 Protobuf 进行对象序列化压缩(IDL)

  • 客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果

3)服务定义

gRPC 允许定义四种服务方法

  • 一元 RPC,客户端向服务器发送单个请求并返回单个响应,就像普通的函数调用一样。

    • 数据包过大会造成瞬时压力
    • 接收数据包时,需要所有数据包都接受成功且正确后,才能够回调响应,进行业务处理(无法客户端边发送,服务端边处理)
    rpc SayHello(HelloRequest) returns (HelloResponse);
    
  • 服务器流式 RPC,客户端向服务器发送请求并获取流以读取一系列消息。客户端从返回的流中读取,直到没有更多消息。gRPC 保证单个 RPC 调用中的消息排序。

    image
    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
    
  • 客户端流式 RPC,单向流,客户端通过流式发起多次 RPC 请求给服务端,服务端发起一次响应给客户端。

    image
    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
    
  • 双向流式 RPC,由客户端以流式的方式发起请求,服务端同样以流式的方式响应请求

    首个请求一定是 Client 发起,但具体交互方式(谁先谁后、一次发多少、响应多少、什么时候关闭)根据程序编写的方式来确定(可以结合协程)。

    image
    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
    

4)gRPC 与 REST 对比

  • gRPC 在很大程度上遵循 HTTP/2 上的 HTTP 语义,但明确允许全双工流。

  • 与典型的 REST 约定不同,因为gRPC在调度期间出于性能原因使用静态路径,因为解析来自路径、查询参数和有效负载主体的调用参数会增加延迟和复杂性。

  • gRPC还对一组错误进行了形式化,这些错误比 HTTP 状态代码更直接适用于 API 用例。

5)gRPC支持的语言与平台

Language OS Compilers / SDK
C/C++ Linux, Mac GCC 4.9+, Clang 3.4+
C/C++ Windows 7+ Visual Studio 2015+
C# Linux, Mac .NET Core, Mono 4+
C# Windows 7+ .NET Core, NET 4.5+
Dart Windows, Linux, Mac Dart 2.12+
Go Windows, Linux, Mac Go 1.13+
Java Windows, Linux, Mac JDK 8 recommended (Jelly Bean+ for Android)
Kotlin Windows, Linux, Mac Kotlin 1.3+
Node.js Windows, Linux, Mac Node v8+
Objective-C macOS 10.10+, iOS 9.0+ Xcode 7.2+
PHP Linux, Mac PHP 7.0+
Python Windows, Linux, Mac Python 3.5+
Ruby Windows, Linux, Mac Ruby 2.3+

6)使用 API

.proto文件中的服务定义开始,gRPC 提供了生成客户端和服务器端代码的协议缓冲区编译器插件。gRPC 用户通常在客户端调用这些 API,并在服务器端实现相应的 API。

  • 服务器实现服务声明的方法并运行 gRPC 服务器来处理客户端调用。gRPC 基础架构解码传入请求、执行服务方法并编码服务响应。
  • 客户端有一个称为stub(对于某些语言,首选术语是client)的本地对象,它实现与服务相同的方法。然后客户端可以在本地对象上调用这些方法,将调用的参数包装在适当的协议缓冲区消息类型中 - gRPC 负责将请求发送到服务器并返回服务器的协议缓冲区响应。

二、安装gRPC与protoc

gRPC Server 和 Client互相通讯,需要使用到如下库:

  • google.golang.org/grpc
  • github.com/golang/protobuf/protoc-gen-go

1.CMake编译与安装gRPC

# 选择一个目录来保存本地安装的软件包
export MY_INSTALL_DIR=$HOME/.local
# 将本地bin文件夹添加到路径变量
export PATH="$MY_INSTALL_DIR/bin:$PATH"

# 需要 3.13 或更高版本的cmake
sudo apt install -y cmake
# 或
wget -q -O cmake-linux.sh https://github.com/Kitware/CMake/releases/download/v3.19.6/cmake-3.19.6-Linux-x86_64.sh
~/.local/bin/cmake -version
cmake version 3.19.6

# 安装构建 gRPC 所需的基本工具
sudo apt install -y build-essential autoconf libtool pkg-config

# 克隆grpc 
git clone --recurse-submodules -b v1.42.0 https://github.com/grpc/grpc

# 构建和安装 gRPC 和协议缓冲区
$ cd grpc
$ mkdir -p cmake/build
$ pushd cmake/build
$ ~/.local/bin/cmake -DgRPC_INSTALL=ON \
      -DgRPC_BUILD_TESTS=OFF \
      -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
      ../..
$ make -j
$ make install
$ popd

2.安装Protocol Buffers v3

wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protobuf-all-3.19.1.zip
unzip protobuf-all-3.19.1.zip
cd protobuf-3.19.1/
./configure
make
make install
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# 查看版本
$ protoc --version
libprotoc 3.19.1

三、构建并使用C++ gRPC示例

1.示例程序目录

基于https://github.com/grpc/grpc

$ tree grpc/examples/cpp/ -L 2
examples/cpp/
├── cmake
│   └── common.cmake
├── compression
│   ├── BUILD
│   ├── CMakeLists.txt
│   ├── greeter_client.cc
│   ├── greeter_server.cc
│   ├── Makefile
│   └── README.md
├── helloworld      # 示例服务端与客户端程序
│   ├── BUILD
│   ├── cmake
│   ├── cmake_externalproject
│   ├── CMakeLists.txt
│   ├── cocoapods
│   ├── greeter_async_client2.cc
│   ├── greeter_async_client.cc
│   ├── greeter_async_server.cc
│   ├── greeter_callback_client.cc
│   ├── greeter_callback_server.cc
│   ├── greeter_client.cc   # gRPC Client程序
│   ├── greeter_server.cc  # gRPC Server程序
│   ├── Makefile
│   ├── README.md
│   ├── xds_greeter_client.cc
│   └── xds_greeter_server.cc
├── keyvaluestore
│   ├── BUILD
│   ├── caching_interceptor.h
│   ├── client.cc
│   ├── CMakeLists.txt
│   └── server.cc
├── load_balancing
│   ├── BUILD
│   ├── CMakeLists.txt
│   ├── greeter_client.cc
│   ├── greeter_server.cc
│   ├── Makefile
│   └── README.md
├── metadata
│   ├── BUILD
│   ├── CMakeLists.txt
│   ├── greeter_client.cc
│   ├── greeter_server.cc
│   ├── Makefile
│   └── README.md
├── README.md
└── route_guide
    ├── BUILD
    ├── CMakeLists.txt
    ├── helper.cc
    ├── helper.h
    ├── Makefile
    ├── README.md
    ├── route_guide_callback_client.cc
    ├── route_guide_callback_server.cc
    ├── route_guide_client.cc
    ├── route_guide_db.json
    └── route_guide_server.cc
    
$ tree examples/protos/ # 协议缓冲区文件
examples/protos/
├── auth_sample.proto
├── BUILD
├── hellostreamingworld.proto
├── helloworld.proto
├── keyvaluestore.proto
├── README.md
└── route_guide.proto

2.使用协议缓冲区文件

gRPC 服务是使用协议缓冲区定义的,存放路径:examples/protos。服务器客户端存根都有一个SayHello()RPC 方法。

examples/protos/helloworld.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

协议缓冲区文件通过protoc工具生成服务类和消息类的代码

  • 通过CMakeLists方式

    project(HelloWorld C CXX)
    
    include(../cmake/common.cmake)
    
    # Proto file
    get_filename_component(hw_proto "../../protos/helloworld.proto" ABSOLUTE)
    get_filename_component(hw_proto_path "${hw_proto}" PATH)
    
    # Generated sources
    set(hw_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.cc")
    set(hw_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.h")
    set(hw_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.cc")
    set(hw_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.h")
    add_custom_command(
          OUTPUT "${hw_proto_srcs}" "${hw_proto_hdrs}" "${hw_grpc_srcs}" "${hw_grpc_hdrs}"
          COMMAND ${_PROTOBUF_PROTOC}
          ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
            --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
            -I "${hw_proto_path}"
            --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
            "${hw_proto}"
          DEPENDS "${hw_proto}")
    
  • 通过Makefile方式

    PROTOC = protoc
    GRPC_CPP_PLUGIN = grpc_cpp_plugin
    GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`
    PROTOS_PATH = ../../protos
    
    .PRECIOUS: %.grpc.pb.cc
    %.grpc.pb.cc: %.proto
      $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<
    
    .PRECIOUS: %.pb.cc
    %.pb.cc: %.proto
      $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $<
    

1)服务类文件

  • <service_name>.grpc.pb.h
  • <service_name>.grpc.pb.cc
#include "helloworld.pb.h"

namespace helloworld {
    
 class StubInterface {
     class Stub final : public StubInterface {
       public:
        class async final :
          public StubInterface::async_interface {
         public:
          void SayHello(::grpc::ClientContext* context, const ::helloworld::HelloRequest* request, ::helloworld::HelloReply* response, std::function<void(::grpc::Status)>) override;
          void SayHello(::grpc::ClientContext* context, const ::helloworld::HelloRequest* request, ::helloworld::HelloReply* response, ::grpc::ClientUnaryReactor* reactor) override;
          void SayHelloAgain(::grpc::ClientContext* context, const ::helloworld::HelloRequest* request, ::helloworld::HelloReply* response, std::function<void(::grpc::Status)>) override;
          void SayHelloAgain(::grpc::ClientContext* context, const ::helloworld::HelloRequest* request, ::helloworld::HelloReply* response, ::grpc::ClientUnaryReactor* reactor) override;
         private:
          friend class Stub;
          explicit async(Stub* stub): stub_(stub) { }
          Stub* stub() { return stub_; }
          Stub* stub_;
        };
        class async* async() override { return &async_stub_; }
     };
     // 定义客户端可以创建根存Stub的方法
     static std::unique_ptr<Stub> NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options = ::grpc::StubOptions());
 };
    
  // 定义服务端需要实现的方法
 class Service : public ::grpc::Service {
   public:
    Service();
    virtual ~Service();
    // Sends a greeting
    virtual ::grpc::Status SayHello(::grpc::ServerContext* context, const ::helloworld::HelloRequest* request, ::helloworld::HelloReply* response);
    // Sends another greeting
    virtual ::grpc::Status SayHelloAgain(::grpc::ServerContext* context, const ::helloworld::HelloRequest* request, ::helloworld::HelloReply* response);
  };   
}
  • 注意:客户端可以通过根存Stub调用服务类的方法,当服务端实现了服务类的方法后,服务端就会响应客户端的方法调用。达到的效果就像客户端调用本地的方法。

2)消息类文件

  • <service_name>.pb.h
  • <service_name>.pb.cc

提供对protobuf定义的消息进行读写等操作的方法

// HelloRequest

// string name = 1;
inline void HelloRequest::clear_name() {
  name_.ClearToEmpty();
}
inline const std::string& HelloRequest::name() const {
  // @@protoc_insertion_point(field_get:helloworld.HelloRequest.name)
  return _internal_name();
}
template <typename ArgT0, typename... ArgT>
inline PROTOBUF_ALWAYS_INLINE
void HelloRequest::set_name(ArgT0&& arg0, ArgT... args) {
 
 name_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
  // @@protoc_insertion_point(field_set:helloworld.HelloRequest.name)
}
inline std::string* HelloRequest::mutable_name() {
  std::string* _s = _internal_mutable_name();
  // @@protoc_insertion_point(field_mutable:helloworld.HelloRequest.name)
  return _s;
}
inline const std::string& HelloRequest::_internal_name() const {
  return name_.Get();
}
inline void HelloRequest::_internal_set_name(const std::string& value) {
  
  name_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, value, GetArenaForAllocation());
}
inline std::string* HelloRequest::_internal_mutable_name() {
  
  return name_.Mutable(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, GetArenaForAllocation());
}
inline std::string* HelloRequest::release_name() {
  // @@protoc_insertion_point(field_release:helloworld.HelloRequest.name)
  return name_.Release(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), GetArenaForAllocation());
}
inline void HelloRequest::set_allocated_name(std::string* name) {
  if (name != nullptr) {
    
  } else {
    
  }
  name_.SetAllocated(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), name,
      GetArenaForAllocation());
  // @@protoc_insertion_point(field_set_allocated:helloworld.HelloRequest.name)
}



// HelloReply

// string message = 1;
inline void HelloReply::clear_message() {
  message_.ClearToEmpty();
}
inline const std::string& HelloReply::message() const {
  // @@protoc_insertion_point(field_get:helloworld.HelloReply.message)
  return _internal_message();
}
template <typename ArgT0, typename... ArgT>
inline PROTOBUF_ALWAYS_INLINE
void HelloReply::set_message(ArgT0&& arg0, ArgT... args) {
 
 message_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
  // @@protoc_insertion_point(field_set:helloworld.HelloReply.message)
}
inline std::string* HelloReply::mutable_message() {
  std::string* _s = _internal_mutable_message();
  // @@protoc_insertion_point(field_mutable:helloworld.HelloReply.message)
  return _s;
}
inline const std::string& HelloReply::_internal_message() const {
  return message_.Get();
}
inline void HelloReply::_internal_set_message(const std::string& value) {
  
  message_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, value, GetArenaForAllocation());
}
inline std::string* HelloReply::_internal_mutable_message() {
  
  return message_.Mutable(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, GetArenaForAllocation());
}
inline std::string* HelloReply::release_message() {
  // @@protoc_insertion_point(field_release:helloworld.HelloReply.message)
  return message_.Release(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), GetArenaForAllocation());
}
inline void HelloReply::set_allocated_message(std::string* message) {
  if (message != nullptr) {
    
  } else {
    
  }
  message_.SetAllocated(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), message,
      GetArenaForAllocation());
  // @@protoc_insertion_point(field_set_allocated:helloworld.HelloReply.message)
}

3.gRPC Server

greeter_server.cc

// greeter_server.cc
#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>

#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;

// Logic and data behind the server's behavior.
class GreeterServiceImpl final : public Greeter::Service {
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
};

void RunServer() {
  std::string server_address("0.0.0.0:50051");
  GreeterServiceImpl service;

  ServerBuilder builder;
  // Listen on the given address without any authentication mechanism.
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  // Register "service" as the instance through which we'll communicate with
  // clients. In this case it corresponds to an *synchronous* service.
  builder.RegisterService(&service);
  // Finally assemble the server.
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;

  // Wait for the server to shutdown. Note that some other thread must be
  // responsible for shutting down the server for this call to ever return.
  server->Wait();
}

int main(int argc, char** argv) {
  RunServer();

  return 0;
}
  • 创建服务实现类的实例:GreeterServiceImpl service
  • 创建ServerBuilder类的实例:ServerBuilder builder
  • 使用构建器的AddListeningPort()方法指定我们要用于侦听客户端请求的地址和端口。
  • 向构建器注册我们的服务实现。
  • 调用BuildAndStart()构建器为我们的服务创建和启动 RPC 服务器。
  • 调用Wait()服务器进行阻塞等待,直到进程被杀死或被 Shutdown()调用。

4.gRPC Client

greeter_client.cc

#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>

#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;

class GreeterClient {
 public:
  GreeterClient(std::shared_ptr<Channel> channel)
      : stub_(Greeter::NewStub(channel)) {}

  // Assembles the client's payload, sends it and presents the response back
  // from the server.
  std::string SayHello(const std::string& user) {
    // Data we are sending to the server.
    HelloRequest request;
    request.set_name(user);

    // Container for the data we expect from the server.
    HelloReply reply;

    // Context for the client. It could be used to convey extra information to
    // the server and/or tweak certain RPC behaviors.
    ClientContext context;

    // The actual RPC.
    Status status = stub_->SayHello(&context, request, &reply);

    // Act upon its status.
    if (status.ok()) {
      return reply.message();
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<Greeter::Stub> stub_;
};

int main(int argc, char** argv) {
  // Instantiate the client. It requires a channel, out of which the actual RPCs
  // are created. This channel models a connection to an endpoint (in this case,
  // localhost at port 50051). We indicate that the channel isn't authenticated
  // (use of InsecureChannelCredentials()).
  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;
}
  • 创建一个 gRPC通道,指定我们想要连接的服务器地址和端口:grpc::CreateChannel(
    "localhost:50051", grpc::InsecureChannelCredentials())
  • 使用通道创建一个Greeter service的根存stub,使用从 .proto 生成NewStub的类中提供的方法
  • 通过根存调用服务方法:stub_->.SayHello(user),几乎和调用本地方法一样简单

5.编译示例工程

编译helloworld程序

$ cd examples/cpp/helloworld
$ mkdir -p cmake/build
$ pushd cmake/build
$ cmake -DCMAKE_PREFIX_PATH=$MY_INSTALL_DIR ../..
$ make -j
$ popd

编译所得文件

$ tree grpc/examples/cpp/helloworld/cmake/build/ -L 1
cmake/build/
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── greeter_async_client
├── greeter_async_client2
├── greeter_async_server
├── greeter_callback_client
├── greeter_callback_server
├── greeter_client
├── greeter_server
├── helloworld.grpc.pb.cc       # 生成服务实现类代码
├── helloworld.grpc.pb.h        # 生成服务实现类的头文件 
├── helloworld.pb.cc    # 生成消息实现类代码
├── helloworld.pb.h     # 生产消息实现类头文件
├── libhw_grpc_proto.a
└── Makefile

测试示例

$ cd cmake/build

# 运行服务器
$ ./greeter_server

# 从不同的终端运行客户端并查看客户端输出:
$ ./greeter_client
Greeter received: Hello world

四、更新 gRPC 服务

1.修改.proto文件

对helloworld.proto添加一个SayHelloAgain()具有相同请求和响应类型的新方法:

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

2.重新生成 gRPC 代码

cd examples/cpp/helloworld/cmake/build
make -j
  • 这将重新生成helloworld.pb.{h,cc}and helloworld.grpc.pb.{h,cc},其中包含生成的客户端和服务器类,以及用于填充、序列化和检索我们的请求和响应类型的类。

3.更新应用程序

有新生成的服务器和客户端代码,但仍然需要在示例应用程序的人工编写部分中实现和调用新方法。

  • 服务端实现新的服务类方法

    打开examples/cpp/helloworld/greeter_server.cc,对GreeterServiceImpl类实现新的方法SayHelloAgain()

    class GreeterServiceImpl final : public Greeter::Service {
      Status SayHello(ServerContext* context, const HelloRequest* request,
                      HelloReply* reply) override {
         // ...
      }
    
      Status SayHelloAgain(ServerContext* context, const HelloRequest* request,
                           HelloReply* reply) override {
        std::string prefix("Hello again ");
        reply->set_message(prefix + request->name());
        return Status::OK;
      }
    };
    
  • 客户端增加新的调用方法

    现在存根中提供了一种新方法SayHelloAgain(),将遵循与现有模式相同的模式,SayHello()并添加一个新 SayHelloAgain()方法到GreeterClient

    打开examples/cpp/helloworld/greeter_client.cc。

    class GreeterClient {
     public:
      // ...
      std::string SayHello(const std::string& user) {
         // ...
      }
    
      std::string SayHelloAgain(const std::string& user) {
        // Follows the same pattern as SayHello.
        HelloRequest request;
        request.set_name(user);
        HelloReply reply;
        ClientContext context;
    
        // Here we can use the stub's newly available method we just added.
        Status status = stub_->SayHelloAgain(&context, request, &reply);
        if (status.ok()) {
          return reply.message();
        } else {
          std::cout << status.error_code() << ": " << status.error_message()
                    << std::endl;
          return "RPC failed";
        }
      }
    

    在main()函数中调用这个新方法

    int main(int argc, char** argv) {
      // ...
      std::string reply = greeter.SayHello(user);
      std::cout << "Greeter received: " << reply << std::endl;
    
      reply = greeter.SayHelloAgain(user);
      std::cout << "Greeter received: " << reply << std::endl;
    
      return 0;
    }
    
  • 再次构建客户端和服务器

    cd examples/cpp/helloworld/cmake/build
    make -j
    
    $ ./greeter_server
    
    $ ./greeter_client
    Greeter received: Hello world
    Greeter received: Hello Againworld
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • 核心工作 在一个 .proto 文件内定义服务. 用 protocol buffer 编译器生成服务器和客户端代码...
    迷糊银儿阅读 7,409评论 0 1
  • 1、Micro是一个专注于简化分布式系统开发的微服务生态系统。 2、怎么使用micro 使用go-micro编写一...
    __apple阅读 7,481评论 1 2
  • 参考:https://blog.csdn.net/fengbingchun/article/details/100...
    upup果阅读 2,250评论 0 0
  • 1.简介 在gRPC中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,使您...
    第八共同体阅读 1,872评论 0 6
  • Prerequisites(先决条件) GoGo的三个最新主要版本之一 Protocol buffer 编译器,p...
    panic阅读 499评论 0 0