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

  • 创建文件夹

之后需要编写 gRPCprotobuf 文件来描述接口,因此为了方便管理需要在 项目根目录 下创建一个名为 proto 的文件夹,如:

mkdir proto
  • 编写

等创建完项目之后,需要编写一个 protobufproto/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

构建

由于 gRPCprotobuf 是需要先进行编译, 因此 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 }

终 !

posted @   abdusami  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示