cover_image

Rust gRPC开发

李明 coding到灯火阑珊
2022年07月23日 02:47



在这篇文章中,我们将使用gRPC创建一个基本的Todo应用程序。首先,我们将非常快速的概述一下gRPC和Protocol Buffers


什么是gRPC

gRPC是一个现代的开源的高性能远程过程调用(RPC)框架,可以在任何环境下运行。RPC代表远程过程调用(Remote Procedure Call),开头的g代表通用(General Purpose),对某些人来说,它代表谷歌。


我假设你熟悉常见的REST api,它们通过JSON对象进行通信。在gRPC中,我们使用Protocol Buffers来序列化数据,而不是JSON。



Protocol Buffers

Protocol Buffers是谷歌定制的用于语言无关、平台无关、可扩展的,序列化结构性数据的一种协议。


在gRPC中,传输的(序列化的)数据是二进制的。这意味着它比JSON或XML更快,因为它占用更少的空间,而更少的空间带来更小的带宽。



Rust gRPC开发

首先,创建一个新的工程:

cargo new rust-grpc


现在,在创建工程之后,我们为gRPC添加一些依赖项,并在Cargo.toml中定义二进制程序,用于我们的服务器和客户端:

[[bin]]name = "grpc-server"path = "src/server.rs"
[[bin]]name = "grpc-client"path = "src/client.rs"
[dependencies]tonic = "0.7"prost = "0.10"tokio = { version = "1.19", features = ["rt-multi-thread", "macros"] }
[build-dependencies]tonic-build = "0.7"


  • tonic:一个基于HTTP/2的Rust gRPC库

  • tonic-build:编译proto文件成Rust代码

  • prost:Rust的Protocol Buffers实现库

  • tokio:Rust的异步运行时


创建proto文件

在项目路径下创建 proto/todo.proto:

syntax = "proto3";

import "google/protobuf/empty.proto";

package todo;

message TodoItem {
    string name = 1;
    string description = 2;
    int32 priority = 3;
    bool completed = 4;
}

message GetTodosResponse {
    repeated TodoItem todos = 1;
}

message CreateTodoRequest {
    string name = 1;
    string description = 2;
    int32 priority = 3;
}

message CreateTodoResponse {
    TodoItem todo = 1;
    bool status = 2;
}

service Todo {
    rpc GetTodos(google.protobuf.Empty) returns (GetTodosResponse);
    rpc CreateTodo(CreateTodoRequest) returns (CreateTodoResponse);
}


这是Proto文件的语法。让我们大概看一下:

  • syntax:定义了proto文件的版本

  • import:允许使用来自其他proto文件的定义。例如,由于GetTodos函数没有请求参数,所以导入empty.proto。

  • message:将message看作定义请求和响应的接口。分配给字段的数字表示字段编号。字段号是Protobuf的重要组成部分。它们用于标识二进制编码数据中的字段,这意味着它们不能在service的不同版本之间进行更改。

  • service:可以将其视为一个接口,在service中定义了函数、它们需要什么参数以及它们返回什么值。


编译proto文件

在项目目录下创建 build.rs :

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::configure(
        .compile(&["proto/todo.proto"], &["proto"])
        .unwrap();

    Ok(())
}


目录结构如下:

图片




Server端代码

src/server.rs

use std::sync::Mutex;

use todo::{
    todo_server::{Todo, TodoServer},
    CreateTodoRequest, CreateTodoResponse, GetTodosResponse, TodoItem,
};
use tonic::transport::Server;

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

#[derive(Debug, Default)]
pub struct TodoService {
    todos: Mutex<Vec<TodoItem>>,
}

#[tonic::async_trait]
impl Todo for TodoService {
    async fn get_todos(
        &self,
        _: tonic::Request<()
>,
    ) -> Result<tonic::Response<GetTodosResponse>, tonic::Status> 
{
        let message = GetTodosResponse {
            todos: self.todos.lock().unwrap().to_vec(),
        };

        Ok(tonic::Response::new(message))
    }

    async fn create_todo(
        &self,
        request: tonic::Request<CreateTodoRequest>,
    )
 -> Result<tonic::Response<CreateTodoResponse>, tonic::Status> 
{
        let payload = request.into_inner();

        let todo_item = TodoItem {
            name: payload.name,
            description: payload.description,
            priority: payload.priority,
            completed: false,
        };

        self.todos.lock().unwrap().push(todo_item.clone());

        let message = CreateTodoResponse {
            todo: Some(todo_item),
            status: true,
        };

        Ok(tonic::Response::new(message))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "0.0.0.0:50051".parse().unwrap();
    let todo_service = TodoService::default();

    Server::builder()
        .add_service(TodoServer::new(todo_service))
        .serve(addr)
        .await?;

    Ok(())
}


让我们快速看看发生了什么:

  • 首先,我们引入了来自tonic, Request, Response等所需的结构体。

  • 同样,我们也引入了从todo.proto编译的Rust struct,可以在/target/debug/build目录中看到它们。

  • 现在我们需要创建TodoService并为该服务实现Todo trait。

  • 然后,实现我们的函数,get_todos将简单地返回带有todo项的GetTodosResponse。create_todo函数将获取request请求,从TodoItem struct创建todo项,将其加入到self.todos中并返回CreateTodoResponse,创建todo并且状态为true。

  • 最后,我们在0.0.0.0:50051上启动服务器


Client端代码

src/client.rs

use crate::todo::{todo_client::TodoClient, CreateTodoRequest};

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = TodoClient::connect("http://0.0.0.0:50051").await?;

    let request = tonic::Request::new(());

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

    println!("{:?}", response.into_inner().todos);

    let create_request = tonic::Request::new(CreateTodoRequest {
        name: "test name".to_string(),
        description: "test description".to_string(),
        priority: 1,
    });

    let create_response = client.create_todo(create_request).await?;

    println!("{:?}", create_response.into_inner().todo);

    Ok(())
}


  • 同样,让我们快速看看这里发生了什么:

  • 首先,我们引入了从todo.proto编译的TodoClient和CreateTodoRequest。

  • 现在,我们在main函数中,连接到0.0.0.0:50051,并为get_todos创建一个空请求(你应该记得,我们在proto文件中没有GetTodos的请求参数)。然后发送请求,等待响应,最后打印;对于create_todo几乎相同,但这次我们将CreateTodoRequest struct传递给请求,然后再次打印。


运行

cargo buildcargo run --bin grpc-servercargo run --bin grpc-client


本文翻译自:

https://betterprogramming.pub/how-to-create-grpc-server-client-in-rust-4e37692229f0




Rust · 目录
上一篇Rust应用程序部署到Docker中下一篇Rust P2P网络应用实战-3 分布式键值存储程序
继续滑动看下一个
coding到灯火阑珊
向上滑动看下一个