当前位置: 首页 > news >正文

windows系统下编译和使用grpc

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;

  }

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 代码随想录——打家劫舍Ⅱ(Leetcode 213)
  • 对JAVA的包package的理解
  • 2024华为数通HCIP-datacom最新题库(H12-831变题更新⑧)
  • rocketmq普通消息-消息类型
  • idea中maven无法下载内网自建的Nexus私服中的依赖
  • 优化冗余代码:提升前端项目开发效率的实用方法
  • 搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(含代码示例)
  • 了解郑州自闭症寄宿学校:提供专业康复服务与关怀
  • 《昇思25天学习打卡营第24天》
  • Springboot 开发之 RestTemplate 简介
  • 微信小程序-获取手机号:HttpClientErrorException: 412 Precondition Failed: [no body]
  • 人工智能与机器学习原理精解【11】
  • 【Git】git stash
  • 解决 Git 访问 GitHub 时的 SSL 错误
  • 等保测评与《网络安全法》的深度融合
  • 【个人向】《HTTP图解》阅后小结
  • android 一些 utils
  • Laravel Telescope:优雅的应用调试工具
  • STAR法则
  • Vue2.x学习三:事件处理生命周期钩子
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 测试开发系类之接口自动化测试
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 近期前端发展计划
  • 模型微调
  • 软件开发学习的5大技巧,你知道吗?
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • # Java NIO(一)FileChannel
  • # 手柄编程_北通阿修罗3动手评:一款兼具功能、操控性的电竞手柄
  • #if等命令的学习
  • #Linux(帮助手册)
  • #职场发展#其他
  • (06)金属布线——为半导体注入生命的连接
  • (42)STM32——LCD显示屏实验笔记
  • (C语言)二分查找 超详细
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (备忘)Java Map 遍历
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (四)鸿鹄云架构一服务注册中心
  • (五)Python 垃圾回收机制
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转) 深度模型优化性能 调参
  • **PHP二维数组遍历时同时赋值
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .babyk勒索病毒解析:恶意更新如何威胁您的数据安全
  • .net 4.0发布后不能正常显示图片问题
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .net 设置默认首页
  • .Net调用Java编写的WebServices返回值为Null的解决方法(SoapUI工具测试有返回值)
  • .NET企业级应用架构设计系列之技术选型
  • [ vulhub漏洞复现篇 ] Grafana任意文件读取漏洞CVE-2021-43798
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce