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();
}
生成代码
mkdir src/pb
因为有 build.rs
,所以运行 cargo run
,将自动生成 proto 对应的 rust 文件。
cargo run
现在,src/pb
下应该有一个helloworld.rs
,这就是 tonic-build
由 proto/helloworld.proto
生成的对应的的 rust 代码。由于 rust 模块管理的要求,请创建 src/pb/mod.rs
,并输入以下内容:
pub mod helloworld;
多入口
我们采用最简单的多入口模式,将服务端和客户端以单独的文件放在 src
目录下。
进入 src
目录,并删除main.rs
:
cd src && rm main.rs
创建 server.rs
和client.rs
:
touch server.rs && touch client.rs
在 Cargo.toml
中配置多入口:
[[bin]]
name="server"
path="src/server.rs"
[[bin]]
name="client"
path="src/client.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();
}
完整代码如下:
// src/server.rs
use pb::helloworld::greeter_server::{Greeter, GreeterServer};
mod pb;
#[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))
}
}
#[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
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