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 消息
  • 定義 SearchRequest、SearchResponse 消息,前者定義了三個字段,每一個字段包含三個屬性:類型、字段名稱、字段編號。
  • 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
    
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 核心工作 在一個 .proto 文件內定義服務. 用 protocol buffer 編譯器生成服務器和客戶端代碼...
    迷糊銀兒閱讀 7,543評論 0 1
  • 1、Micro是一個專注于簡化分布式系統開發的微服務生態系統。 2、怎么使用micro 使用go-micro編寫一...
    __apple閱讀 7,603評論 1 2
  • 參考:https://blog.csdn.net/fengbingchun/article/details/100...
    upup果閱讀 2,292評論 0 0
  • 1.簡介 在gRPC中,客戶端應用程序可以直接調用不同計算機上的服務器應用程序上的方法,就像它是本地對象一樣,使您...
    第八共同體閱讀 1,939評論 0 6
  • Prerequisites(先決條件) GoGo的三個最新主要版本之一 Protocol buffer 編譯器,p...
    panic閱讀 530評論 0 0