学习gRPC (三)

测试gRPC例子

  • 编写proto文件
  • 实现服务端代码
  • 实现客户端代码

通过gRPC 已经编译并且安装好之后,就可以在源码目录下找到example 文件夹下来试用gRPC 提供的例子。

在这里我使用VS2022来打开仓库目录下example/cpp/helloworld目录
在这里插入图片描述

编写proto文件

下面是我改写的example/protos/helloworld.proto ,和相应的greeter_client.ccgreeter_server.cc两个文件。这里面定义了一个叫做Greeter的服务,同时这里尝试4种类型的方法。

  1. 简单 RPC ,客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样。
  2. 服务器端流式 RPC , 客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。从例子中可以看出,通过在响应类型前插入 stream 关键字,可以指定一个服务器端的流方法。
  3. 客户端流式 RPC ,客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。通过在请求类型前指定 stream 关键字来指定一个客户端的流方法。
  4. 双向流式 RPC,双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如,服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。 每个流中的消息顺序被预留。你可以通过在请求响应前加 stream 关键字去制定方法的类型。
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 {// A simple RPC rpc SayHello (HelloRequest) returns (HelloReply) {}// A server-side streaming RPCrpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}// A client-side streaming RPC rpc StreamHelloReply (stream HelloRequest) returns (HelloReply) {}// A bidirectional streaming RPCrpc StreamHelloStreamReply (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;
}

当编辑完helloworld.proto文件之后,可以直接点击vs2022->生成->全部重新生成。此时VS会调用protocol buffer 编译器和gRPC C++ plugin生成4个客户端和服务端的文件,所有生成的文件在${projectDir}\out\build\${name}文件夹下。

如下方是我编写的手动生成这4个文件的批处理脚本

@echo offset fileName=temp
set currentPath=%~dp0%
set target=--cpp_out
set protocPath= "Your protoc path"
set pluginPath= "Your c++ plugin path"for %%I in (%*) do (for /f "tokens=1,2 delims=-/:" %%J in ("%%I%") do (if %%J==f (set fileName=%%K)if %%J==t (set target=%%K))
)if not exist "%currentPath%%fileName%" (echo file not existgoto:error
)
if %target%==python (set pluginPath= "Your python plugin path"set target=--python_out
)
if %target%==csharp (set pluginPath= "Your C# plugin path"set target=--csharp_out
)%protocPath% --grpc_out=%currentPath% --plugin=protoc-gen-grpc=%pluginPath% --proto_path=%currentPath% %fileName%
%protocPath% %target%=%currentPath% --proto_path=%currentPath% %fileName% 
:error
pause
exit

真正起作用编译出4个文件的代码是

%protocPath% --grpc_out=%currentPath% --plugin=protoc-gen-grpc=%pluginPath% --proto_path=%currentPath% %fileName%
%protocPath% %target%=%currentPath% --proto_path=%currentPath% %fileName% 

至此${projectDir}\out\build\${name}文件夹下会生成如下4个文件

  • helloworld.pb.h 消息类的头文件
  • helloworld.pb.cc 消息类的实现
  • helloworld.grpc.pb.h 服务类的头文件
  • helloworld.grpc.pb.cc 服务类的实现

同时里面还包含所有的填充、序列化和获取请求和响应消息类型的 protocol buffer 代码,和一个名为Greeter的类,这个类中包含了:

  • 方便客户端调用的存根
  • 需要服务端实现的虚接口

实现服务端代码

以下是greeter_server.cc

#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"
#endifusing grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using grpc::ServerWriter;
using grpc::ServerReader;
using grpc::ServerReaderWriter;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;using std::string;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::cout << "--------" << __FUNCTION__ << "--------" << std::endl;string strName = request->name();reply->set_message("Hello : " + strName);std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;return Status::OK;}Status SayHelloStreamReply(ServerContext* context, const HelloRequest* request, ServerWriter<HelloReply>* writer){std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;string strName = request->name();HelloReply reply;reply.set_message("Hello " + strName);writer->Write(reply);for (unsigned int i = 0; i < 10; i++){reply.clear_message();reply.set_message("This is Server Reply : " + std::to_string(i));writer->Write(reply);}std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;return Status::OK;}Status StreamHelloReply(ServerContext* context, ServerReader<HelloRequest>* reader, HelloReply* response){std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;HelloRequest req;while (reader->Read(&req)){std::cout << "Server got what you said " << req.name() << std::endl;}response->set_message("Server got what you said " + req.name());std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;return Status::OK;}Status StreamHelloStreamReply(ServerContext* context, ServerReaderWriter< HelloReply, HelloRequest>* stream){std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;HelloRequest req;HelloReply reply;unsigned long uiCount = 0ul;while (stream->Read(&req)){std::cout << "Server got what you said " << req.name() << std::endl;reply.clear_message();reply.set_message("This is Server count " + std::to_string(uiCount++));stream->Write(reply);}std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;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;
}

可以看到,里面有两部分代码。一部分是真正实现服务接口内在逻辑的代码,逻辑都在GreeterServiceImpl 这个类中。另一部分是运行一个服务,并使它监听固定端口的代码,逻辑都在RunServer这个函数中。

实现客户端代码

以下是greeter_client.cc

#include <iostream>
#include <memory>
#include <string>
#include <chrono>
#include <thread>#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"
#endifABSL_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;
using std::thread;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.void SayHello(const std::string& user){std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;// 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()) {std::cout << "Server said " << reply.message() << std::endl;} else {status.error_message();std::cout << status.error_code() << ": " << status.error_message()<< std::endl;}std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;}void SayHelloStreamReply(const std::string& user){std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;// Data we are sending to the server.HelloRequest request;request.set_name(user);// Context for the client. It could be used to convey extra information to// the server and/or tweak certain RPC behaviors.ClientContext context;HelloReply reply;// The actual RPC.auto Reader = stub_->SayHelloStreamReply(&context, request);while (Reader->Read(&reply)){std::cout << reply.message() << std::endl;}if (!Reader->Finish().ok()){std::cout << Reader->Finish().error_code() << ": " << Reader->Finish().error_message()<< std::endl;std::cout <<  "RPC failed" << std::endl;}std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;}void StreamHelloReply(const std::string& user){std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;HelloReply reply;ClientContext context;auto Writer = stub_->StreamHelloReply(&context, &reply);for (unsigned int i = 0; i < 10; i++){// Data we are sending to the server.HelloRequest request;std::string strName = user + std::to_string(i);request.set_name(strName);Writer->Write(request);}Writer->WritesDone();if (!Writer->Finish().ok()){std::cout << Writer->Finish().error_code() << ": " << Writer->Finish().error_message()<< std::endl;std::cout << "RPC failed" << std::endl;}else{std::cout << reply.message() << std::endl;}std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;}void StreamHelloStreamReply(const std::string& user){std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;HelloReply reply;ClientContext context;std::shared_ptr< ::grpc::ClientReaderWriter< ::helloworld::HelloRequest, ::helloworld::HelloReply>> Stream = stub_->StreamHelloStreamReply(&context);thread t1([Stream, user]() {for (unsigned int i = 0; i < 20; i++){// Data we are sending to the server.HelloRequest request;std::string strName = user + std::to_string(i);request.set_name(strName);Stream->Write(request);}Stream->WritesDone();});while (Stream->Read(&reply)){std::cout << reply.message() << std::endl;}t1.join();if (!Stream->Finish().ok()){std::cout << Stream->Finish().error_code() << ": " << Stream->Finish().error_message()<< std::endl;std::cout << "RPC failed" << std::endl;}std::cout << "--------" << __FUNCTION__ << "--------" << std::endl;}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 strName = "User";greeter.SayHello(strName);Sleep(500);greeter.SayHelloStreamReply(strName);Sleep(500);greeter.StreamHelloReply(strName);Sleep(500);greeter.StreamHelloStreamReply(strName);return 0;
}

客户端代码想要调用服务必须要使用到存根,所以我们可以在代码里看到std::unique_ptr<Greeter::Stub> stub_。这样我们在客户端调用时,就像是调用一个本地方法一样。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/54057.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端主题切换方案——CSS变量

前言 主题切换是前端开发中老生常谈的问题&#xff0c;本文将介绍主流的前端主题切换实现方案——CSS变量 CSS变量 简介 编写CSS样式时&#xff0c;为了避免代码冗余&#xff0c;降低维护成本&#xff0c;一些CSS预编译工具&#xff08;Sass/Less/Stylus&#xff09;等都支…

使用ChatGPT编写技术文档

技术文档对于任何项目都是至关重要的&#xff0c;因为它确保所有利益相关者都在同一层面上&#xff0c;并允许有效的沟通和协作。创建详细而准确的技术文档可能既耗时又具有挑战性&#xff0c;特别是对于那些不熟悉主题或缺乏强大写作技巧的人来说。ChatGPT 是一个强大的人工智…

go编译文件

1.编译go文件 go build [go文件]2.执行文件编译文件 ./demo [demo为go文件名称]

4个顶级的支持消费级硬件的NeRF软件平台

似乎每天都有大量的创新发布&#xff0c;人们很容易感到不知所措。因此&#xff0c;让我们放慢脚步&#xff0c;看看4个主流的支持消费级硬件的NeRF 平台。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 1、Instant-NGP&#xff08;Instant-NeRF&#xff09; 2022 年…

Word中如何断开表格中线段

Word中如何断开表格中线段_word表格断线怎么弄_仰望星空_LiDAR的博客-CSDN博客有时候为了美观&#xff0c;需要实现如下的效果&#xff0c;即第2条线段被断开成3段步骤如下&#xff1a;选中需要断开的格网&#xff0c;如下&#xff0c;再选择段落、针对下框标即可。_word表格断…

VR全景在建筑工程行业能起到哪些作用?

在建筑工程领域&#xff0c;数字化技术为行业的发展起到巨大的推动作用&#xff0c;虽然建筑施工行业主要是依赖于工人劳动力和施工设备&#xff0c;但是VR全景在该行业中方方面面都能应用&#xff0c;从设计建模到项目交付&#xff0c;帮助建筑师以及项目方更好的理解每个环节…

使用nativephp开发桌面应用测试

2023年7月21日10:29:03 官网&#xff1a;https://nativephp.com/ 源码&#xff1a;https://github.com/NativePHP/laravel 看起像laravel团队的作品 安装&#xff1a;注意需要php8.1以上&#xff0c;laravel10以上 composer create-project laravel/laravel example-app或者&am…

(四)Node.js - npm与包

1. 什么是包 Node.js中的第三方模块又叫做包。 不同于Node.js中的内置模块与自定义模块&#xff0c;包是由第三方个人或团队开发出来的&#xff0c;免费供所有人使用。 由于Node.js的内置模块进提供了一些底层的API&#xff0c;导致在基于内置模块进行项目开发时&#xff0c…

【Linux后端服务器开发】poll/epoll多路转接IO服务器

目录 一、poll原理 二、poll实现多路转接IO服务器 三、epoll函数接口 四、epoll的工作原理 五、epoll实现多路转接IO服务器 一、poll原理 poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构 struct pollfd …

使用Gunicorn+Nginx部署Flask项目

部署-开发机上的准备工作 确认项目没有bug。用pip freeze > requirements.txt将当前环境的包导出到requirements.txt文件中&#xff0c;方便部署的时候安装。将项目上传到服务器上的/srv目录下。这里以git为例。使用git比其他上传方式&#xff08;比如使用pycharm&#xff…

代码随想录算法训练营day25 | 216. 组合总和 III,17. 电话号码的字母组合

目录 216. 组合总和 III 17. 电话号码的字母组合 216. 组合总和 III 难度&#xff1a;medium 类型&#xff1a;回溯 思路&#xff1a; 与77组合类似的题目。 代码随想录算法训练营day24 | 回溯问题&#xff0c;77. 组合_Chamberlain T的博客-CSDN博客 注意两处剪枝。 代码…

Vue表格导出Excel数据,自定义表头,使用xlsx-style修饰

继续上篇文章封装导出方法: 效果图&#xff1a; 1、安装xlsx-style依赖&#xff1a; yarn add xlsx-style 2、安装node-polyfill-webpack-plugin依赖&#xff1a; yarn add node-polyfill-webpack-plugin -D 解决报错&#xff1a;jszip is not a constructor 3、配置vue.…