Rust使用gRPC

其实比较好的思路是,server用rust写,client用其他语言写。这才是rpc的用处~


使用gRPC在Rust中创建一个简单的例子涉及几个步骤。这里我将为你提供一个简单的gRPC服务器和客户端的例子。为了实现这个,你需要以下几个组件:

  1. Protobuf定义(.proto文件):这定义了gRPC服务和消息。
  2. Rust gRPC服务器:它实现了.proto文件中定义的服务。
  3. Rust gRPC客户端:它调用服务器上的gRPC服务。

首先,确保你的环境安装了以下工具:

第一步:创建项目结构

  1. 创建一个新的Rust项目:

    1
    2
    cargo new rust_grpc_example
    cd rust_grpc_example
  2. 在项目根目录创建一个proto文件夹,并在其中创建一个hello.proto文件。

第二步:编写.proto文件

proto/hello.proto文件中写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

package hello;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings.
message HelloReply {
string message = 1;
}

第三步:添加依赖

在你的Cargo.toml文件中添加以下依赖:

1
2
3
4
5
6
7
[dependencies]
tonic = "0.6"
tokio = { version = "1", features = ["full"] }
prost = "0.9"

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

第四步:创建build脚本

build.rs文件中,添加以下代码来生成Rust代码从.proto文件:

1
2
3
4
fn main() {
tonic_build::compile_protos("proto/hello.proto")
.expect("Failed to compile proto files");
}

第五步:编写gRPC服务器

src/main.rs中,创建一个gRPC服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use tonic::{transport::Server, Request, Response, Status};

pub mod hello {
tonic::include_proto!("hello");
}

use hello::greeter_server::{Greeter, GreeterServer};
use hello::{HelloReply, HelloRequest};

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

#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
let reply = hello::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 = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();

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

Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;

Ok(())
}

第六步:编写gRPC客户端

在同一个src/main.rs文件中,你可以添加一个客户端来测试服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 客户端代码
// ...

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 服务器代码
// ...

// 客户端测试
let mut client = hello::greeter_client::GreeterClient::connect("http://[::1]:50051").await?;

let request = tonic::Request::new(HelloRequest {
name: "world".into(),
});

let response = client.say_hello(request).await?;

println!("RESPONSE={:?}", response.into_inner().message);

Ok(())
}

编译和运行

  1. 在项目根目录执行cargo run来编译和运行项目。
  2. 服务器将启动并监听在[::1]:50051,客户端将发送一个请求并打印

两个fn main,无法编译通过吧

你是对的,Rust 程序不能有两个 main 函数。我之前的示例中同时包含了客户端和服务器代码,这在实际应用中是不可行的。通常,客户端和服务器应该是分开的程序。为了简化,我将展示如何分别为客户端和服务器创建单独的文件。

修改项目结构

  1. src 目录下创建两个新文件:server.rsclient.rs

server.rs

将服务器代码放入 server.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use tonic::{transport::Server, Request, Response, Status};

pub mod hello {
tonic::include_proto!("hello");
}

use hello::greeter_server::{Greeter, GreeterServer};
use hello::{HelloReply, HelloRequest};

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

#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
let reply = hello::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 = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();

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

Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;

Ok(())
}

client.rs

将客户端代码放入 client.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use tonic::Request;

pub mod hello {
tonic::include_proto!("hello"); // 指定.proto文件的路径
}

use hello::greeter_client::GreeterClient;
use hello::HelloRequest;

#[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: "world".into(),
});

let response = client.say_hello(request).await?;

println!("RESPONSE={:?}", response.into_inner().message);

Ok(())
}

修改Cargo.toml

Cargo.toml 文件中,指定这两个不同的二进制目标:

1
2
3
4
5
6
7
[[bin]]
name = "server"
path = "src/server.rs"

[[bin]]
name = "client"
path = "src/client.rs"

这样,你就可以分别运行服务器和客户端了:

  • 运行服务器:cargo run --bin server
  • 运行客户端:cargo run --bin client

确保服务器在运行时启动客户端,以便客户端可以连接并发送请求。



本篇是对 Rust网络编程系列学习与实践.


https://github.com/anonymousGiga/net/blob/master/use_grpc/Cargo.toml

一直报错: protobuf must have version 3

1
2
3
4
5
6
7
8
9
error: failed to run custom build command for `use_grpc v0.1.0 (/Users/fliter/net/use_grpc)`

Caused by:
process didn't exit successfully: `/Users/fliter/net/use_grpc/target/debug/build/use_grpc-96c98097edaccc88/build-script-build` (exit status: 101)
--- stderr
thread 'main' panicked at /Users/fliter/.cargo/registry/src/mirrors.ustc.edu.cn-61ef6e0cd06fb9b8/protoc-rust-grpc-0.7.1/src/lib.rs:107:13:
protobuf must have version 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...


需先安装protoc. Mac上使用brew install protobuf,Ubuntu可使用apt install -y protobuf-compiler

之后执行 protoc --version验证是否成功安装


cargo new use_grpc --lib


在src同级目录下创建foobar.proto文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
syntax = "proto3";

package foobar;

service FooBarService {
rpc record_cab_location(CabLocationRequest) returns (CabLocationResponse);
rpc get_cabs(GetCabRequest) returns (GetCabResponse);
}

message CabLocationRequest {
string name = 1;
Location location = 2;
}

message CabLocationResponse {
bool accepted = 1;
}

message GetCabRequest {
Location location = 1;
}

message GetCabResponse {
repeated Cab cabs = 1;
}

message Cab {
string name = 1;
Location location = 2;
}

message Location {
float latitude = 1;
float longitude = 2;
}

在Cargo.toml的[dependencies]下新增

1
2
3
4
5
6
7
8
9
[dependencies]
protobuf = "2"
grpc = "0.8.3"
grpc-protobuf = "0.8.3"
futures = "0.3.*"
tls-api = "0.9.*"

[build-dependencies]
protoc-rust-grpc = "0.8.3"

关于这个crate详细信息,参考 https://crates.io/crates/grpc

demo参考 https://github.com/stepancheg/grpc-rust/tree/master/grpc-examples/greeter


参考上面链接,在src同级目录下新建build.rs,并做相应修改,内容如下:

1
2
3
4
5
6
7
8
9
10
extern crate protoc_rust_grpc;

fn main() {
protoc_rust_grpc::Codegen::new()
.out_dir("src")
.input("foobar.proto")
.rust_protobuf(true)
.run()
.expect("protoc-rust-grpc");
}

在src/lib.rs中,

1
2
3
4
5
6
// extern crate protobuf;
// extern crate grpc;
// extern crate tls_api;
// extern crate grpc_protobuf;
pub mod foobar;
pub mod foobar_grpc;

https://learnku.com/articles/44387


https://learnku.com/users/53818


https://lvlv.fun/posts/2022-05-30/

https://jasonkayzk.github.io/2022/12/03/Rust%E7%9A%84GRPC%E5%AE%9E%E7%8E%B0Tonic/

https://rustmagazine.github.io/rust_magazine_2021/chapter_5/rust_grpc_load_balancing.html