域名 AXUM.RS 将于2025年10月到期。我们无意再对其进行续费,我们希望你能够接续这个域名,让更多 AXUM 开发者继续受益。
  • 方案1️⃣AXUM.RS 域名 = 3000
  • 方案2️⃣方案1️⃣ + 本站所有专题原始 Markdown 文档 = 5000
  • 方案3️⃣方案2️⃣ + 本站原始数据库 = 5500
如果你有意接续这份 AXUM 情怀,请与我们取得联系。
说明:
  1. 如果有人购买 AXUM.RS 域名(方案1️⃣),或者该域名到期,本站将启用新的免费域名继续提供服务。
  2. 如果有人购买了 AXUM.RS 域名,且同时购买了内容和/或数据库(方案2️⃣/方案3️⃣),本站将关闭。届时我们或许会以另一种方式与你再相遇。

Rust 开发 gRPC 服务端和客户端

Rust 开发 gRPC 主要依赖两个 crate:

tonic 依赖 prost(和axum),它们都提供了 build 工具,用于将 proto 生成 rust 代码。同样,tonic-build 也依赖于 prost-build

# Cargo.toml
[dependencies]
tokio = {version = "1", features = ["full"]}
prost = "0.11"
tonic = "0.8"

[build-dependencies]
tonic-build = "0.8"

构建文件

// build.rs
fn main() {
    tonic_build::configure()
        .out_dir("src/pb") // 生成代码的存放目录
        .compile(
            &["../proto/helloworld.proto"], // 欲生成的 proto 文件列表
            &["../proto"],                  // proto 依赖所在的根目录
        )
        .unwrap();
}

生成代码

src 目录下创建 pb目录:

mkdir src/pb

因为有 build.rs ,所以运行 cargo run ,将自动生成 proto 对应的 rust 文件。

cargo run

现在,src/pb下应该有一个helloworld.rs,这就是 tonic-buildproto/helloworld.proto 生成的对应的的 rust 代码。由于 rust 模块管理的要求,请创建 src/pb/mod.rs,并输入以下内容:

pub mod helloworld;

多入口

我们采用最简单的多入口模式,将服务端和客户端以单独的文件放在 src目录下。

进入 src目录,并删除main.rs

cd src && rm main.rs

创建 server.rsclient.rs

touch server.rs && touch client.rs

Cargo.toml中配置多入口:

服务端

打开 src/server.rs,开始编写服务端代码。

首先,我们定义一个自己的 MyGreeter 服务,并实现 tonic-build 根据 helloworld.proto 生成的代码中的 Greeter

#[derive(Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(
        &self,
        request: tonic::Request<pb::helloworld::HelloRequest>,
    ) -> Result<tonic::Response<pb::helloworld::HelloReply>, tonic::Status> {
        println!("从 {:?} 获取到一个请求", request.remote_addr());
        let reply = pb::helloworld::HelloReply {
            message: format!("[Rust] 你好 {}!", request.into_inner().name),
        };
        Ok(tonic::Response::new(reply))
    }
}

接着,在 main() 函数中启用这个服务:

#[tokio::main]
async fn main() {
    let addr = "127.0.0.1:19527";
    let greeter = MyGreeter::default();

    println!("GreeterServer listening on {}", addr);

    tonic::transport::Server::builder()
        .add_service(GreeterServer::new(greeter))
        .serve(addr.parse().unwrap())
        .await
        .unwrap();
}

启动服务端:

cargo run --bin server

客户端

客户端代码位于 src/client.rs,相较于服务端代码,它简单许多:

// src/client.rs

use pb::helloworld::{greeter_client::GreeterClient, HelloRequest};
use std::env;
mod pb;
#[tokio::main]
async fn main() {
    let grpc_server_addr = env::var("GRPC_SERVER").unwrap_or("127.0.0.1:19527".to_string());
    let grpc_server_addr = format!("http://{}", grpc_server_addr);

    let mut client = GreeterClient::connect(grpc_server_addr).await.unwrap();
    let request = tonic::Request::new(HelloRequest {
        name: "李四".into(),
    });
    let response = client.say_hello(request).await.unwrap();
    println!("{:?}", response);
}

为了让客户端能连接 Rust 编写的服务端和 Go 编写的服务端,我们通过环境变量 GRPC_SERVER 来指定要连接服务端地址。

启动客户端:

cargo run --bin client

指定要连接的服务端地址:

GRPC_SERVER=127.0.0.1:19527 cargo run --bin client
要查看完整内容,请先登录