学习gRPC (三)

news2024/11/19 9:31:07

测试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 RPC
  rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}

  // A client-side streaming RPC 
  rpc StreamHelloReply (stream HelloRequest) returns (HelloReply) {}

  // A bidirectional streaming RPC
  rpc 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 off

set 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 exist
    goto: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"
#endif

using 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"
#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;
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.coloradmin.cn/o/830898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

领域驱动设计(六) - 架构设计浅谈

单用一篇文章很难把这个主题描述的清楚&#xff0c;但为了系列的完整性&#xff0c;笔者会围绕DDD中所介绍的内容做下初步总结&#xff0c;使读者有一个连续性。 一、概述 现在不是局部解决问题的时代了要运用新的技术创造新的效率提升&#xff0c;需要整个商业链条一起前进。…

粉末治金液压系统伺服阀控制器

粉末冶金液压系统是一种应用于粉末治金工艺的液压系统。该系统由液压泵、压力调节器、液压缸、液压管道、电气控制系统等组成。 该系统的优点包括&#xff1a; 工艺动作可靠&#xff1a;粉末冶金液压系统能够精确控制压力和流量&#xff0c;保证工艺动作的可靠性。 提高生产…

分布式存储系统中一致性与可用性核心实战

《高并发系统实战派》-- 你值得拥有 文章目录 副本的喜与忧什么是一致性和可用性&#xff1f;一致性与可用性的较量如何有效权衡&#xff0c;提高系统性能和稳定性&#xff1f;带入实际场景场景案例CAP BASE 双轮指导CAP指导BASE指导 副本的喜与忧 我们要知道&#xff0c;无…

CSDN竞赛68期题解

总结 近几期的题目质量有所提升&#xff0c;数据范围还是一如既往的没给。对于算法题&#xff0c;给定详细的数据范围&#xff0c;规范输入输出&#xff0c;再多给出几个样例以及样例说明&#xff0c;参赛的体验感才会提升。 题目列表 1.小球游戏 题目描述 某台有10个小球的…

[Python] Pylance 插件打开 Python 的类型检查

安装 Python 插件 2.打开一个 Python 文件 可以看到右下角有一个花括号和 Python 字样&#xff0c;点击花括号&#xff08;不是 Python 字样&#xff09;打开类型检查即可&#xff1a;

【问题随记】

ubuntu 14.04源更新(sources.list) deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-update…

echart图表X轴文字太长被隐藏标签解决方案

在Echart图标中&#xff0c;X轴的标签文字间隔默认是自动计算的&#xff0c;在标签文字长度太长的情况下&#xff0c;有可能标签会被隐藏掉&#xff0c;如图 这种显示显然是不符合严谨的业务需求。以下提供三种解决方案 第一种&#xff1a;竖排显示 效果&#xff1a; 在高度一…

汽车EBSE测试流程分析(四):反思证据及当前问题解决

EBSE专题连载共分为“五个”篇章。此文为该连载系列的“第四”篇章&#xff0c;在之前的“篇章&#xff08;三&#xff09;”中已经结合具体研究实践阐述了“步骤二&#xff0c;通过系统调研确定改进方案”等内容。那么&#xff0c;在本篇章&#xff08;四&#xff09;中&#…

web爬虫第五弹 - JS逆向入门(猿人学第一题)

0- 前言 爬虫是一门需要实战的学问。 而对于初学者来说&#xff0c;要想学好反爬&#xff0c;js逆向则是敲门砖。今天给大家带来一个js逆向入门实例&#xff0c;接下来我们一步一步来感受下入门的逆向是什么样的。该案例选自猿人学练习题。猿人学第一题 1- 拿到需求 进入页面…

据说这是最全的,App自动化测试思路总结,从0到1实施...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、编程语言选择 …

Open3d——根据距离参数给点云上渐变色

找了半天网上都没有开源的根据距离进行点云渐变色上色的代码&#xff0c;所以写一个方便自己和大家的使用~ 渐变色生成代码 输入点云shape为[N,3]&#xff0c;我这里使用的是nuScenes数据集&#xff0c;生成的点云使用到中心点的距离作为参数&#xff0c;进行渐变&#xff0c;…

XML(eXtensible Markup Language)

目录 为什么需要XML? 一 XML语法 1.文档声明 2.元素 语法: 3.属性 4.注释 5.CDATA节 二 树结构 三 转义字符 四 DOM4J 1.XML解析技术 2.dom4j介绍 3.dom4j基本使用 XML 指可扩展标记语言&#xff08;eXtensible Markup Language&#xff09;。 XML 被设计用来传…

SQL-每日一题【1179. 重新格式化部门表】

题目 部门表 Department&#xff1a; 编写一个 SQL 查询来重新格式化表&#xff0c;使得新的表中有一个部门 id 列和一些对应 每个月 的收入&#xff08;revenue&#xff09;列。 查询结果格式如下面的示例所示&#xff1a; 解题思路 1.题目要求我们重新格式化表&#xff0c;…

leetcode 1480.一维数组的动态和

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;一维数组的动态和 ps&#xff1a; 动态数组求和其实就是当前 i 位置的值等于 0 - i 的求和&#xff0c;控制好循环条件即可。 代码&#xff1a; /*** Note: The returned array must be malloced, assume caller calls…

4000字,详解数据管理之简介篇

DAMA-DMBOK 2.0既 DAMA 数据管理知识体系指南共分为17章&#xff0c;是从事数据治理人员的参考宝典。也CDMP及CDGA、CDGP认证的考试用书。 我在这里将17个章节根据个人理解分为数据管理简介、数据管理框架、数据处理伦理、数据管理组件、数据管理组织、大数据与数据科学和数据…

阿里云容器服务助力极氪荣获 FinOps 先锋实践者

作者&#xff1a;海迩 可信云评估是中国信息通信研究院下属的云计算服务和软件的专业评估体系&#xff0c;自 2013 年起历经十年发展&#xff0c;可信云服务评估体系已日臻成熟&#xff0c;成为政府支撑、行业规范、用户选型的重要参考。 2022 年 5 月国务院国资委制定印发《…

FTP可能是免费且易于使用,但这就是问题所在

当团队里的某个人发现他们需要马上发送的文件太大&#xff0c;无法通过电子邮件发送时&#xff0c;就会陷入困境。另一个同事开始用电子邮件发送账号或密码&#xff0c;然后意识到&#xff0c;也许电子邮件不够安全&#xff0c;FTP替代&#xff0c;托管文件传输。 FTP可能是免费…

线扫激光算法原理

一:线扫激光算法原理 激光器发出的激光束经准直聚焦后垂直入射到物体表面上,表面的散射光由接收透镜成像于探测器的阵列上。光敏面于接收透镜的光轴垂直。如图: 当被测物体表面移动x,反应到光敏面上像点位移为x’。a为接收透镜到物体的距离(物距),b为接收后主面到成像…

HCIE-Security 安全策略技术——流量处理

一、防火墙安全策略原理及配置 1.包过滤技术 对需要转发的数据包&#xff0c;先获取包头信息&#xff0c;然后和设定的规则进行比较&#xff0c;根据比较结果对数据包进行转发和丢弃 2.包过滤技术进阶——安全策略 包过滤&#xff1a;五元组&#xff0c;ACL 安全策略&#…

主动带宽控制工具

停机和带宽过度使用是任何组织都无法避免的两个问题。随着企业采用 BYOD 文化&#xff0c;通过网络的流量负载可能很重&#xff0c;导致网络拥塞并使网络容易受到网络攻击。为了解决这个问题&#xff0c;企业需要全面的监控策略来保护网络&#xff0c;当看似大量的流量进入网络…