十八、Rust gRPC 多 proto 演示

2024-04-03 23:52
文章标签 rust proto 演示 grpc 十八

本文主要是介绍十八、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

这篇关于十八、Rust gRPC 多 proto 演示的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/874308

相关文章

JavaScript Array.from及其相关用法详解(示例演示)

《JavaScriptArray.from及其相关用法详解(示例演示)》Array.from方法是ES6引入的一个静态方法,用于从类数组对象或可迭代对象创建一个新的数组实例,本文将详细介绍Array... 目录一、Array.from 方法概述1. 方法介绍2. 示例演示二、结合实际场景的使用1. 初始化二

Rust中的注释使用解读

《Rust中的注释使用解读》本文介绍了Rust中的行注释、块注释和文档注释的使用方法,通过示例展示了如何在实际代码中应用这些注释,以提高代码的可读性和可维护性... 目录Rust 中的注释使用指南1. 行注释示例:行注释2. 块注释示例:块注释3. 文档注释示例:文档注释4. 综合示例总结Rust 中的注释

Rust格式化输出方式总结

《Rust格式化输出方式总结》Rust提供了强大的格式化输出功能,通过std::fmt模块和相关的宏来实现,主要的输出宏包括println!和format!,它们支持多种格式化占位符,如{}、{:?}... 目录Rust格式化输出方式基本的格式化输出格式化占位符Format 特性总结Rust格式化输出方式

Rust中的Drop特性之解读自动化资源清理的魔法

《Rust中的Drop特性之解读自动化资源清理的魔法》Rust通过Drop特性实现了自动清理机制,确保资源在对象超出作用域时自动释放,避免了手动管理资源时可能出现的内存泄漏或双重释放问题,智能指针如B... 目录自动清理机制:Rust 的析构函数提前释放资源:std::mem::drop android的妙

Rust中的BoxT之堆上的数据与递归类型详解

《Rust中的BoxT之堆上的数据与递归类型详解》本文介绍了Rust中的BoxT类型,包括其在堆与栈之间的内存分配,性能优势,以及如何利用BoxT来实现递归类型和处理大小未知类型,通过BoxT,Rus... 目录1. Box<T> 的基础知识1.1 堆与栈的分工1.2 性能优势2.1 递归类型的问题2.2

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

关于Maven生命周期相关命令演示

《关于Maven生命周期相关命令演示》Maven的生命周期分为Clean、Default和Site三个主要阶段,每个阶段包含多个关键步骤,如清理、编译、测试、打包等,通过执行相应的Maven命令,可以... 目录1. Maven 生命周期概述1.1 Clean Lifecycle1.2 Default Li

Rust 数据类型详解

《Rust数据类型详解》本文介绍了Rust编程语言中的标量类型和复合类型,标量类型包括整数、浮点数、布尔和字符,而复合类型则包括元组和数组,标量类型用于表示单个值,具有不同的表示和范围,本文介绍的非... 目录一、标量类型(Scalar Types)1. 整数类型(Integer Types)1.1 整数字