GRPC简介
GRPC 是一个高性能、开源和通用的 RPC 框架,面向服务端和移动端,基于 HTTP/2 设计;
GRPC 默认使用protocol buffers,使用protocol buffers作为IDL和消息交换格式,google开源的成熟的数据序列化机制;
定义服务:通过指定方法调用的参数和返回值来定义,就是使用IDL来 描述你的服务接口和传输消息结构;
gRPC 特点:
①语言中立,支持多种语言;
②基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
③通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
④序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
简单理解:Grpc Server 实现方法,Grpc Client 像调用本地方法一样调用server的方法,Server不主动进行通信,主要依靠client调用来进行交互通信;
准备编译环境
安装Git
作用:下载grpc代码以及相关的第三方库代码。
下载地址:Git
备注:可以不下载安装,因为通过git下载太慢了 在这里都是直接下载zip压缩包,
安装CMake
作用:用来编译,生成grpc.sln
下载:Download CMake
备注:将Cmake 的bin目录配置到系统环境变量Path。
这里我下载的cmake-3.29.0-windows-x86_64.msi
安装Perl
作用:grpc依赖的第三方库依赖的包。
参考:https://jingyan.baidu.com/article/9f7e7ec0b798ae6f281554e9.html
备注:这里下载安装的是ActivePerl_5.16.2.3010812913.msi
说明:
Perl是一种功能丰富、强大而灵活的高级编程语言,由Larry Wall在1987年首次发布。它以其强大的文本处理能力和灵活的语法而闻名,被广泛用于系统管理、文本处理、网络编程和Web开发等领域。尤其擅长文本处理和自动化任务。
官网地址:Perl Download - www.perl.org
Windows版本有:Strawberry Perl和ActiveState Perl
ActiveState Perl和 Strawberry Perl区别是 Strawberry Perl 里面有多包含一些 CPAN 里的模块,所以Strawberry Perl 下载的安装文件有 80多M, 而ActiveState Perl 只有20M 左右。
ActiveState Perl: ActiveState提供了一个免费的社区版本和一个商业支持的Perl用于Win32和Perl的二进制分布。
Strawberry Perl:用于Windows的100%开源Perl,使用来自CPAN的模块不需要二进制包。
个人认为也可以安装Strawberry Perl,如strawberry-perl-5.32.1.1-64bit.msi
Strawberry Perl的官网地址:https://strawberryperl.com/
安装Go
作用:grpc依赖的第三方库依赖的包。
下载地址:Go下载 - Go语言中文网 - Golang中文社区
备注:我这里下载安装的是go1.22.0.windows-amd64.msi
安装yasm
作用:grpc依赖的第三方库依赖的包。
下载地址:http://yasm.tortall.net/Download.html
备注:下载的直接是exe,免安装的,将其名字改为yasm.exe,将yasm.exe所在目录配置到系统环境变量Path。我这里下载的是yasm-1.3.0-win64.exe
安装Visual Studio
下载地址:https://visualstudio.microsoft.com/zh-hans/vs/
注意:windows sdk选择10.0.2以上版本。
备注:我这里下载的是Visual Studio 2019
异议
下装ninja
作用:grpc依赖的第三方库依赖的包。
下载地址:https://github.com/ninja-build/ninja/releases
备注:将ninja.exe所在目录配置到系统环境变量Path。
已安装
说明:
Ninja 是一个高效的构建工具,被广泛用于编译软件项目。
访问 Ninja 的官方网站(https://ninja-build.org/)并下载最新的 Windows 版本的二进制文件。
可选非必须
安装配置openssl库
如果不想使用ssl库,而是使用openssl库。则需自己通过源码编译出openssl库。这一步操作非必须。可参考编译openssl文章。
下载Grpc源码及第三方依赖库
Grpc源码
下载地址:GitHub - grpc/grpc: The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)
我下载的是master分支的,通过code->download zip方式直接下载zip压缩包
第三方依赖库
grpc依赖了大量第三方库,但是光下载grpc源码,还不能完全下载 grpc依赖的库。
解压grpc源码得到 grpc/third_party。把该目录下的所依赖的库文件都通过https://github.com/grpc/grpc/tree/master/third_party下载并解压放到grpc/third_party。
编译GRPC
编译工具选择vs2019 32bit
可选非必须
如果不想使用grpc/third_party的ssl库,而是自己从外面找openssl库,需要先配置Openssl库环境变量。
这个文件夹放到环境变量里面去。新建一个叫OPENSSL_ROOT_DIR的变量名称。因为在cmake-gui工具配置上需设置ssl的路径。
打开CMake工具设置
输入源码目录,编译输出目录
然后点击Configure,然后弹出,选择vs2019 32bit
修改INSTALL生成的路径
INSTALL生成的默认路径如下
如果想在INSTALL设置其他的路径,设置。在这里我设置install 生成在其他路径。
可选非必须
因为默认是生成dynamic_runtime,如果想生成的是静态库来调用的。则gRPC_MSVC_STATIC_RUNTIME、CARES_MSVC_STATIC_RUNTIME、protobuf_MSVC_STATIC_RUNTIME这几个项的前面那个框勾选上。
备注:经测试,如果生成静态库,在qt程序启动无法进去代码断点处,不知道为什么。还是不要勾选为好。
如果是使用openssl的库
则把gRPC_SSL_PROVIDER那个地方的module改成package。如果是使用grpc的third_party/boringssl-with-bazel,则不用改。在这里我不修改,直接使用boringssl-with-bazel
点击Configure。
会提示Configuring done
再点Generate
就生成VS工程文件,在编译vs工程输出目录如E:\grpc\build_32bit,双击打开grpc.sln
ALL_BUILD右键,生成。
在这里选择的是win32 release版本,等待好长一段时间。
单击INSTALL,右键,仅用于项目,仅生成INSTALL
install的路径就是在cmake设置的路径,如果使用默认install路径(C:\Program Files (x86)\grpc),需要是以管理员的身份执行INSTALL
打开INSTALL目录
(这里是E:\grpc\grpcinstall\win32\grpc),就可以看到完整的grpc,这样就可以直接使用了。
C:\Program Files (x86)\grpc是在cmake-gui配置上默认设置的,也可以自定义。
编程使用GRPC
创建.proto
.proto 文件用来定义客户端和服务端的服务接口和消息字段格式。
服务接口
HelloRequest为输入参数,HelloReply为返回值,Greeter 为服务类,服务端必须继承于它。
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
}
消息字段
// 字段后面的数字并不表示该字段的值,只是 proto 程序用来生成相关源代码使用
message HelloRequest {
string name = 1;
}
完整的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) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream 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;
}
处理.proto文件
如命名上面的文件为helloworld.proto,通过执行命令会生成helloworld.grpc.pb.cc、helloworld.grpc.pb.h、helloworld.pb.cc和helloworld.pb.h。服务端和客户端就是通过引用这些文件编写代码.
配置环境变量
把protoc.exe(在grpc的bin目录下)所在的路径添加到环境变量。打开cmd,输入protoc,回车,可看到是否配置成功。
执行cmd命令
打开cmd, 使用cd命令进入到helloworld.proto文件所在的目录(E:\study\grpc\win_32bit_release\hello)。同时把protoc.exe和grpc_cpp_plugin.exe拷贝到该目录,这两个软件在grpc\bin目录下。
执行protoc.exe ./helloworld.proto --cpp_out=./
会生成helloworld.pb.h和helloworld.pb.cc文件
执行protoc.exe --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin.exe helloworld.proto
会生成helloworld.grpc.pb.h和helloworld.grpc.pb.cc文件
服务端编程
使用vs2019 操作编程
以x86 release版本为例。
配置grpc的头文件和依赖库
配置头文件
将grpc的include目录添加进附加包含目录
配置lib库路径
配置依赖库
在VS项目工程的“属性>配置属性>链接器>命令行>其他选项”中,设置"[lib目录]\*.lib "即可在工程中一次性引入全部lib。
处理增加_WIN32_WINNT=0x0A00
项目右键–> 属性–>c/c++ -->预处理器—>预处理定义
运行库模式设置
Release模式选择“多线程(/MD)”
多线程(/MT) :对应的是MD_StaticRelease
多线程(/MTd):对应的是MD_StaticDebug
多线程Dll (/MD) :对应的是MD_DynamicRelease
多线程调试Dll (/MDd) :对应的是MD_DynamicDebug
需要确保你的项目设置与所链接的库的运行库设置相匹配.
添加文件到项目
因为这里直接使用grpc/example的现成例子,greeter_server.cc可以从grpc\examples\cpp\helloworld目录下获取。
把所有的文件拷贝到工程目录中(E:\study\grpc\win_32bit_release\hello),然后在VS中添加helloworld.proto、greeter_server.cc进创建的新项目。
编译代码
然后编译代码,出现一个错误。
解决方案:发现旧版本的container.h并没有这个函数,现在直接把部分代码给屏蔽。
输出目录设置bin/release目录,同时需要把zlib.dll拷贝到输出目录。
运行程序
最后先运行服务端,再运行客户端。
使用qt5.15 编程
使用qt的文件目录如下,依赖的grpc全放在grpc文件夹
配置grpc头文件和依赖库
配置头文件
Qt配置头文件,是配置.pro文件
配置lib库
Qt配置lib库不能像vs设置"[lib目录]\*.lib "加入依赖库。需要手动自己一个一个把所有的lib库加进来。其中libprotoc、libprotobuf-lite、libprotobuf、zlib、zlibstatic这5个库还区分是debug还是release. 另外还需配置WS2_32、Advapi32库。加上预定义DEFINES += _WIN32_WINNT=0x600
如下就是在pro文件配置lib库
CONFIG(debug, debug|release){
DESTDIR += $$PWD/../bin/debug/
gRPC_libpath = $$PWD/../../grpc/lib/debug
LIBS += -L$$gRPC_libpath \
-llibprotocd \
-llibprotobuf-lited \
-llibprotobufd \
-lzlibd \
-lzlibstaticd \
}else{
DESTDIR += $$PWD/../bin/release/
gRPC_libpath = $$PWD/../../grpc/lib/release
LIBS += -L$$gRPC_libpath \
-llibprotoc \
-llibprotobuf-lite \
-llibprotobuf \
-lzlib \
-lzlibstatic \
}
LIBS += -L$$gRPC_libpath \
-labsl_bad_any_cast_impl \
-labsl_bad_optional_access \
-labsl_bad_variant_access \
-labsl_base \
-labsl_city \
-labsl_civil_time \
-labsl_cord \
-labsl_cord_internal \
-labsl_cordz_functions \
-labsl_cordz_handle \
-labsl_cordz_info \
-labsl_cordz_sample_token \
-labsl_crc_cord_state \
-labsl_crc_cpu_detect \
-labsl_crc_internal \
-labsl_crc32c \
-labsl_debugging_internal \
-labsl_demangle_internal \
-labsl_die_if_null \
-labsl_examine_stack \
-labsl_exponential_biased \
-labsl_failure_signal_handler \
-labsl_flags_commandlineflag \
-labsl_flags_commandlineflag_internal \
-labsl_flags_config \
-labsl_flags_internal \
-labsl_flags_marshalling \
-labsl_flags_parse \
-labsl_flags_private_handle_accessor \
-labsl_flags_program_name \
-labsl_flags_reflection \
-labsl_flags_usage \
-labsl_flags_usage_internal \
-labsl_graphcycles_internal \
-labsl_hash \
-labsl_hashtablez_sampler \
-labsl_int128 \
-labsl_kernel_timeout_internal \
-labsl_leak_check \
-labsl_log_entry \
-labsl_log_flags \
-labsl_log_globals \
-labsl_log_initialize \
-labsl_log_internal_check_op \
-labsl_log_internal_conditions \
-labsl_log_internal_fnmatch \
-labsl_log_internal_format \
-labsl_log_internal_globals \
-labsl_log_internal_log_sink_set \
-labsl_log_internal_message \
-labsl_log_internal_nullguard \
-labsl_log_internal_proto \
-labsl_log_severity \
-labsl_log_sink \
-labsl_low_level_hash \
-labsl_malloc_internal \
-labsl_periodic_sampler \
-labsl_random_distributions \
-labsl_random_internal_distribution_test_util \
-labsl_random_internal_platform \
-labsl_random_internal_pool_urbg \
-labsl_random_internal_randen \
-labsl_random_internal_randen_hwaes \
-labsl_random_internal_randen_hwaes_impl \
-labsl_random_internal_randen_slow \
-labsl_random_internal_seed_material \
-labsl_random_seed_gen_exception \
-labsl_random_seed_sequences \
-labsl_raw_hash_set \
-labsl_raw_logging_internal \
-labsl_scoped_set_env \
-labsl_spinlock_wait \
-labsl_stacktrace \
-labsl_status \
-labsl_statusor \
-labsl_str_format_internal \
-labsl_strerror \
-labsl_string_view \
-labsl_strings \
-labsl_strings_internal \
-labsl_symbolize \
-labsl_synchronization \
-labsl_throw_delegate \
-labsl_time \
-labsl_time_zone \
-labsl_vlog_config_internal \
-laddress_sorting \
-lcares \
-lcrypto \
-lgpr \
-lgrpc \
-lgrpc_authorization_provider \
-lgrpc_plugin_support \
-lgrpc_unsecure \
-lgrpc++ \
-lgrpc++_alts \
-lgrpc++_error_details \
-lgrpc++_reflection \
-lgrpc++_unsecure \
-lgrpcpp_channelz \
-lre2 \
-lssl \
-lupb_base_lib \
-lupb_json_lib \
-lupb_mem_lib \
-lupb_message_lib \
-lupb_textformat_lib \
-lutf8_range \
-lutf8_range_lib \
-lutf8_validity \
LIBS += -lWS2_32
LIBS += -lAdvapi32
DEFINES += _WIN32_WINNT=0x600
添加文件到项目
把helloworld.proto、helloworld.pb.h、helloworld.pb.cc、helloworld.grpc.pb.h、helloworld.grpc.pb.cc添加到server项目中,同时greeter_server.cc的代码拷贝到main.cpp文件,并稍微改造一下。
编译代码
然后编译服务端,输出目录设置bin/release目录,同时需要把zlib.dll拷贝到输出目录。
运行程序
最后先运行服务端,再运行客户端。
编程思路
创建继承于服务类的子类
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;
}
注册服务、创建开始服务。
std::string server_address = absl::StrFormat("0.0.0.0:%d", port);
GreeterServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
grpc::reflection::InitProtoReflectionServerBuilderPlugin();
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();
完成的代码:
#include <iostream>
#include <memory>
#include <string>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/strings/str_format.h"
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.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::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;
ABSL_FLAG(uint16_t, port, 50051, "Server port for the service");
// 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(uint16_t port) {
std::string server_address = absl::StrFormat("0.0.0.0:%d", port);
GreeterServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
grpc::reflection::InitProtoReflectionServerBuilderPlugin();
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) {
absl::ParseCommandLine(argc, argv);
RunServer(absl::GetFlag(FLAGS_port));
return 0;
}
客户端编程
使用vs2019 操作编程
配置grpc的头文件和依赖库
和服务端一样的配置
配置头文件
将grpc的include目录添加进附加包含目录
配置lib库路径
配置依赖库
在VS项目工程的“属性>配置属性>链接器>命令行>其他选项”中,设置"[lib目录]\*.lib "即可在工程中一次性引入全部lib。
处理增加_WIN32_WINNT=0x0A00
项目右键–> 属性–>c/c++ -->预处理器—>预处理定义
运行库模式设置
添加文件到项目
因为这里直接使用grpc/example的现成例子,greeter_client.cc可以从grpc\examples\cpp\helloworld目录下获取。
把所有的文件拷贝到工程目录中(E:\study\grpc\win_32bit_release\hello),然后在VS中添加helloworld.proto、greeter_server.cc进创建的新项目。
编译代码
然后编译服务端,输出目录设置bin/release目录,同时需要把zlib.dll拷贝到输出目录。
运行程序
最后先运行服务端,再运行客户端。
使用qt5.15 编程
客户端配置grpc的头文件和依赖库、添加文件到项目、编译程序、运行程序的操作和服务端一样的,这里就不写了。
编程思路
客户端封装成一个自定义类,定义一个独占智能指针std::unique_ptr<MathTest::Stub> stub_;
class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(Greeter::NewStub(channel)) {}
private:
std::unique_ptr<Greeter::Stub> stub_;
};
自定义一个函数,然后通过私有成员变量 stub_ 向服务端发送请求消息并接收返回结果。
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";
}
初始化自定义类对象,通过地址创建实例化stub_对象。
ABSL_FLAG(std::string, target, "localhost:50051", "Server address");
std::string target_str = absl::GetFlag(FLAGS_target);
// We indicate that the channel isn't authenticated (use of
// InsecureChannelCredentials()).
GreeterClient greeter(
grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));
std::string user("world");
std::string reply = greeter.SayHello(user);
std::cout << "Greeter received: " << reply << std::endl;
std::cin.get();//防止客户端一闪而过
完成的代码:
#include <iostream>
#include <memory>
#include <string>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include <grpcpp/grpcpp.h>
#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif
ABSL_FLAG(std::string, target, "localhost:50051", "Server address");
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;
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) {
absl::ParseCommandLine(argc, argv);
// Instantiate the client. It requires a channel, out of which the actual RPCs
// are created. This channel models a connection to an endpoint specified by
// the argument "--target=" which is the only expected argument.
std::string target_str = absl::GetFlag(FLAGS_target);
// We indicate that the channel isn't authenticated (use of
// InsecureChannelCredentials()).
GreeterClient greeter(
grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));
std::string user("world");
std::string reply = greeter.SayHello(user);
std::cout << "Greeter received: " << reply << std::endl;
std::cin.get();//防止客户端一闪而过
return 0;
}
参考
Windows GRPC源码编译C++库——详细步骤_grpc源码下载-CSDN博客
https://blog.csdn.net/w13l14/article/details/118155498?spm=1001.2014.3001.5506
延伸扩展
.proto 简介
.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;//表示定义消息的包名,对于 C++ 程序,消息类将会被包装在命名空间中
服务接口
HelloRequest为输入参数,HelloReply为返回值,Greeter 为服务类,服务端必须继承于它。
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
}
消息字段
// 字段后面的数字并不表示该字段的值,只是 proto 程序用来生成相关源代码使用
message HelloRequest {
string name = 1;
}
关键字
required
required是必须的意思,数据发送方和接收方都必须处理这个字段。
message Person {
required int32 age = 1;
required string name = 2;
}
optional
字面意思是可选的意思,具体protobuf里面怎么处理这个字段呢,就是protobuf处理的时候另外加了一个bool的变量,用来标记这个optional字段是否有值,发送方在发送的时候,如果这个字段有值,那么就给bool变量标记为true,否则就标记为false,接收方在收到这个字段的同时,也会收到发送方同时发送的bool变量,拿着bool变量就知道这个字段是否有值了,这就是option的意思。
Repeated
Repeated是protobuf中的一种限定修饰符,从字面意思看有“重复”的意思,实际上它就是用来指定某一个字段可以存放同一个类型的多个数据,相当于C++中的vector或者Java中的List。
message Family {
repeated Person person = 1;
}
代码中实现关键字
简单数据
xx( )函数为直接获取该变量 xx对应在proto定义的字段
赋值直接采用set_xx()即可,xx对应在proto定义的字段
Person person;
Int age = Person.age ;
Person.set_age = 20;
嵌套数据
嵌套消息即在proto定义了结构体,在c++使用主要对set_allocated_和mutable_的使用
1 使用set_allocated_,赋值的对象需要new出来,不能用局部的,这里保存的是对象的指针。
2 使用mutable_,赋值时候,可以使用局部变量,因为在调用时,内部做了new操作。
在proto定义
message ErrorCode {
string message = 1; //错误信息
string note = 2; //注释
uint32 code = 3; //错误码
}
message HashProjectRet{
ErrorCode errorCode=1;//错误码
string hashValue=2;// hash值
}
在c++代码使用
HashProjectRet *response;
std::string hashvalue = “qwert”;
response->set_hashvalue(hashvalue);
ErrorCode* errorcode = new ErrorCode;
errorcode->set_note("getHashProject");
errorcode->set_code(0);
errorcode->set_message("OK");
response->set_allocated_errorcode(errorcode);
重复数据
重复数据即在proto定义使用了repeated
message Family {
repeated Person person = 1;
}
获取数据
int size = family.person_size();
for( int i=0; i<size; i++)
{
Person psn = family.person(i);
cout <<” 姓名” << psn.name() << ", 年龄 " << psn.age() << endl;
}
设置数据
person = family.add_person();
person->set_age(25);
person->set_name("John");
grpc有四种交互模式
简单模式(Simple RPC)
客户端发起请求并等待服务端响应;
在proto定义为:
rpc SayHello (HelloRequest) returns (HelloReply) {}
具体例子见上面的代码
服务端流式 RPC(Server-side streaming RPC)
客户端发起一个请求到服务端,服务端返回一段连续的数据流响应
在proto定义为:
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
服务端
重写虚函数SayHelloStreamReply
Status SayHelloStreamReply(::grpc::ServerContext* context, const ::helloworld::HelloRequest* request, ::grpc::ServerWriter< ::helloworld::HelloReply>* writer)
{
std::cout << "request:" << request->name() << std::endl;
HelloReply reply;
int i = 1;
while(1)
{
reply.set_message("server stream reply:" + std::to_string(i));
::grpc::WriteOptions options;
writer->Write(reply, options);
Sleep(300);
if (i > 10)
break;
i++;
}
Sleep(3000);
std::cout << "SayHelloStreamReply end" << std::endl;
return Status::OK;
}
延伸:分块传输多个文件
#include <iostream>
#include <fstream>
#define SEND_FILESIZE 1024*1024
for(auto strFilePath : strFilePathList)
{
int epos = strFilePath.lastIndexOf("/");//从后面开始查找
QString strFileName = strFilePath.right(strFilePath.size()-epos-1);
std::string strpath = strFilePath.toStdString();
std::ifstream infile;
infile.open(strpath, std::ifstream::in | std::ifstream::binary| std::ios::ate);//std::ios::ate 先定位到末尾
int nFileSize = infile.tellg();
infile.seekg(0,std::ios::beg);
char data[SEND_FILESIZE];
while(!infile.eof())
{
infile.read(data, SEND_FILESIZE);
FaultLogFileReply reply;
reply.set_filename(strFileName.toStdString());
reply.set_filesize(nFileSize);
int nlen = infile.gcount();
reply.set_filecontent(data,nlen);
vate_lib::ReturnStatusCode *errorcode = new vate_lib::ReturnStatusCode;
errorcode->set_note("getFaultLogFile");
errorcode->set_code(statuscode.nCode);
errorcode->set_message(statuscode.strMessage.toStdString());
reply.set_allocated_statuscode(errorcode);
::grpc::WriteOptions options;
if(!writer->Write(reply, options))
{
break;
}
if(infile.peek() == EOF)
{
break;
}
}
infile.close();
QThread::msleep(10);
}
客户端
自定义一个函数,在该函数中通过Stub调用SayHelloStreamReply函数。
void sayserveralltime()
{
ClientContext context;
HelloRequest request;
request.set_name("request server stream ");
std::unique_ptr<grpc::ClientReader<HelloReply>> reader = stub_->SayHelloStreamReply(&context, request);//异步
HelloReply response;
int count = 0;
while (reader->Read(&response)) //客户端不断read //阻塞 测试过了:服务端没有WritesDone,所以只能等到服务端的函数返回,才退出循环
{
time_t currentTime = time(0);
struct tm* localTime = localtime(¤tTime);
std::cout << localTime->tm_hour << ":" << localTime->tm_min << ":" << localTime->tm_sec << " " << response.message() << std::endl;
}
Status status = reader->Finish();
if (!status.ok())
std::cout << "error:" << status.error_message() << std::endl;
std::cout << "exit sayserveralltime" << std::endl;
}
延伸:分块传输多个文件
ClientContext context;
FaultLogFileRequest request;
request.set_nstationid(nStationID);
request.set_nslotnum(nSlotNum);
FaultLogFileReply response;
std::unique_ptr<::grpc::ClientReader<FaultLogFileReply>> reader = m_stub->getFaultLogFile(&context, request);//异步
QList<std::string> filelist;
std::ofstream outfile;
QString strFileDir = strfiledir;
int nRecvFileSize = 0;
while (reader->Read(&response)) //客户端不断read //阻塞 服务端没有WritesDone,所以只等等到服务端的函数返回,才退出循环
{
int ncode = response.statuscode().code();
if(ncode == 0)
{
std::string strfilename = response.filename();
int nFileSize = response.filesize();
bool bcontains = filelist.contains(strfilename);
if(!bcontains)
{
QString strfilepath = strFileDir + "/" + QString::fromStdString(strfilename);
std::string filepath = strfilepath.toStdString();
outfile.open(filepath, std::ios::binary | std::ios::out | std::ios::trunc);
filelist.append(strfilename);
nRecvFileSize = 0;
}
int nContentLen = response.filecontent().length();
const char *data = response.filecontent().c_str();
outfile.write(data, nContentLen);
nRecvFileSize += nContentLen;
if(nRecvFileSize == nFileSize)
{
outfile.close();
}
QThread::msleep(10);
}
else
{
if(outfile.is_open())
outfile.close();
break;
}
}
Status status = reader->Finish();
if (!status.ok())
std::cout << "error:" << status.error_message() << std::endl;
std::cout << "exit getFaultlog" << std::endl;
return status;
客户端流式 RPC(Client-side streaming RPC)
与服务端流式相反,客户端流式是客户端不断地向服务端发送数据流,最后由服务端返回一个响应。
rpc SayHelloStreamRequest (stream HelloRequest) returns (HelloReply) {}
服务端实现
Status SayHelloStreamRequest(::grpc::ServerContext* context, ::grpc::ServerReader< ::helloworld::HelloRequest>* reader, ::helloworld::HelloReply* response)override
{
HelloRequest request;
while (reader->Read(&request)) //阻塞 需要client运行writedone()才会返回false结束
{
//handle request
std::cout << "request:" << request.name() << std::endl;
}
response->set_message("server only reply");
std::cout << "SayHelloStreamRequest end" << std::endl;
return Status::OK;
}
客户端实现
void sayclientalltime()
{
ClientContext context;
HelloRequest request;
HelloReply response;
std::unique_ptr<::grpc::ClientWriter<HelloRequest>> wRriter = stub_->SayHelloStreamRequest(&context, &response);//异步
grpc::WriteOptions args;
int count = 1;
bool status = false;
//send request //此处可以使用线程同时执行
while (1)
{
request.set_name("request client stream : " + std::to_string(count));
if (wRriter->Write(request, args))
{
}
else
{
break;
}
if (count > 10)
break;
Sleep(500);
++count;
}
wRriter->WritesDone(); //写入结束 如果不执行此函数会导server 出现read一直不结束会阻塞在那
Status st = wRriter->Finish(); //这个函数阻塞 ,直到SayHelloStreamRequest函数返回才执行后面代码
if (!st.ok())
std::cout << "status error:" << st.error_message() << std::endl;
//get response
std::cout << "response:" << response.message() << std::endl;
std::cout << "exit " << std::endl;
}
双向流式 RPC(Bidirectional streaming RPC)
客户端和服务端可同时向对方发送数据流,同时也可以接收数据。
在proto定义:
rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
服务端实现
重写虚函数SayHelloBidiStream
Status SayHelloBidiStream(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::helloworld::HelloReply, ::helloworld::HelloRequest>* stream) override
{
HelloRequest request;
HelloReply response;
int i = 1;
bool status = true;
//在这里我是进行read and write 一起进行的(实际开发中可以添加线程进入同时执行)
while (1)
{
//handle request
if (stream->Read(&request)) //阻塞 需要client运行writedone()返回false结束
{
std::cout << "request:" << request.name() << std::endl;
}
else
{
status = false;
}
if (!status)
break;
//handle response
response.set_message("server BidiStream:" +std::to_string(i));
::grpc::WriteOptions options;
stream->Write(response, options);
i++;
}
std::cout << "SayHelloBidiStream end" << std::endl;
return Status::OK;
}
客户端实现
自定义一个函数,在该函数中通过Stub调用SayHelloBidiStream函数。
void Sayalltime()
{
ClientContext context;
HelloRequest request;
HelloReply response;
std::unique_ptr<::grpc::ClientReaderWriter<HelloRequest, HelloReply>> wRriter = stub_->SayHelloBidiStream(&context);//异步
grpc::WriteOptions args;
int count = 1;
bool status = false;
//send request //此处可以使用线程同时执行
while (1)
{
request.set_name("client BidiStream" + std::to_string(count));
if (wRriter->Write(request, args))
{
if (wRriter->Read(&response)) //阻塞
{
time_t currentTime = time(0);
struct tm* localTime = localtime(¤tTime);
std::cout << localTime->tm_hour << ":" << localTime->tm_min << ":"<< localTime->tm_sec << " "
<< "response:" << response.message() << std::endl;
}
else
break;
}
else
{
break;
}
if (count > 10)
break;
Sleep(500);
++count;
}
wRriter->WritesDone(); //写入结束 避免server read 阻塞
Status st = wRriter->Finish();
if (!st.ok())
std::cout << "error:" << st.error_message() << std::endl;
std::cout << "exit " << std::endl;
}