十八、Rust gRPC 多 proto 演示

十八、Rust gRPC 多 proto 演示

  网上及各官方资料,基本是一个 proto 文件,而实际项目,大多是有层级结构的多 proto 文件形式,本篇文章 基于此诉求,构建一个使用多 proto 文件的 rust grpc 使用示例。

关于 grpc 的实现,找到两个库:

  • Tonic:https://github.com/hyperium/tonic,8.9k Star、852 Commits、2024-03-12 updated。

  • PingCAP 的 grpc-rs:https://github.com/tikv/grpc-rs,1.8k Star、357 Commits、2023-08 updated。

  据说 grpc-rs 的 benchmark 稍高一些,但看关注度和提交量不如 tonic,且据说 tonic 开发体验更好一些,本篇以 tonic 为例。

编译 Protobuf,还需要 protoc,可以参考官方文档,这里先给出 macOS 的:

  • brew install protobuf
  • https://grpc.io/docs/protoc-installation/

关于 Tonic:Tonic 是基于 HTTP/2 的 gRPC 实现,专注于高性能,互通性和灵活性;

目录说明

.
├── Cargo.toml
├── README.md
├── build.rs
├── proto
│   ├── basic
│   │   └── basic.proto
│   ├── goodbye.proto
│   └── hello.proto
└── src├── bin│   ├── client.rs│   └── server.rs├── lib.rs└── proto-gen├── basic.rs├── goodbye.rs└── hello.rs
  • build.rs 存放通过 proto 生成 rs 的脚本;
  • proto 目录放置 grpc 的 proto 文件,定义服务和消息体;
  • src 常规意义上的项目源码目录;
    • proto-gen 目录存放 build.rs 编译 proto 后生成的 rs 文件;
    • lib.rs 引入 proto 的 rs 文件;
    • bin 目录下进行 proto 所定义服务的实现,此例为 客户端、服务端 的实现;

创建项目

  • 首先创建一个 lib 项目:
cargo new grpc --lib
  • 并按前面的说明,在 src/bin 目录下,进行 client 及 server 的实现。

  • Cargo.toml

[package]
name = "grpc"
version = "0.1.0"
edition = "2021"
description = "A demo to learn grpc with tonic."# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[[bin]]
name="server"
path="src/bin/server.rs"[[bin]]
name="client"
path="src/bin/client.rs"[dependencies]
prost = "0.12.3"
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
tonic = "0.11.0"[build-dependencies]
tonic-build = "0.11.0"

定义服务

  • grpc/proto/basic/basic.proto
syntax = "proto3";package basic;message BaseResponse {string message = 1;int32 code = 2;
}
  • grpc/proto/hello.proto
syntax = "proto3";import "basic/basic.proto";package hello;service Hello {rpc Hello(HelloRequest) returns (HelloResponse) {}
}message HelloRequest {string name = 1;
}message HelloResponse {string data = 1;basic.BaseResponse message = 2;
}
  • grpc/proto/goodbye.proto
syntax = "proto3";import "basic/basic.proto";package goodbye;service Goodbye {rpc Goodbye(GoodbyeRequest) returns (GoodbyeResponse) {}
}message GoodbyeRequest {string name = 1;
}message GoodbyeResponse {string data = 1;basic.BaseResponse message = 2;
}

配置编译

  Rust 约定:在 build.rs 中定义的代码,会在编译真正项目代码前被执行,因此 可以在这里先编译 protobuf 文件;

  • grpc/Cargo.toml 引入
[build-dependencies]
tonic-build = "0.11.0"
  • grpc/build.rs
use std::error::Error;
use std::fs;static OUT_DIR: &str = "src/proto-gen";fn main() -> Result<(), Box<dyn Error>> {let protos = ["proto/basic/basic.proto","proto/hello.proto","proto/goodbye.proto",];fs::create_dir_all(OUT_DIR).unwrap();tonic_build::configure().build_server(true).out_dir(OUT_DIR).compile(&protos, &["proto/"])?;rerun(&protos);Ok(())
}fn rerun(proto_files: &[&str]) {for proto_file in proto_files {println!("cargo:rerun-if-changed={}", proto_file);}
}

稍作解释:

  • OUT_DIR 全局定义 proto 文件编译后的输出位置(默认在 target/build 目录下)。
  • let protos = [...] 声明了所有待编译 proto 文件。
  • tonic_build::configure()
    • .build_server(true) 是否编译 server 端,项目以 proto 为基准,则编就完了。
    • .compile(&protos, &["proto/"])?; 开始编译。

最终生成:

  • grpc/src/proto-gen/
    • basic.rs、hello.rs、goodbye.rs

由 proto 生成的原代码,内容一般较长,这里不贴出,感兴趣的读者,运行一下就可以看到。另外翻看其代码,可以看到:

  • 为客户端生成的HelloClient类型:impl<T> HelloClient<T> 实现了CloneSyncSend,因此可以跨线程使用。
  • 为服务端生成的 HelloServer 类型:impl<T: Hello> HelloServer<T> {} 包含了 impl<T: Hello>,预示着我们创建 HelloServer 实现,假设为 HelloService 时,需实现该 Hello Trait

引入proto生成的文件

  • grpc/src/lib.rs
#![allow(clippy::derive_partial_eq_without_eq)]pub mod basic {include!("./proto-gen/basic.rs");
}pub mod hello {include!("./proto-gen/hello.rs");
}pub mod goodbye {include!("./proto-gen/goodbye.rs");
}
  • 这里使用了标准库提供的 include! 来引入源文件;

  • 如果没有定义 proto 编译输出位置的话,默认是在 target/build 目录下,此时需要使用 tonic 提供的 include_proto!("hello") 宏,来引入对应文件,而不用额外提供路径了,其中的 hello 为 grpc 的 “包名”,具体来说就是:

    • 注释掉 grpc/build.rs.out_dir(OUT_DIR) 一行。
    • grpc/src/lib.rs 中:
      • include!("./proto-gen/basic.rs"); 改为 include_proto!("basic");
      • include!("./proto-gen/hello.rs"); 改为 include_proto!("hello");
      • include!("./proto-gen/goodbye.rs"); 改为 include_proto!("goodbye");
    • 但这样,在进行 server、client 实现、源码编写时,将无法正常引用,致使大量 “漂红” (只 IDE 下这样,如 CLion,不影响 shell 下编译及运行) 。
  • 参考官方文档:https://docs.rs/tonic/latest/tonic/macro.include_proto.html

服务实现

  服务端实现各语言基本类似,为对应 proto 定义,创建相应的 Service 实现即可:

  • grpc/src/bin/server.rs
use tonic::{Request, Response, Status};
use tonic::transport::Server;use grpc::basic::BaseResponse;
use grpc::goodbye::{GoodbyeRequest, GoodbyeResponse};
use grpc::goodbye::goodbye_server::{Goodbye, GoodbyeServer};
use grpc::hello;
use hello::{HelloRequest, HelloResponse};
use hello::hello_server::{Hello, HelloServer};#[derive(Default)]
pub struct HelloService {}#[tonic::async_trait]
impl Hello for HelloService {async fn hello(&self, req: Request<HelloRequest>) -> Result<Response<HelloResponse>, Status> {println!("hello receive request: {:?}", req);let response = HelloResponse {data: format!("Hello, {}", req.into_inner().name),message: Some(BaseResponse {message: "Ok".to_string(),code: 200,}),};Ok(Response::new(response))}
}#[derive(Default)]
pub struct GoodbyeService {}#[tonic::async_trait]
impl Goodbye for GoodbyeService {async fn goodbye(&self,req: Request<GoodbyeRequest>,) -> Result<Response<GoodbyeResponse>, Status> {println!("goodbye receive request: {:?}", req);let response = GoodbyeResponse {data: format!("Goodbye, {}", req.into_inner().name),message: Some(BaseResponse {message: "Ok".to_string(),code: 200,}),};Ok(Response::new(response))}
}#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {let addr = "0.0.0.0:50051".parse()?;println!("server starting at: {}", addr);Server::builder().add_service(HelloServer::new(HelloService::default())).add_service(GoodbyeServer::new(GoodbyeService::default())).serve(addr).await?;Ok(())
}
  • grpc/src/bin/client.rs
use tonic::Request;
use tonic::transport::Endpoint;use grpc::goodbye::goodbye_client::GoodbyeClient;
use grpc::goodbye::GoodbyeRequest;
use grpc::hello;
use hello::hello_client::HelloClient;
use hello::HelloRequest;#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {let addr = Endpoint::from_static("https://127.0.0.1:50051");let mut hello_cli = HelloClient::connect(addr.clone()).await?;let request = Request::new(HelloRequest {name: "tonic".to_string(),});let response = hello_cli.hello(request).await?;println!("hello response: {:?}", response.into_inner());let mut goodbye_cli = GoodbyeClient::connect(addr).await?;let request = Request::new(GoodbyeRequest {name: "tonic".to_string(),});let response = goodbye_cli.goodbye(request).await?;println!("goodbye response: {:?}", response.into_inner());Ok(())
}

运行及测试

cargo run --bin server
cargo run --bin client

故障时重新编译:cargo clean && cargo build

关于 Github Action

  • 需添加步骤
- name: Install protocrun: sudo apt-get install -y protobuf-compiler

  完事 ~~

参考资料:

  • Rust grpc 实现 - https://jasonkayzk.github.io/2022/12/03/Rust%E7%9A%84GRPC%E5%AE%9E%E7%8E%B0Tonic/

  • Tonic 流式 grpc - https://github.com/hyperium/tonic/blob/master/examples/routeguide-tutorial.md

  • 开源库 - https://github.com/tokio-rs/prost

  • Tonic - https://github.com/hyperium/tonic

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

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

相关文章

高德定位 SDK 到底提供了什么服务?

最近我被高德的销售烦到不行&#xff0c;说是我用了他们的 SDK&#xff0c;现在 SDK 要收费。 表达是很绕的&#xff0c;什么授权啦&#xff0c;什么企业认证风险啦&#xff0c;讲了一堆&#xff0c;还跟我开始搬出协议了。感觉高德的销售真够垃圾的&#xff0c;编个话术都不会…

Lua环境下载与配置

这里介绍如何下载已经编译好的Lua环境&#xff0c;如何配置Lua环境。 如希望自己从源码编译Lua环境&#xff0c;请自行搜索资料。 第一步&#xff1a;下载编译好的lua环境 打开下面链接&#xff0c;然后根据指引下载。 The Programming Language Luahttps://www.lua.org/hom…

windows下基于python语言的TTS开发

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享,包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概述Visual St…

算法打卡day33|动态规划篇01|动态规划理论基础| Leetcode 509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

动态规划理论 定义 动态规划(Dynamic Programming&#xff0c;简称DP)&#xff0c;主要用于解决多阶段决策问题。它的核心思想是将一个复杂的多阶段问题转化为一系列相对简单的单阶段问题&#xff0c;然后逐一求解这些单阶段问题&#xff0c;最后将这些单阶段问题的解合并&…

坦克大战_java源码_swing界面_带毕业论文

一. 演示视频 坦克大战_java源码_swing界面_带毕业论文 二. 实现步骤 完整项目获取 https://githubs.xyz/y22.html 部分截图 启动类是 TankClinet.java&#xff0c;内置碰撞检测算法&#xff0c;线程&#xff0c;安全集合&#xff0c;一切皆对象思想等&#xff0c;是java进阶…

【vue2+antvx6】报错Cannot read properties of undefined (reading ‘toUpperCase‘)

我的代码是这样的 <el-collapseref"collapse"v-model"active"accordionclass"collapseStart"change"collapsechange"><el-collapse-item:name"String(index 1)"v-for"(i, index) in List":key"in…

杂货铺 | 使用 Github Pages 和 Hexo 搭建自己的独立博客

文章目录 &#x1f4da;Step1&#xff1a;安装Node.js和Git&#x1f4da;Step2&#xff1a;安装并初始化配置Hexo&#x1f4da;Step3&#xff1a;本地查看效果&#x1f4da;Step4&#xff1a;将博客部署到Github Pages上&#x1f407;创建项目代码库&#x1f407;配置SSH密钥&a…

本题解答错误----暂做记录----130. 被围绕的区域

130. 被围绕的区域 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;_130被围绕的区域_dfs_130被围绕的区域_bfs_01 错误经验吸取 原题链接&#xff1a; 添加链接描述130. 被围绕的区域 https://leetcode.cn/problems/surrounded-regions…

JavaSE:抽象类和接口

目录 一、前言 二、抽象类 &#xff08;一&#xff09;抽象类概念 &#xff08;二&#xff09;使用抽象类的注意事项 &#xff08;三&#xff09;抽象类的作用 三、接口 &#xff08;一&#xff09;接口概念 &#xff08;二&#xff09;接口语法规则 &#xff08;三&a…

Leetcode 423. 从英文中重建数字

给你一个字符串 s &#xff0c;其中包含字母顺序打乱的用英文单词表示的若干数字&#xff08;0-9&#xff09;。按 升序 返回原始的数字。 示例 1&#xff1a; 输入&#xff1a;s “owoztneoer” 输出&#xff1a;“012” 示例 2&#xff1a; 输入&#xff1a;s “fviefur…

jakarta 实现 HttpClient

正在使用quarkus开发项目。使用jdk17&#xff0c;过去的HttpClient不能用了&#xff0c;经过一番周折&#xff0c;重新实现了HttpClient简单版&#xff0c;代码如下 import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.client.Client; import jaka…

视频创作者必备应用!三步帮你解决前置内容条件,打造专属大片!

你还没尝试过AI视频生成&#xff1f;AI技术逐渐浸透我们的生活&#xff0c;太多的事物变得有了捷径可走。AI视频生成为创作者们提供更加便利快捷的输出形式&#xff0c;更优质更契合的生产内容。尝试AI视频生成系列应用&#xff0c;迎接AI宇宙&#xff01; 生成剧本分镜 视频…

Unity Toggle组件

Toggle Group组件 Allow Switch Off属性值为false时&#xff0c; 1&#xff0c;Toggle初始时默认会有一个被勾选&#xff08;ison为true&#xff09;&#xff0c;可以自己打勾指定 2&#xff0c;不能取消勾选 Allow Switch Off属性值为true时&#xff0c; 1&#xff0c;Toggl…

抖音招聘如何报白?

抖音招聘如何报白&#xff1f; 人力资源行业报白的好处&#xff0c;最直接的就是在直播和视频中发布招聘和企业信息&#xff0c;不用担心被封禁和限制流量。 可以通过抖音直播进行招聘&#xff0c;也可以在视频中添加小程序&#xff0c;流量不会受到限制&#xff0c;等于多了…

海德堡MC8B Kbox C101工业电脑维修印刷机工控机HEIDELBERG主机

海德堡推出具有增强自动化和人体工程学设计的新型印刷机 海德堡推出了新一代 Speedmaster SX 102&#xff0c;据称该打印机通过人工智能和其他自动化辅助系统进行了增强&#xff0c;可提供符合人体工程学的用户体验和高质量的商业印刷。 据海德堡介绍&#xff0c;Speedmaster…

LabVIEW专栏五、网口

该节目标编写一个网口调试VI。 上一章是串口&#xff0c;这章介绍网口的写法。 一、网口硬件 1.1、上位机网口 1.2、网口线 由线缆和水晶头组成&#xff0c;现在一般用5类和超5类的网线 1.3、接线方式 忽略&#xff0c;这里加上这点为了提醒一个硬件和上位机连接&#xf…

[Python学习篇] Python格式化输出

输出关键字 print 语法&#xff1a;print("内容") 示例&#xff1a;print("123") 格式化符号 符号转换%s字符串%c字符%d有符号的十进制整数 &#xff08;输出正整数 和负整数-&#xff09;%03d&#xff1a;表示输出3位数字&#xff0c;如果不足3位在数字前…

[NSSRound#8 Basic]MyPage

[NSSRound#8 Basic]MyPage 打开页面后什么都没有 尝试使用php伪协议 //读取文件源码 filephp://filter/readconvert.base64-encode/resourceindex.php 显示&#xff1a;空白一片 filephp://filter/readconvert.base64-encode/resource/var/www/html/index.php 显示&#xff1…

推箱子_java源码_50张地图_带背景音乐

一. 演示视频 推箱子_java源码_50张地图_带背景音乐 二. 实现步骤 完整项目获取 https://githubs.xyz/y23.html 部分截图 map地图实现 00000000000000000000 00000000000000000000 00000000000000000000 00000000000000000000 00000000000000000000 00000000000000000000 00…

C# 关于Encoding编码 举例UTF8

在UTF-8编码中&#xff0c;一个字符可能由一个或多个字节组成。每个字节的前几位用于指示该字符需要多少字节来表示。具体来说&#xff0c;UTF-8编码使用以下规则&#xff1a; 如果一个字节的最高位是0&#xff0c;那么它表示一个ASCII字符&#xff0c;并且该字节就是该字符的…