|
一、基础概念
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 的默认行为,例如打开或关闭消息压缩。通道具有状态,包括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)通信过程
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/grpcgithub.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 或更高版本的cmakesudo 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 -versioncmake 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$ popd2.安装Protocol Buffers v3
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protobuf-all-3.19.1.zipunzip protobuf-all-3.19.1.zipcd protobuf-3.19.1/./configuremakemake installexport LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH# 查看版本$ protoc --versionlibprotoc 3.19.1三、构建并使用C++ gRPC示例
1.示例程序目录
基于https://github.com/grpc/grpc
$ tree grpc/examples/cpp/ -L 2examples/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.proto2.使用协议缓冲区文件
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 greetingsmessage HelloReply { string message = 1;}
协议缓冲区文件通过protoc工具生成服务类和消息类的代码
通过CMakeLists方式
project(HelloWorld C CXX)include(../cmake/common.cmake)# Proto fileget_filename_component(hw_proto "../../protos/helloworld.proto" ABSOLUTE)get_filename_component(hw_proto_path "${hw_proto}" PATH)# Generated sourcesset(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 = protocGRPC_CPP_PLUGIN = grpc_cpp_pluginGRPC_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_INLINEvoid 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_INLINEvoid 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"#endifusing 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"#endifusing 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 1cmake/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_clientGreeter 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 greetingsmessage HelloReply { string message = 1;}2.重新生成 gRPC 代码
cd examples/cpp/helloworld/cmake/buildmake -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/buildmake -j$ ./greeter_server$ ./greeter_clientGreeter received: Hello worldGreeter received: Hello Againworld
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|