前言
当前越来越多的公司基于 Google gRPC 通信框架来构建微服务体系,比较流行的是使用 Go/Java/C++ 这样的主流编程语言来编写服务端,我们今天来尝试使用 Rust 语言来实现一个 gRPC 服务端/客户端。
打开官方文档可以看到目前 Rust 并不在 gRPC 官方支持的语言列表中:
Supported languages
- C#
- C++
- Dart
- Go
- Java
- Kotlin
- Node
- Objective-C
- PHP
- Python
- Ruby
不过不用担心这个问题。我们知道只要某个语言兼容了基于 C/C++ 编写的 gRPC 的核心库 ,那么该语言就可以完美支持 gRPC。目前 Rust 可以实现 gRPC 的主流 crate 如下:
以上三种任选其一都可以,只是 grpc-rs/grpc-rust 当前还处于开发状态,我们在这里使用 tonic 包。
构建程序
首先检查你的 Rust 版本:
$ rustc --version
rustc 1.61.0 (fe5b13d68 2022-05-18)
tonic 适用于 1.56 及以上,如果低于这个版本,你应该先更新你的 Rust 编译器:
$ rustup update stable
确保你已经提前安装了 protobuf:
$ protoc --version
libprotoc 3.19.4
# macOS 可以通过以下命令安装
$ brew install protobuf
使用 cargo 新建一个项目
$ cargo new grpcrs
$ cd grpcrs
$ cargo run
Compiling grpcrs v0.1.0 (/Users/lvlv/Documents/project/demo/grpcrs)
Finished dev [unoptimized + debuginfo] target(s) in 0.55s
Running `target/debug/grpcrs`
Hello, world!
编辑 cargo.toml 文件:
[package]
name = "grpcrs"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "user-server"
path = "src/user/server.rs"
[[bin]]
name = "user-client"
path = "src/user/client.rs"
[dependencies]
tonic = "0.7.2"
tokio = { version = "1.18.2", features = ["macros", "rt-multi-thread"] }
prost = "0.10"
[build-dependencies]
tonic-build = "0.7.2"
创建下列文件:
$ mkdir -p proto/user src/user
$ touch build.rs proto/user/user.proto src/user/{server.rs,client.rs}
当前目录结构:
$ tree -L 3
.
├── Cargo.lock
├── Cargo.toml
├── build.rs # Cargo 构建脚本
├── proto
│ └── user
│ └── user.proto # proto 文件
└── src
└── user
├── client.rs # gRPC 客户端代码
└── server.rs # gRPC 服务端代码
分别将以下内容拷贝到各个文件:
- proto/user/user.proto
syntax = "proto3";
package user;
service User {
rpc Hello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
- src/user/server.rs
use tonic::{transport::Server, Request, Response, Status};
use user::user_server::{User, UserServer};
use user::{HelloReply, HelloRequest};
pub mod user {
tonic::include_proto!("user");
}
#[derive(Default)]
pub struct UserService {}
#[tonic::async_trait]
impl User for UserService {
async fn hello(&self, request: Request<HelloRequest>) -> Result<Response<HelloReply>, Status> {
println!("New user request from {:?}", request.remote_addr());
let reply = user::HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:50051".parse().unwrap();
let user_service = UserService::default();
println!("UserService listening on {}", addr);
Server::builder()
.add_service(UserServer::new(user_service))
.serve(addr)
.await?;
Ok(())
}
- src/user/client.rs
use user::user_client::UserClient;
use user::HelloRequest;
pub mod user {
tonic::include_proto!("user");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = UserClient::connect("http://127.0.0.1:50051").await?;
let request = tonic::Request::new(HelloRequest {
name: "Rick".into(),
});
let response = client.hello(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
- build.rs
use std::{env, path::PathBuf};
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let user_proto = "proto/user/user.proto";
tonic_build::configure()
.build_server(true)
.build_client(true)
.out_dir(&out_dir)
.file_descriptor_set_path(&out_dir.join("user_descriptor.bin"))
.compile(&[user_proto], &["proto"])
.unwrap_or_else(|err| panic!("protobuf compile failed: {}", err));
}
尝试编译代码:
$ cargo build
Compiling proc-macro2 v1.0.39
Compiling unicode-ident v1.0.0
Compiling syn v1.0.95
Compiling libc v0.2.126
Compiling cfg-if v1.0.0
Compiling log v0.4.17
# ... 省略
Compiling hyper v0.14.19
Compiling axum v0.5.6
Compiling hyper-timeout v0.4.1
Compiling tonic v0.7.2
Finished dev [unoptimized + debuginfo] target(s) in 21.01s
如果编译通过,现在我们可以尝试执行编译好的程序了。
首先启动 gRPC 服务端程序:
$ cargo run --bin user-server
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/user-server`
UserService listening on 127.0.0.1:50051
重新打开一个 terminal 窗口并执行客户端程序:
$ cargo run --bin user-client
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/user-client`
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Sun, 29 May 2022 21:54:18 GMT", "grpc-status": "0"} }, message: HelloReply { message: "Hello Rick!" }, extensions: Extensions } # <- 客户端请求成功并返回响应
此时我们切回服务端 terminal 窗口查看日志:
# ...
UserService listening on 127.0.0.1:50051
New user request from Some(127.0.0.1:52147) # <- 客户端调用成功
至此,一个简单的基于 Rust 的 gRPC 服务端/客户端就实现了。上述代码很简陋,相信只要是接触过 gRPC 的同学都比较容易就可以理解。