一、基礎概念
1.RPC
RPC 代指遠程過程調用(Remote Procedure Call),它的調用包含了傳輸協議和編碼(對象序列號)協議等等。允許運行于一臺計算機的程序調用另一臺計算機的子程序,而開發人員無需額外地為這個交互作用編程。)
1)RPC 框架
一個完整的 RPC 框架,應包含負載均衡、服務注冊和發現、服務治理等功能,并具有可拓展性便于流量監控系統等接入。
常見 RPC 框架:
\ | 跨語言 | 多 IDL | 服務治理 | 注冊中心 | 服務管理 |
---|---|---|---|---|---|
gRPC | √ | × | × | × | × |
Thrift | √ | × | × | × | × |
Rpcx | × | √ | √ | √ | √ |
Dubbo | × | √ | √ | √ | √ |
2)RPC優點
簡單、通用、安全、效率。
3)RPC 生命周期
-
一元RPC
首先考慮客戶端發送單個請求并返回單個響應的最簡單的 RPC 類型。
-
服務器流式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 的默認行為,例如打開或關閉消息壓縮。通道具有狀態,包括
connected
和idle
。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)通信過程
客戶端(gRPC Sub)調用 A 方法,發起 RPC 調用
對請求信息使用 Protobuf 進行對象序列化壓縮(IDL)
服務端(gRPC Server)接收到請求后,解碼請求體,進行業務邏輯處理并返回
對響應結果使用 Protobuf 進行對象序列化壓縮(IDL)
客戶端接受到服務端響應,解碼請求體。回調被調用的 A 方法,喚醒正在等待響應(阻塞)的客戶端調用并返回響應結果
3)服務定義
gRPC 允許定義四種服務方法
-
一元 RPC,客戶端向服務器發送單個請求并返回單個響應,就像普通的函數調用一樣。
- 數據包過大會造成瞬時壓力
- 接收數據包時,需要所有數據包都接受成功且正確后,才能夠回調響應,進行業務處理(無法客戶端邊發送,服務端邊處理)
rpc SayHello(HelloRequest) returns (HelloResponse);
-
服務器流式 RPC,客戶端向服務器發送請求并獲取流以讀取一系列消息。客戶端從返回的流中讀取,直到沒有更多消息。gRPC 保證單個 RPC 調用中的消息排序。
imagerpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
-
客戶端流式 RPC,單向流,客戶端通過流式發起多次 RPC 請求給服務端,服務端發起一次響應給客戶端。
imagerpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
-
雙向流式 RPC,由客戶端以流式的方式發起請求,服務端同樣以流式的方式響應請求
首個請求一定是 Client 發起,但具體交互方式(誰先誰后、一次發多少、響應多少、什么時候關閉)根據程序編寫的方式來確定(可以結合協程)。
imagerpc 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}
andhelloworld.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