gRPC in Rust - Simple (Tonic)
gRPC in Rust
Abdusami
Rust
gRPC
abdusami.dev@aliyun.com
背景
最近在开发一个基于微服务架构的项目,最初将各种服务之间的调用设计为通过 HTTP API
的形式,因此每个服务节点都应该实现一个 Web 服务器,并已经确定使用 Actix web
来实现,虽然基本上没啥问题,但是因本人的原因向研究和尝试以下通过 gRPC 来进行服务调用,从此通过结合官方的文档和示例代码来实现了一个简单的 gRPC 服务端和客户端,
由于关于 gRPC
相关的基本只是不再本博客范围之内,因此直接上代码。
配置 crate
Cargo.toml
[package]
name = "tonic-helloworld"
version = "0.1.0"
edition = "2021"
# gRPC 客户端
[[bin]]
name = "server"
path = "src/server.rs"
# gRPC 服务端
[[bin]]
name = "client"
path = "src/client.rs"
[dependencies]
tonic = "0.8" # gRPC 的 Rust 实现库
prost = "0.11"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
[build-dependencies]
tonic-build = "0.8"
将配置完上述内容之后继续编写相关的实现
由于 tonic 是在
Rust Tokio
生态当中,因此依赖tokio
很正常
编写 Proto
- 创建文件夹
之后需要编写 gRPC
的 protobuf
文件来描述接口,因此为了方便管理需要在 项目根目录 下创建一个名为 proto
的文件夹,如:
mkdir proto
- 编写
等创建完项目之后,需要编写一个 protobuf
在 proto/helloworld.proto
的大致内容如下:
还是最著名的
Hello world
案例
syntax = "proto3";
package helloworld;
// 定义一个服务,类似于 Rust 里
service Greeter {
// 接口名称 (参数列表) returns (返回值)
rpc say_hello(RpcRequest) returns (RpcResponse);
}
// 定义请求参数 (函数参数)
message RpcRequest{
string content = 1;
}
// 定义响应体 (函数返回值)
message RpcResponse {
string content = 1;
}
等编写完上述接口生命之后,需要编写构建脚本
该构建脚本是指 Rust 里的
build.rs
构建
由于 gRPC
的 protobuf
是需要先进行编译, 因此 Rust
中也不例外,需要进行编写构建规则,就需要在 项目根目录下 创建一个 build.rs
文件并编辑其内容如下:
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("tonic::build is compiling proto file ...");
// compile_protos 函数的参数是我们编写的的 proto 文件的路径,可以是相对路径
tonic_build::compile_protos("proto/helloworld.proto")?;
Ok(())
}
服务端实现
然后再实现 gRPC
中服务端,首先需要在源码目录下创建两个文件如 Cargo.toml
中所描述而那样,如下:
touch src/server.rs && touch src/client.rs
通过上述命令在 src/
目录下创建如下两个文件:
-
server.rs
: 是 gRPC 的服务端实现 -
client.rs
: 是 gRPC 的客户端 (调用端)
然后再编写其内容:
use tonic::{transport::Server, Request, Response, Status};
use helloworld::greeter_server::{Greeter, GreeterServer};
use helloworld::{RpcRequest, RpcResponse};
pub mod helloworld {
/// 此时的 `helloworld` 是在 proto 文件里定义的 `package` 值对应
tonic::include_proto!("helloworld");
}
/// 定义一个自定义 struct, 若有必要,可以实现 App State , 就存储应用状态 (定义字段)
#[derive(Debug, Default)]
pub struct MyGreeter {}
/// 为自己的 struct 实现在 proto 文件里所定义的 `Trait`, 如下面所见,是异步
/// 因此需要通过 `#[tonic::async_trait]` 宏来修饰
#[tonic::async_trait]
impl Greeter for MyGreeter {
/// 函数签名,有一个 `&self`
async fn say_hello(
&self,
request: Request<RpcRequest>,
) -> Result<Response<RpcResponse>, Status> {
// 函数内部实现
println!("GOT a new request on say_hello with {:?}", request);
// 构造返回值
let reply = helloworld::RpcResponse {
content: format!("this response from server"),
};
// 函数返回
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// gRPC 服务端地址
let address = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
println!("RPC Server is starting on {:?} ...", address);
// 启动服务端
let server = Server::builder()
.add_service(GreeterServer::new(greeter)) // 注册定义和实现的接口
.serve(address);
println!("RPC Server is running ...");
server.await?;
Ok(())
}
客户端实现
接下来需要编辑 client.rs
的内容来远程调用服务端提供的接口,其内容如下:
use helloworld::greeter_client::GreeterClient;
use helloworld::RpcRequest;
pub mod helloworld{
/// 同样引入 proto
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(RpcRequest{
content: "this request is from client".to_owned()
});
// 调用函数并携带参数,等执行完之后获取返回值,该方式如调用本地方法一样,比较直观
let response = client.say_hello(request).await?;
println!("response is {:?}", response);
Ok(())
}
启动和测试
首先需要启动 gRPC
服务端,使用如下命令:
cargo run --bin server
其输出内容如下:
rusty:~/Documents/projects/rs/tonic-helloworld$ cargo run --bin server
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/server`
RPC Server is starting on [::1]:50051 ...
RPC Server is running ...
如上述所见,服务端已经启动,接下来启动客户端来实现调用,如下命令:
cargo run --bin client
输出内容如下:
rusty:~/Documents/projects/rs/tonic-helloworld$ cargo run --bin client
Compiling tonic-helloworld v0.1.0 (/home/xxx/Documents/projects/rs/tonic-helloworld)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.04s
Running `target/debug/client`
response is Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Wed, 12 Feb 2025 17:30:37 GMT", "grpc-status": "0"} }, message: RpcResponse { content: "this response from server" }, extensions: Extensions }
终 !
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~