☰
grpc-rust(tonic)入门指南:基于 async/await 的高性能 gRPC 客户端与服务端实现
2026/10/2 13:44:03 网站建设 项目流程
  • 后端
  • RPC框架

【免费下载链接】grpc-rust

A native gRPC client & server implementation with async/await support.

项目地址:https://gitcode.com/GitHub_Trending/to/grpc-rust
点击查看免费下载

本指南以仓库根目录 README.md 为骨架,融合 helloworld 教程、routeguide 教程 以及tonic源码细节,系统讲解如何在 Rust 中基于异步生态搭建 gRPC 服务端与客户端。读完本文,你将掌握 gRPC 四类 RPC 的定义与编码、tonic的三大核心组件架构、特性开关与 TLS/压缩/负载均衡等生产级能力,并能独立从零写出一个可运行、可验证的 HelloWorld 示例。

项目概览:gRPC 的 Rust 原生实现

README.md 明确指出,本仓库是 gRPC 的一个 Rust 实现。gRPC 是一个高性能、开源、通用的 RPC 框架,其设计把移动端和 HTTP/2 放在首位。仓库中的核心 cratetonic是基于 HTTP/2 的 gRPC 实现,聚焦三个关键词:高性能(high performance)、互操作性(interoperability)与灵活性(flexibility)。

它被设计出来的目的有两层:

  1. 对async/await提供一等公民(first class)支持;
  2. 作为用 Rust 编写生产系统时的核心构建块(core building block)。

需要留意的是:README 中有一则重要提示——tonic的master分支当前正在准备破坏性变更(breaking changes),若想使用最新已发布版本,应关注0.14.x分支。仓库内tonic/Cargo.toml显示的版本为0.14.6,工作区级的rust-version = "1.88"即 MSRV(最低支持的 Rust 版本),与 README 中 “tonic's MSRV is1.88” 的声明一致。

三大核心组件

README 的 Overview 一节把tonic拆解为三个相互协作的组成部分:

组件职责底层技术
通用 gRPC 实现提供与具体 HTTP/2 栈、编码格式解耦的通用 gRPC 语义,通过一组泛型 trait 支撑任意 HTTP/2 实现与任意编码tonic 自身
高性能 HTTP/2 实现提供开箱即用的 Channel(客户端)与 Server(服务端)hyper、基于稳健的tokio栈构建
代码生成(codegen)从protobuf定义构建客户端与服务端代码prost

也就是说:tonic在最低层面允许使用任意 HTTP/2 实现配合不同类型的 gRPC 编码格式;而默认提供的transport模块则是一套基于tokio、hyper、tower的“全家桶”实现,同时被设计成一份参考实现,供需要更丰富特性的团队在此基础上继续扩展。

功能特性清单

README 列出了tonic的核心特性,这些特性也都能在 examples 中找到对应的完整示例代码:

  • 双向流式传输(Bi-directional streaming)—— 客户端与服务端可同时收发消息流,对应 streaming 示例 与 routeguide 教程中的RouteChat;
  • 高性能异步 IO(High performance async io)—— 基于tokio+hyper的异步网络栈;
  • 互操作性(Interoperability)—— 遵循标准 gRPC 协议,可与各语言 gRPC 实现互通,interop 目录提供了互操作测试实现;
  • TLS 支持,由rustls背书—— 通过特性开关启用,示例见 tls 示例、tls_client_auth 示例 与 tls_rustls 示例;
  • 负载均衡(Load balancing)—— 客户端侧可配置多种均衡策略,见 load_balance 示例 与 dynamic_load_balance 示例;
  • 自定义元数据(Custom metadata)—— 通过tonic::metadata模块(metadata/key.rs、metadata/map.rs)在请求/响应中附加键值元数据;
  • 认证(Authentication)—— 服务端与客户端拦截器实现,见 authentication 示例;
  • 健康检查(Health Checking)—— 提供标准 gRPC 健康检查服务的实现,见 tonic-health crate 与 health 示例。

此外,tonic/src/lib.rs 的 crate 级文档确认,该库的定位是“用于生产系统的核心构建块”,强调性能、互操作性与灵活性三者的平衡。

特性开关(Feature Flags):按需裁剪依赖

tonic通过 Cargo feature 精细控制启用哪些能力。tonic/src/lib.rs 与 tonic/Cargo.toml 给出了完整清单,按“默认开启 / 默认关闭”整理如下:

默认开启(default = ["router", "transport", "codegen"]):

特性作用
transport启用基于hyper、tower、tokio的“全家桶”客户端与服务端实现,同时开启server与channel
server仅启用transport中服务端部分(依赖h2、hyper?/server、socket2、tower?/limit、tower?/load-shed等)
channel仅启用transport中客户端 Channel 部分(依赖h2、hyper?/client、hyper-timeout、tower?/balance等)
router启用基于axum的服务路由
codegen启用tonic-build所需的导出与可选依赖(async-trait)

默认关闭,按需开启:

特性作用
tls-ring基于rustls的 TLS 选项,使用ringlibcrypto provider
tls-aws-lc基于rustls的 TLS 选项,使用aws-lc-rslibcrypto provider(与tls-ring二选一)
tls-native-roots通过rustls-native-certs为 gRPC 客户端注入系统信任根证书
tls-webpki-roots通过webpki-roots为 gRPC 客户端注入标准信任根证书
tls-connect-info为常见 TLS connector 增加Connected实现,便于在不启用其他tls-*特性时配合connect_with_connector使用自定义 TLS connector
gzip启用请求、响应与流的 gzip 压缩(依赖flate2)
deflate启用 deflate 压缩(依赖flate2)
zstd启用 zstd 压缩(依赖zstd)

从 tonic/Cargo.toml 的源码可以看到,tls-ring与tls-aws-lc都经由内部特性_tls-any统一接通tokio运行时与tls-connect-info,二者本质是tokio-rustls下两种 libcrypto 提供者的选择。

消息大小上限:防止内存耗尽

tonic/src/lib.rs 还披露了一个容易被忽视的默认配置:服务端与客户端都可设置最大编码/解码消息大小,以确保入站 gRPC 消息不会耗尽系统内存。默认解码上限为 4MB,编码上限为usize::MAX。生产环境中建议按业务需要显式调整。

依赖说明:关于 protoc

README 的 Dependencies 一节提醒了一个实际工程中常见的坑:tonic-build的某些 API(例如tonic_build::compile_protos())需要protoc(Protocol Buffers 编译器)来编译.proto资源文件。这意味着构建机器上需要安装 Protocol Buffers 编译器(可参考 Protocol Buffers 官方下载页获取对应平台的protoc)。与之对应,tonic-prost-build(tonic-prostcrate 的构建端,见 tonic-prost)提供了基于prost的纯 Rust 编译路径,这也是本仓库教程默认采用的方式。

快速上手:从 HelloWorld 开始

README 推荐的入门路径是 helloworld 教程(适合首次使用tonic的读者),进阶路径是 routeguide 教程(覆盖tonic全部特性)。下面按教程脉络,从零搭建一个可运行的 gRPC 服务端与客户端。

第一步:创建项目并定义 proto

$ cargo new helloworld-tonic $ cd helloworld-tonic $ mkdir proto $ touch proto/helloworld.proto

tonic对.proto文件存放位置没有硬性要求(教程中放在项目根目录的proto/下即可)。定义服务时,先在proto文件中声明syntax与package名——这个 package 名就是后续include_proto!宏查找生成代码的依据:

syntax = "proto3"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }

gRPC 共支持四类服务方法(简单 RPC、服务端流式、客户端流式、双向流式),tonic全部支持;HelloWorld 教程只用到最简单的简单 RPC,四类方法都会用到的话请看 routeguide 教程(仓库中的完整定义见 examples/proto/routeguide/route_guide.proto)。

第二步:配置 Cargo.toml 与 build.rs

examples/helloworld-tutorial.md 给出了完整依赖清单。核心要点:

[[bin]] # Bin to run the HelloWorld gRPC server name = "helloworld-server" path = "src/server.rs" [[bin]] # Bin to run the HelloWorld gRPC client name = "helloworld-client" path = "src/client.rs" [dependencies] tonic = "*" prost = "0.14" tonic-prost = "*" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } [build-dependencies] tonic-prost-build = "*"

然后在项目根目录(不是src下)创建build.rs,把 proto 编译接入 cargo 构建流程:

fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_prost_build::compile_protos("proto/helloworld.proto")?; Ok(()) }

routeguide 教程中用的是带unwrap_or_else的错误处理变体,效果等价。这样每次cargo build都会自动保持生成代码与.proto定义同步,无需额外步骤。

第三步:编写服务端

服务端实现分两步:把生成代码引入作用域,然后实现生成的服务 trait。首先生成代码通过include_proto!宏引入(参数是 proto 里的package 名,不是文件名):

use tonic::{transport::Server, Request, Response, Status}; use hello_world::greeter_server::{Greeter, GreeterServer}; use hello_world::{HelloReply, HelloRequest}; pub mod hello_world { tonic::include_proto!("helloworld"); }

接着实现Greetertrait。注意#[tonic::async_trait]属性宏让 trait 支持 async 方法(其内部基于async-trait,对应tonic的codegen特性):

#[derive(Debug, Default)] pub struct MyGreeter {} #[tonic::async_trait] impl Greeter for MyGreeter { async fn say_hello( &self, request: Request<HelloRequest>, ) -> Result<Response<HelloReply>, Status> { println!("Got a request: {:?}", request); let reply = HelloReply { message: format!("Hello {}!", request.into_inner().name), }; Ok(Response::new(reply)) } }

注意:gRPC 请求与响应的字段是私有的,必须通过request.into_inner()解包后才能访问name等字段;同理构造响应时要Response::new(...)包装。

最后在tokio运行时上启动服务:

#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let addr = "[::1]:50051".parse()?; let greeter = MyGreeter::default(); Server::builder() .add_service(GreeterServer::new(greeter)) .serve(addr) .await?; Ok(()) }

运行cargo run --bin helloworld-server即可启动服务。验证方式有两种:使用支持 gRPC 的 GUI 客户端(如 Postman),或使用grpcurl:

$ grpcurl -plaintext -import-path ./proto -proto helloworld.proto -d '{"name": "Tonic"}' '[::1]:50051' helloworld.Greeter/SayHello

预期响应:

{ "message": "Hello Tonic!" }

第四步:编写客户端

客户端同样通过include_proto!引入生成代码,然后connect到服务端地址发起调用。GreeterClient::connect返回的客户端是mut 的,因为它需要维护内部状态:

use hello_world::greeter_client::GreeterClient; use hello_world::HelloRequest; pub mod hello_world { tonic::include_proto!("helloworld"); } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let mut client = GreeterClient::connect("http://[::1]:50051").await?; let request = tonic::Request::new(HelloRequest { name: "Tonic".into(), }); let response = client.say_hello(request).await?; println!("RESPONSE={:?}", response); Ok(()) }

分别在两个终端运行cargo run --bin helloworld-server与cargo run --bin helloworld-client,服务端终端会打印出收到的请求,客户端终端会打印出响应——一个最小可运行的 gRPC 闭环就完成了。

深入:四类 RPC 与流式处理模式

routeguide 教程 用一个“路线导航”应用完整演示了 gRPC 的四类服务方法,其定义方式如下:

// 简单 RPC:客户端发一个请求,等一个响应 rpc GetFeature(Point) returns (Feature) {} // 服务端流式:`stream` 放在响应类型前 rpc ListFeatures(Rectangle) returns (stream Feature) {} // 客户端流式:`stream` 放在请求类型前 rpc RecordRoute(stream Point) returns (RouteSummary) {} // 双向流式:请求与响应前都放 `stream` rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

在实现层面,四类方法对应的tonic::Request<T>/ 返回值形态完全不同,examples/src/routeguide/server.rs 提供了完整参考实现:

  • 简单 RPC(get_feature):接收Request<Point>,返回Result<Response<Feature>, Status>,本质是一次普通函数调用;
  • 服务端流式(list_features):需要返回一个流。典型做法是用tokio::sync::mpsc::channel开异步任务生产数据,再把ReceiverStream包进tonic::Response返回:
type ListFeaturesStream = ReceiverStream<Result<Feature, Status>>; async fn list_features( &self, request: Request<Rectangle>, ) -> Result<Response<Self::ListFeaturesStream>, Status> { let (tx, rx) = mpsc::channel(4); let features = self.features.clone(); tokio::spawn(async move { for feature in &features[..] { if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { tx.send(Ok(feature.clone())).await.unwrap(); } } }); Ok(Response::new(ReceiverStream::new(rx))) }
  • 客户端流式(record_route):方法接收Request<tonic::Streaming<Point>>,用stream.next().await逐个消费输入流,同时折叠出统计结果,流结束后返回单个RouteSummary;
  • 双向流式(route_chat):接收输入流、返回输出流,常借助async-stream的try_stream!宏做“流到流”的异步变换:
type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + 'static>>; async fn route_chat( &self, request: Request<tonic::Streaming<RouteNote>>, ) -> Result<Response<Self::RouteChatStream>, Status> { let mut notes = HashMap::new(); let mut stream = request.into_inner(); let output = async_stream::try_stream! { while let Some(note) = stream.next().await { let note = note?; let location = note.location.unwrap(); let location_notes = notes.entry(location).or_insert(vec![]); location_notes.push(note); for note in location_notes { yield note.clone(); } } }; Ok(Response::new(Box::pin(output) as Self::RouteChatStream)) }

客户端调用流式方法时,返回流的消费方式是stream.message().await?循环读取,直到流结束(见 examples/src/routeguide/client.rs 中的print_features)。客户端侧发送流时,可以用tokio_stream::iter(集合)把Vec廉价地转成Stream再包进Request。

运行完整示例(仓库根目录执行):

$ cargo run --bin routeguide-server $ cargo run --bin routeguide-client # 另开终端

客户端终端会以每秒一行的节奏打印双向流式 RPC 的输出,形如:

NOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" }

代码生成配置:默认配置之外的工作流

routeguide 教程 的附录专门讲解了tonic_prost_build的配置。默认的代码生成配置适合自包含示例与小项目,但当遇到以下场景时就需要定制:

  • 在不同 crate里分别构建 Rust 客户端与服务端;
  • 在更大的多语言项目中只构建 Rust 侧的一部分;
  • 编辑器无法索引默认位置(OUT_DIR)下生成的文件,想要 IDE 支持。

tonic_prost_build可以通过configure()链式方法定制输出:例如把.proto定义留在独立 crate、按需(而非构建时)生成代码,并指定输出目录:

fn main() { tonic_prost_build::configure() .build_client(false) // 只生成服务端代码 .out_dir("another_crate/src/pb") // 输出到指定目录 .compile_protos(&["path/my_proto.proto"], &["path"]) .expect("failed to compile protos"); }

另一种工作流是:将.proto定义放进独立 crate 后,直接以该 crate 作为依赖被其他 crate 引用。README 中 tonic-build(prost系的服务代码生成)与 tonic-prost 正是这一职责的承载者。仓库中 codegen/src/main.rs 与examples/generated/下的生成结果(如 helloworld/helloworld_grpc.pb.rs)可以让你直观了解生成代码的结构。

项目布局:一个完整 gRPC 生态的工作区

README 的 Project Layout 一节把仓库划分成若干可独立复用的 crate。结合根 Cargo.toml 的工作区成员,各模块定位如下:

目录定位
tonic通用 gRPC 与 HTTP/2 客户端/服务端实现(核心 crate,version 0.14.6)
tonic-build基于prost的服务代码生成
tonic-prost面向prost编码的 codec 与集成层
tonic-types基于prost的 gRPC 工具类型,含 gRPC Well Known Types 支持
tonic-health标准 gRPC 健康检查服务实现,同时是 unary 与响应流式 RPC 的示范
tonic-reflection基于 tonic 的 gRPC reflection 实现
examples演示 TLS、负载均衡、双向流式等特性的 gRPC 示例
interop互操作测试实现,覆盖大量 gRPC 特性场景

除此之外,仓库还包含面向 xDS 控制面集成的一族 crate:xds-client、xds-client-opentelemetry、tonic-xds、grpc-xds(gRPC 生态中的高级负载均衡与服务发现方案),以及曾经的grpc、grpc-protobuf、grpc-benchmark等模块。对生产环境有高级路由、重试、熔断、负载均衡需求的读者可深入 tonic-xds 与 xds-client 探索。

生产化要点小结

综合 README、教程与源码,把tonic投入生产前建议关注以下几点:

  1. 版本与 MSRV:当前工作区rust-version = "1.88"(见 Cargo.toml),tonic0.14.6 要求 Rust 1.88 及以上;如需使用最新已发布代码,关注0.14.x分支而非正在变更的master。
  2. 按需开启特性:TLS(tls-ring/tls-aws-lc二选一,可叠加tls-native-roots/tls-webpki-roots信任根)、压缩(gzip/deflate/zstd)默认都是关闭的,用到才开,避免引入多余依赖。
  3. 消息大小上限:默认解码 4MB / 编码usize::MAX,超限场景要显式调大解码限制或对响应流做分页。
  4. 构建依赖:若使用tonic_build::compile_protos()这类 API,构建机需安装protoc;纯 Rust 的tonic-prost-build路径则无此要求。
  5. 中间件与拦截器:tonic内部基于tower与hyper,天然支持可组合的中间件栈——认证、限流、负载均衡(tower的limit、balance、load-shed均已接入channel/server特性,见 tonic/Cargo.toml),示例见 interceptor 示例 与 tower 示例。

获取帮助与参与贡献

README 建议的求助路径依次是:先查 API 文档;若没有答案,可在 Tonic Discord 频道提问;仍无法解决再开 issue 提交问题。仓库本身是只读的,欢迎按 CONTRIBUTING.md 的指引参与贡献。

项目采用 MIT 许可。除非明确声明,任何为 Tonic 贡献的代码默认按 MIT 条款授权,不附加额外条件。

  • 后端
  • RPC框架

【免费下载链接】grpc-rust

A native gRPC client & server implementation with async/await support.

项目地址:https://gitcode.com/GitHub_Trending/to/grpc-rust
点击查看免费下载

相关推荐

上一篇:老游戏在 Windows 10/11 上花屏卡顿?DDrawCompat 用一个 dll 就能救回来
下一篇:Source Sans 3 可变字体实战教程:用两个文件替代 14 个静态字体并解锁任意字重的完整方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询