gRPC in Rust - Simple (Tonic)

打印 上一主题 下一主题

主题 830|帖子 830|积分 2490

gRPC in Rust

Abdusami Rust gRPC abdusami.dev@aliyun.com
背景

最近在开发一个基于微服务架构的项目,最初将各种服务之间的调用设计为通过 HTTP API 的形式,因此每个服务节点都应该实现一个 Web 服务器,并已经确定使用 Actix web 来实现,虽然基本上没啥问题,但是因本人的原因向研究和尝试以下通过 gRPC 来进行服务调用,从此通过联合官方的文档和示例代码来实现了一个简单的 gRPC 服务端和客户端,
由于关于 gRPC 干系的基本只是不再本博客范围之内,因此直接上代码。
设置 crate

Cargo.toml
  1. [package]
  2. name = "tonic-helloworld"
  3. version = "0.1.0"
  4. edition = "2021"
  5. # gRPC 客户端
  6. [[bin]]
  7. name = "server"
  8. path = "src/server.rs"
  9. # gRPC 服务端
  10. [[bin]]
  11. name = "client"
  12. path = "src/client.rs"
  13. [dependencies]
  14. tonic = "0.8"  # gRPC 的 Rust 实现库
  15. prost = "0.11"
  16. tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
  17. [build-dependencies]
  18. tonic-build = "0.8"
复制代码
将设置完上述内容之后继承编写干系的实现
由于 tonic 是在 Rust Tokio 生态当中,因此依靠 tokio 很正常
编写 Proto


  • 创建文件夹
之后需要编写 gRPC 的 protobuf 文件来形貌接口,因此为了方便管理需要在 项目根目录 下创建一个名为 proto 的文件夹,如:
  1. mkdir proto
复制代码

  • 编写
等创建完项目之后,需要编写一个 protobuf 在 proto/helloworld.proto 的大致内容如下:
还是最著名的 Hello world 案例
  1. syntax = "proto3";
  2. package helloworld;
  3. // 定义一个服务,类似于 Rust 里
  4. service Greeter {
  5. // 接口名称 (参数列表) returns (返回值)
  6.     rpc say_hello(RpcRequest) returns (RpcResponse);
  7. }
  8. // 定义请求参数 (函数参数)
  9. message RpcRequest{
  10.     string content = 1;
  11. }
  12. // 定义响应体 (函数返回值)
  13. message RpcResponse {
  14.     string content = 1;
  15. }
复制代码
等编写完上述接口生命之后,需要编写构建脚本
该构建脚本是指 Rust 里的 build.rs
构建

由于 gRPC 的 protobuf 是需要先进行编译, 因此 Rust 中也不破例,需要进行编写构建规则,就需要在 项目根目录下 创建一个 build.rs 文件并编辑其内容如下:
  1. fn main() -> Result<(), Box<dyn std::error::Error>> {
  2.    
  3.     println!("tonic::build is compiling proto file ...");
  4.     // compile_protos 函数的参数是我们编写的的 proto 文件的路径,可以是相对路径
  5.     tonic_build::compile_protos("proto/helloworld.proto")?;
  6.     Ok(())
  7. }
复制代码
服务端实现

然后再实现 gRPC 中服务端,首先需要在源码目录下创建两个文件如 Cargo.toml 中所形貌而那样,如下:
  1. touch src/server.rs && touch src/client.rs
复制代码
通过上述命令在 src/ 目录下创建如下两个文件:

  • server.rs : 是 gRPC 的服务端实现
  • client.rs : 是 gRPC 的客户端 (调用端)
然后再编写其内容:
  1. use tonic::{transport::Server, Request, Response, Status};
  2. use helloworld::greeter_server::{Greeter, GreeterServer};
  3. use helloworld::{RpcRequest, RpcResponse};
  4. pub mod helloworld {
  5.     /// 此时的 `helloworld` 是在 proto 文件里定义的 `package` 值对应
  6.     tonic::include_proto!("helloworld");
  7. }
  8. /// 定义一个自定义 struct,  若有必要,可以实现 App State , 就存储应用状态 (定义字段)
  9. #[derive(Debug, Default)]
  10. pub struct MyGreeter {}
  11. /// 为自己的 struct 实现在 proto 文件里所定义的 `Trait`, 如下面所见,是异步
  12. /// 因此需要通过 `#[tonic::async_trait]` 宏来修饰
  13. #[tonic::async_trait]
  14. impl Greeter for MyGreeter {
  15.     /// 函数签名,有一个 `&self`
  16.     async fn say_hello(
  17.         &self,
  18.         request: Request<RpcRequest>,
  19.     ) -> Result<Response<RpcResponse>, Status> {
  20.         // 函数内部实现
  21.         println!("GOT a new request on say_hello with {:?}", request);
  22.         
  23.         // 构造返回值
  24.         let reply = helloworld::RpcResponse {
  25.             content: format!("this response from server"),
  26.         };
  27.         
  28.         // 函数返回
  29.         Ok(Response::new(reply))
  30.     }
  31. }
  32. #[tokio::main]
  33. async fn main() -> Result<(), Box<dyn std::error::Error>> {
  34.     // gRPC 服务端地址
  35.     let address = "[::1]:50051".parse()?;
  36.     let greeter = MyGreeter::default();
  37.     println!("RPC Server is starting on {:?} ...", address);
  38.    
  39.     // 启动服务端
  40.     let server = Server::builder()
  41.         .add_service(GreeterServer::new(greeter))  // 注册定义和实现的接口
  42.         .serve(address);
  43.     println!("RPC Server is running ...");
  44.     server.await?;
  45.     Ok(())
  46. }
复制代码
客户端实现

接下来需要编辑 client.rs 的内容来长途调用服务端提供的接口,其内容如下:
  1. use helloworld::greeter_client::GreeterClient;
  2. use helloworld::RpcRequest;
  3. pub mod helloworld{
  4.     /// 同样引入 proto  
  5.     tonic::include_proto!("helloworld");
  6. }
  7. #[tokio::main]
  8. async fn main() -> Result<(), Box<dyn std::error::Error>>{
  9.     // 连接服务端
  10.     let mut client = GreeterClient::connect("http://[::1]:50051").await?;
  11.    
  12.     // 构造请求
  13.     let request = tonic::Request::new(RpcRequest{
  14.         content: "this request is from client".to_owned()
  15.     });
  16.    
  17.     // 调用函数并携带参数,等执行完之后获取返回值,该方式如调用本地方法一样,比较直观
  18.     let response = client.say_hello(request).await?;
  19.    
  20.     println!("response is {:?}", response);
  21.    
  22.     Ok(())
  23. }
复制代码
启动和测试

首先需要启动 gRPC 服务端,使用如下命令:
  1. cargo run --bin server
复制代码
其输出内容如下:
  1. rusty:~/Documents/projects/rs/tonic-helloworld$ cargo run --bin server
  2.     Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
  3.      Running `target/debug/server`
  4. RPC Server is starting on [::1]:50051 ...
  5. RPC Server is running ...
复制代码
如上述所见,服务端已经启动,接下来启动客户端来实现调用,如下命令:
  1. cargo run --bin client
复制代码
输出内容如下:
  1. rusty:~/Documents/projects/rs/tonic-helloworld$ cargo run --bin client
  2.    Compiling tonic-helloworld v0.1.0 (/home/xxx/Documents/projects/rs/tonic-helloworld)
  3.     Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.04s
  4.      Running `target/debug/client`
  5. 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 }
复制代码
终 !

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

祗疼妳一个

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表