Rust gRPC---Tonic教程

API

一个API做了两件事

  • 客户端发起请求Request
  • 服务端作出响应Response

REST是什么

REST(Representational State Transfer):表现层状态传输,是一种设计风格,通常将 HTTP API 称为 RESTful API、RESTful 服务或 REST 服务

  • 资源由URL决定
  • 通过GET、POST、PUT、DELETE、PATCH、OPTIONS、HEAD、TRACE方法来操作资源
  • 操作资源的表现形式是XML、HTML、JSON等格式
  • 由客户端保存状态

RPC是什么

RPC(Remote Procedure Call):远程过程调用 ,像调用本地函数一样调用远程函数,grpc是RPC的一种实现

Tonic是什么

tonic基于HTTP/2构建,grpc的Rust实现

Protocol Buffers

Protocol Buffers协议缓冲区

服务方法

允许定义四种服务方法

  • 一元RPC:客户端向服务端发送单个请求并得到单个响应,就像调用普通函数一样
  • 服务端流式RPC:客户端向服务端发送请求并获取流以读取一系列消息,客户端从返回的流中读取直到没有消息
  • 客户端流式RPC:客户端使用流编写一系列消息发送到服务端,等待服务端做出响应
  • 双向流式RPC:双方使用读写流发送一系列消息,两个流独立运行

Protocol Buffers语法

  • 文件名以.proto结尾
  • 类似于json,体积更小、速度更快,会生成本机语言绑定

message:定义数据传递格式

  • 一个proto文件可以定义多个消息,消息可以嵌套

  • required:proto2必填、proto3不需要填

  • optional:可选字段

  • repeate:可重复字段

  • 标识号:每个字段必须要有一个唯一的标识号,范围1~2^29 -1(19000-19999保留标识号不能用) service:RPC服务接口

认证

  • SSL/TLS认证
    • TLS(Transport layerSecurity)安全传输层,建立在TCP协议上,前身是SSL(Secure Socket Layer)安全套接字层,将应用层的报文进行加密后再交由TCP传输
  • 基于Token认证
  • 自定义认证

安装protobuf

github.com/protocolbuf...

解压后将路径加入Path环境变量

验证

css 复制代码
protoc --version

Tonic实战

创建项目

vbscript 复制代码
cargo new tonic-server

cargo new tonic-client

两个项目都添加依赖

toml 复制代码
[dependencies]
tonic = "0.12.2"
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
# grpc编码器
prost = "0.13.2"
# 我们只在构建的时候需要它
[build-dependencies]
tonic-build = "0.12.2"

在两个项目同级新建proto\order.proto

proto 复制代码
syntax = "proto3";// 指定proto版本
package order;// 指定包名
// 定义服务
service OrderService {
    rpc GetOrder (OrderRequest) returns (OrderResponse);
    rpc SetOrder (Order) returns (OrderResponse);
}
// 定义message类型
message OrderRequest {
    int32 id = 1;
}

message OrderResponse {
    int32 id = 1;
    string description = 2;
    double price = 3;
}

message Order {
    int32 id = 1;
    string description = 2;
    double price = 3;
}

两个项目根目录新建build.rs,用于编译客户端和服务端的代码

rust 复制代码
// 告诉tonic-build,需要编译哪些文件
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("../proto/order.proto")?;// 注意导入的是项目外也就是项目同级的文件夹
    Ok(())
}

此时两个项目编译

cargo build

生成的rust代码在target\debug\build\tonic-server-****\out\order.rs

server 项目新建src\order_service.rs

rust 复制代码
use tonic::{ Request, Response, Status };
use order::order_service_server::{ OrderService, OrderServiceServer };
use order::{ OrderRequest, OrderResponse, Order };

pub mod order {
    tonic::include_proto!("order");// 使用宏引入proto文件
}

#[derive(Debug, Default)] // 实现打印、默认值
pub struct MyOrderService;

#[tonic::async_trait] // 异步方法
impl OrderService for MyOrderService {
    // 获取订单
    async fn get_order(
        &self,
        request: Request<OrderRequest>
    ) -> Result<Response<OrderResponse>, Status> {
        let req = request.into_inner();
        let response = OrderResponse {
            id: req.id,
            description: format!("Order {}", req.id),
            price: 100.0,
        };
        Ok(Response::new(response))
    }
    // 创建订单
    async fn set_order(&self, request: Request<Order>) -> Result<Response<OrderResponse>, Status> {
        let order = request.into_inner();
        let response = OrderResponse {
            id: order.id,
            description: order.description,
            price: order.price,
        };
        Ok(Response::new(response))
    }
}
// 启动grpc服务
pub fn create_server() -> OrderServiceServer<MyOrderService> {
    OrderServiceServer::new(MyOrderService::default())
}

server 项目main.rs

rust 复制代码
mod order_service;
use order_service::create_server;
use tonic::transport::Server;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 监听端口
    let addr = "[::1]:50051".parse()?;
    // 创建grpc服务
    let order_service = create_server();
    println!("OrderService listening on {}", addr);
    // 启动服务
    Server::builder().add_service(order_service).serve(addr).await?;
    Ok(())
}

此时启动server项目

arduino 复制代码
cargo run

使用Apifox创建grpc请求

设置端口[::1]:50051

导入proto文件

此时调用即可响应请求

json 复制代码
{
    "id": 0,
    "description": "Order 0",
    "price": 100
}

client 项目main.rs

rust 复制代码
use order::order_service_client::OrderServiceClient;
use order::{ OrderRequest, Order };

pub mod order {
    tonic::include_proto!("order");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接到gRPC服务端
    let mut client = OrderServiceClient::connect("http://[::1]:50051").await?;
    // 创建请求
    let request = tonic::Request::new(OrderRequest { id: 1 });
    // 发送请求并等待响应
    let response = client.get_order(request).await?;
    println!("RESPONSE={:?}", response);

    let order = Order {
        id: 1,
        description: "New Order".to_string(),
        price: 150.0,
    };
    // 创建请求
    let request = tonic::Request::new(order);
    // 发送请求并等待响应
    let response = client.set_order(request).await?;
    println!("RESPONSE={:?}", response);

    Ok(())
}

运行即可调用服务端的方法

rust 复制代码
cargo run

响应数据

css 复制代码
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponse { id: 1, description: "Order 1", price: 100.0 }, extensions: Extensions }
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 tent-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponse { id: 1, description: "Order 1", price: 100.0 }, extensions: Extensions }
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponions: Extensions }
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderRespontent-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponse { id: 1, description: "New Order", price: 150.0 }, extensions: Extensions }

一元RPC、服务端流式RPC、客户端流式RPC、双向流式RPC

example.proto

proto 复制代码
syntax = "proto3";

package example;

service ExampleService {
  // Unary RPC
  rpc UnaryCall(RequestMessage) returns (ResponseMessage);

  // Server-side streaming RPC
  rpc ServerStream(RequestMessage) returns (stream ResponseMessage);

  // Client-side streaming RPC
  rpc ClientStream(stream RequestMessage) returns (ResponseMessage);

  // Bidirectional streaming RPC
  rpc BidiStream(stream RequestMessage) returns (stream ResponseMessage);
}

message RequestMessage {
  string message = 1;
}

message ResponseMessage {
  string message = 1;
}

server 项目build.rs

rust 复制代码
// 告诉tonic-build,需要编译哪些文件
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("../proto/example.proto")?;// 注意导入的是项目外也就是项目同级的文件夹
    Ok(())
}

server 项目cargo.toml

toml 复制代码
[dependencies]
tonic = "0.12.2"
tokio = { version = "1.40.0", features = ["full"] }
prost = "0.13.2"
futures-util = "0.3.30"
tokio-stream = "0.1.14"
tonic-reflection = "0.12.2"
[build-dependencies]
tonic-build = "0.12.2"

server 项目main.rs

rust 复制代码
use tonic::{ transport::Server, Request, Response, Status };
use futures_util::Stream; // 使用 futures_util 提供的 Stream trait
use tokio_stream::wrappers::ReceiverStream; // 引入 tokio_stream
use tokio::sync::mpsc;
use std::pin::Pin;
use example::example_service_server::{ ExampleService, ExampleServiceServer };
use example::{ RequestMessage, ResponseMessage };

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

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

#[tonic::async_trait]
impl ExampleService for MyExampleService {
    //    1. 一元 RPC 调用
    async fn unary_call(
        &self,
        request: Request<RequestMessage>
    ) -> Result<Response<ResponseMessage>, Status> {
        println!("一元调用");
        let message = request.into_inner().message;
        Ok(
            Response::new(ResponseMessage {
                message: format!("Hello from Unary: {}", message),
            })
        )
    }

    // 2. 服务端流式 RPC 调用
    type ServerStreamStream = Pin<Box<dyn Stream<Item = Result<ResponseMessage, Status>> + Send>>;

    async fn server_stream(
        &self,
        request: Request<RequestMessage>
    ) -> Result<Response<Self::ServerStreamStream>, Status> {
        println!("服务端流式调用");
        let message = request.into_inner().message;

        let (tx, rx) = mpsc::channel(4);
        tokio::spawn(async move {
            for i in 1..=5 {
                tx.send(
                    Ok(ResponseMessage {
                        message: format!("Stream {}: {}", i, message),
                    })
                ).await.unwrap();
            }
            println!("流结束");
        });

        Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
    }

    // 3. 客户端流式 RPC 调用
    async fn client_stream(
        &self,
        request: Request<tonic::Streaming<RequestMessage>>
    ) -> Result<Response<ResponseMessage>, Status> {
        println!("客户端流式调用");
        let mut stream = request.into_inner();
        let mut messages = vec![];

        while let Some(req) = stream.message().await? {
            messages.push(req.message);
        }

        Ok(
            Response::new(ResponseMessage {
                message: format!("Received: {:?}", messages),
            })
        )
    }

    // 4. 双向流式 RPC 调用
    type BidiStreamStream = Pin<Box<dyn Stream<Item = Result<ResponseMessage, Status>> + Send>>;

    async fn bidi_stream(
        &self,
        request: Request<tonic::Streaming<RequestMessage>>
    ) -> Result<Response<Self::BidiStreamStream>, Status> {
        println!("双向流式调用");
        let mut stream = request.into_inner();
        let (tx, rx) = mpsc::channel(4);

        tokio::spawn(async move {
            while let Some(req) = stream.message().await.unwrap() {
                tx.send(
                    Ok(ResponseMessage {
                        message: format!("Echo: {}", req.message),
                    })
                ).await.unwrap();
            }
            println!("流结束");
        });

        Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse()?;
    let example_service = MyExampleService::default();

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

    Server::builder().add_service(ExampleServiceServer::new(example_service)).serve(addr).await?;

    Ok(())
}

此时cargo buildcargo run即可用apifox发送rpc请求

注意我们更改了proto文件,需要重新指定

一元RPC

服务端流式 RPC

客户端流式 RPC

双向流式 RPC 调用

client 项目main.rs

rust 复制代码
use tonic::Request;
use example::example_service_client::ExampleServiceClient;
use example::{ RequestMessage };

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接到服务端
    let mut client = ExampleServiceClient::connect("http://[::1]:50051").await?;

    // 1. 一元 RPC 调用
    let request = Request::new(RequestMessage {
        message: String::from("Unary Hello"),
    });
    let response = client.unary_call(request).await?;
    println!("Unary Response: {:?}", response.into_inner());

    // 2. 服务器流式 RPC 调用
    let request = Request::new(RequestMessage {
        message: String::from("Stream Hello"),
    });
    let mut stream = client.server_stream(request).await?.into_inner();
    while let Some(res) = stream.message().await? {
        println!("Server Stream Response: {:?}", res);
    }
    Ok(())
}

由于proto生成的rust方法在不同的位置,例如server生成的代码在server/target***下,客户端无法调用server提供的方法,我们需要将代码生成的目录设置为两个项目的公共目录

改造为gRPC微服务通信

  • 反射(Reflection):允许客户端在运行时发现 gRPC 服务所提供的方法、消息类型等信息,而无需在编译时就知道这些详细信息,但是它只提供了一种动态发现服务的能力,你可以用postman、apifox的反射调用而不需要导入proto文件,在编写客户端代码时仍然需要导入生成后的代码

反射仅仅是为了让客户端在运行时查询服务定义,而不是为了消除客户端对服务定义的依赖

项目结构 创建公共目录proto proto\example.proto

proto 复制代码
syntax = "proto3";

package example;

service ExampleService {
  // Unary RPC
  rpc UnaryCall(RequestMessage) returns (ResponseMessage);

  // Server-side streaming RPC
  rpc ServerStream(RequestMessage) returns (stream ResponseMessage);

  // Client-side streaming RPC
  rpc ClientStream(stream RequestMessage) returns (ResponseMessage);

  // Bidirectional streaming RPC
  rpc BidiStream(stream RequestMessage) returns (stream ResponseMessage);
}

message RequestMessage {
  string message = 1;
}

message ResponseMessage {
  string message = 1;
}

两个项目的cargo.toml

toml 复制代码
[dependencies]
tonic = "0.12.2"
tokio = { version = "1.40.0", features = ["full"] }
prost = "0.13.3"
tokio-stream = "0.1.14"
tonic-reflection = "0.12.2"
[build-dependencies]
tonic-build = "0.12.2"

server\build.rs

rust 复制代码
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = PathBuf::from("../proto");
    // let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); // 指定输出目录在target下
    tonic_build
        ::configure()
        .build_server(true)// 指定是否生成服务端代码
        .out_dir("src/generated")// 指定rust文件输出目录
        .file_descriptor_set_path(out_dir.join("example_descriptor.bin")) // 生成文件描述符集
        .compile(&["../proto/example.proto"], &["../proto"])
        .unwrap();
    Ok(())
}

client\build.rs

rust 复制代码
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = PathBuf::from("../proto");
    // let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); // 指定输出目录在target下
    tonic_build
        ::configure()
        .build_client(true)// 指定是否生成客户端端代码
        .out_dir("src/generated")// 指定rust文件输出目录
        .file_descriptor_set_path(out_dir.join("example_descriptor.bin")) // 生成文件描述符集
        .compile(&["../proto/example.proto"], &["../proto"])
        .unwrap();
    Ok(())
}
  • .build_client(true):生成客户端Rust代码
  • .build_server(true):生成服务端Rust代码
  • .out_dir("src/generated"):指定生成的Rust代码的目录,cargo build你会发现生成的example.rs内容是一样的,因为我们定义的请求与响应的proto文件内容是一样的
  • .file_descriptor_set_path:生成文件描述符集,用于创建反射服务
  • .compile(&["../proto/example.proto"], &["../proto"]):指定编译的proto文件及其目录

server\src\main.rs

rust 复制代码
mod server_lib;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    server_lib::start_server().await?;
    Ok(())
}

server\src\server_lib.rs 这里我们添加了反射

rust 复制代码
use tonic::{ Request, Response, Status, Streaming };
use tokio_stream::{ wrappers::ReceiverStream, StreamExt };
use std::pin::Pin;
use tokio::sync::mpsc;

pub mod example {
    // 1. 导入文件描述符集
    pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("../../proto/example_descriptor.bin");
    // tonic::include_proto!("example");// 你可以ctrl点击查看源码,它是一个宏,添加了env的编译目录,我们这里指定生成的rust目录,所以直接导入即可
    include!("./generated/example.rs");
}

use example::example_service_server::{ ExampleService, ExampleServiceServer };
use example::{ RequestMessage, ResponseMessage };

#[derive(Debug, Default)]
pub struct MyExampleService;

#[tonic::async_trait]
impl ExampleService for MyExampleService {
    // 一元RPC调用
    async fn unary_call(
        &self,
        request: Request<RequestMessage>
    ) -> Result<Response<ResponseMessage>, Status> {
        let response = ResponseMessage {
            message: format!("Unary response to {}", request.into_inner().message),
        };
        Ok(Response::new(response))
    }

    type ServerStreamStream = Pin<
        Box<dyn tokio_stream::Stream<Item = Result<ResponseMessage, Status>> + Send>
    >;
    // 服务端流式RPC调用
    async fn server_stream(
        &self,
        request: Request<RequestMessage>
    ) -> Result<Response<Self::ServerStreamStream>, Status> {
        let message = request.into_inner().message;
        let (tx, rx) = mpsc::channel(4);

        tokio::spawn(async move {
            for i in 0..3 {
                let response = ResponseMessage {
                    message: format!("Stream response {} to {}", i, message),
                };
                tx.send(Ok(response)).await.unwrap();
            }
        });

        Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::ServerStreamStream))
    }
    // 客户端流式RPC调用
    async fn client_stream(
        &self,
        request: Request<Streaming<RequestMessage>>
    ) -> Result<Response<ResponseMessage>, Status> {
        let mut stream = request.into_inner();
        let mut messages = Vec::new();

        while let Some(req) = stream.next().await {
            let req = req?;
            messages.push(req.message);
        }

        let response = ResponseMessage {
            message: format!("Received messages: {:?}", messages.join(", ")),
        };
        Ok(Response::new(response))
    }

    type BidiStreamStream = Pin<
        Box<dyn tokio_stream::Stream<Item = Result<ResponseMessage, Status>> + Send>
    >;
    // 双向流式RPC调用
    async fn bidi_stream(
        &self,
        request: Request<Streaming<RequestMessage>>
    ) -> Result<Response<Self::BidiStreamStream>, Status> {
        let mut stream = request.into_inner();
        let (tx, rx) = mpsc::channel(4);

        tokio::spawn(async move {
            while let Some(req) = stream.next().await {
                let req = req.unwrap();
                let response = ResponseMessage {
                    message: format!("Echo: {}", req.message),
                };
                tx.send(Ok(response)).await.unwrap();
            }
        });

        Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::BidiStreamStream))
    }
}

pub async fn start_server() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse()?;
    let example_service = MyExampleService::default();

    println!("Server listening on {}", addr);
    // 2. 创建反射服务
    let reflection_service = tonic_reflection::server::Builder
        ::configure()
        .register_encoded_file_descriptor_set(example::FILE_DESCRIPTOR_SET)
        .build_v1()?;

    tonic::transport::Server
        ::builder()
        .add_service(ExampleServiceServer::new(example_service))
        .add_service(reflection_service) // 3. 添加反射服务
        .serve(addr).await?;

    Ok(())
}

tonic-client\src\main.rs你可以在生成的client\src\generated\example.rs查看client端提供的方法

rust 复制代码
use tonic::transport::Channel;
use example::example_service_client::ExampleServiceClient;
use example::RequestMessage;
use tokio_stream::StreamExt;

pub mod example {
    // tonic::include_proto!("example");
    include!("./generated/example.rs");
}
// 一元RPC调用
async fn unary_call(
    client: &mut ExampleServiceClient<Channel>
) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(RequestMessage {
        message: "Hello from unary".into(),
    });
    let response = client.unary_call(request).await?;
    println!("Unary response: {:?}", response.into_inner());
    Ok(())
}
// 服务端流式RPC调用
async fn server_stream(
    client: &mut ExampleServiceClient<Channel>
) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(RequestMessage {
        message: "Hello from stream".into(),
    });
    let mut response = client.server_stream(request).await?.into_inner();
    while let Some(res) = response.next().await {
        println!("Server stream response: {:?}", res?);
    }
    Ok(())
}
// 客户端流式RPC调用
async fn client_stream(
    client: &mut ExampleServiceClient<Channel>
) -> Result<(), Box<dyn std::error::Error>> {
    let request = tokio_stream::iter(
        vec![
            RequestMessage { message: "Hello".into() },
            RequestMessage { message: "from".into() },
            RequestMessage { message: "client".into() }
        ]
    );
    let response = client.client_stream(request).await?;
    println!("Client stream response: {:?}", response.into_inner());
    Ok(())
}
// 双向流式RPC调用
async fn bidi_stream(
    client: &mut ExampleServiceClient<Channel>
) -> Result<(), Box<dyn std::error::Error>> {
    let request = tokio_stream::iter(
        vec![
            RequestMessage { message: "Hello".into() },
            RequestMessage { message: "from".into() },
            RequestMessage { message: "bidi".into() }
        ]
    );
    let mut response = client.bidi_stream(request).await?.into_inner();
    while let Some(res) = response.next().await {
        println!("Bidi stream response: {:?}", res?);
    }
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接到gRPC服务器
    let mut client = ExampleServiceClient::connect("http://[::1]:50051").await?;

    unary_call(&mut client).await?;
    server_stream(&mut client).await?;
    client_stream(&mut client).await?;
    bidi_stream(&mut client).await?;

    Ok(())
}

此时cargo run两个服务即可看到响应信息,你也可以用postman、apifox请求

tonic超时处理、认证看官方例子就可以了github.com/hyperium/to...

bin crate实现client\server(可选了解)

  • bin:二进制可执行文件
  • lib:库crate 一般来说是用两个服务,两个服务间通信用rpc,bin crate的方法直接调用就好了,很多教程都是用的bin,包括官方例子也是用的bin

这里给出的是在main中启动服务端和客户端,可能你在嵌入式中使用gRPC?

如果你想作为bin使用gRPC,你可以在cargo.toml里添加[[bin]]指定crate,bin crate会编译为单独的二进制文件

toml 复制代码
[[bin]]
name="client"
path="src/example_client.rs"
[[bin]]
name="server"
path="src/example_server.rs"

在client、server编写main函数

然后cargo run --bin servercargo run --bin client

cargo.toml

toml 复制代码
[dependencies]
tonic = "0.12.2"
tokio = { version = "1.40.0", features = ["full"] }
prost = "0.13.3"
tokio-stream = "0.1.14"
tonic-reflection = "0.12.2"
[build-dependencies]
tonic-build = "0.12.2"

../proto/example.proto

proto 复制代码
syntax = "proto3";

package example;

service ExampleService {
  // Unary RPC
  rpc UnaryCall(RequestMessage) returns (ResponseMessage);

  // Server-side streaming RPC
  rpc ServerStream(RequestMessage) returns (stream ResponseMessage);

  // Client-side streaming RPC
  rpc ClientStream(stream RequestMessage) returns (ResponseMessage);

  // Bidirectional streaming RPC
  rpc BidiStream(stream RequestMessage) returns (stream ResponseMessage);
}

message RequestMessage {
  string message = 1;
}

message ResponseMessage {
  string message = 1;
}

build.rs

rust 复制代码
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build
        ::configure()
        .compile(&["../proto/example.proto"], &["../proto"])
        .unwrap();
    Ok(())
}

src/example_client.rs

rust 复制代码
use example::example_service_client::ExampleServiceClient;
use example::RequestMessage;
use tokio_stream::StreamExt;

pub mod example {
    tonic::include_proto!("example");
}
// 一元RPC
pub async fn unary_call() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = ExampleServiceClient::connect("http://[::1]:50051").await?;
    let request = tonic::Request::new(RequestMessage {
        message: "Hello from unary".into(),
    });
    let response = client.unary_call(request).await?;
    println!("Unary response: {:?}", response.into_inner());
    Ok(())
}
// 服务端流式RPC
pub async fn server_stream() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = ExampleServiceClient::connect("http://[::1]:50051").await?;
    let request = tonic::Request::new(RequestMessage {
        message: "Hello from stream".into(),
    });
    let mut response = client.server_stream(request).await?.into_inner();
    while let Some(res) = response.next().await {
        println!("Server stream response: {:?}", res?);
    }
    Ok(())
}
// 客户端流式RPC
pub async fn client_stream() -> Result<(), Box<dyn std::error::Error>> {
    let mut client_stream = ExampleServiceClient::connect("http://[::1]:50051").await?;
    let request = tokio_stream::iter(
        vec![
            RequestMessage { message: "Hello".into() }, 
            RequestMessage { message: "from".into() },
            RequestMessage { message: "client".into() }
        ]
    );
    let response = client_stream.client_stream(request).await?;
    println!("Client stream response: {:?}", response.into_inner());
    Ok(())
}
// 双向流式RPC
pub async fn bidi_stream() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = ExampleServiceClient::connect("http://[::1]:50051").await?;
    let request = tokio_stream::iter(
        vec![
            RequestMessage { message: "Hello".into() },
            RequestMessage { message: "from".into() },
            RequestMessage { message: "bidi".into() }
        ]
    );
    let mut response = client.bidi_stream(request).await?.into_inner();
    while let Some(res) = response.next().await {
        println!("Bidi stream response: {:?}", res?);
    }
    Ok(())
}

src/example_server.rs

rust 复制代码
use tonic::{ transport::Server, Request, Response, Status, Streaming };
use tokio_stream::{ wrappers::ReceiverStream, StreamExt };
use std::pin::Pin;
use tokio::sync::mpsc;

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

use example::example_service_server::{ ExampleService, ExampleServiceServer };
use example::{ RequestMessage, ResponseMessage };

#[derive(Debug, Default)]
pub struct MyExampleService;

#[tonic::async_trait]
impl ExampleService for MyExampleService {
    async fn unary_call(
        &self,
        request: Request<RequestMessage>
    ) -> Result<Response<ResponseMessage>, Status> {
        let response = ResponseMessage {
            message: format!("Unary response to {}", request.into_inner().message),
        };
        Ok(Response::new(response))
    }

    type ServerStreamStream = Pin<
        Box<dyn tokio_stream::Stream<Item = Result<ResponseMessage, Status>> + Send>
    >;

    async fn server_stream(
        &self,
        request: Request<RequestMessage>
    ) -> Result<Response<Self::ServerStreamStream>, Status> {
        let message = request.into_inner().message;
        let (tx, rx) = mpsc::channel(4);

        tokio::spawn(async move {
            for i in 0..3 {
                let response = ResponseMessage {
                    message: format!("Stream response {} to {}", i, message),
                };
                tx.send(Ok(response)).await.unwrap();
            }
        });

        Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::ServerStreamStream))
    }

    async fn client_stream(
        &self,
        request: Request<Streaming<RequestMessage>>
    ) -> Result<Response<ResponseMessage>, Status> {
        let mut stream = request.into_inner();
        let mut messages = Vec::new();

        while let Some(req) = stream.next().await {
            let req = req?;
            messages.push(req.message);
        }

        let response = ResponseMessage {
            message: format!("Received messages: {:?}", messages.join(", ")),
        };
        Ok(Response::new(response))
    }

    type BidiStreamStream = Pin<
        Box<dyn tokio_stream::Stream<Item = Result<ResponseMessage, Status>> + Send>
    >;

    async fn bidi_stream(
        &self,
        request: Request<Streaming<RequestMessage>>
    ) -> Result<Response<Self::BidiStreamStream>, Status> {
        let mut stream = request.into_inner();
        let (tx, rx) = mpsc::channel(4);

        tokio::spawn(async move {
            while let Some(req) = stream.next().await {
                let req = req.unwrap();
                let response = ResponseMessage {
                    message: format!("Echo: {}", req.message),
                };
                tx.send(Ok(response)).await.unwrap();
            }
        });

        Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::BidiStreamStream))
    }
}

pub async fn start_server() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse()?;
    let example_service = MyExampleService::default();

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

    Server::builder().add_service(ExampleServiceServer::new(example_service)).serve(addr).await?;

    Ok(())
}

main.rs

rust 复制代码
use tokio::task;
mod example_client;
mod example_server;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // 启动服务端 
    task::spawn(async {
        example_server::start_server().await.unwrap();
    });

    // 延时
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;

    // 调用客户端
    example_client::unary_call().await?;
    example_client::server_stream().await?;
    example_client::client_stream().await?;
    example_client::bidi_stream().await?;

    Ok(())
}

cargo build然后cargo run即可看到调用结果

bash 复制代码
Unary response: ResponseMessage { message: "Unary response to Hello from unary" }
Server stream response: ResponseMessage { message: "Stream response 0 to Hello from stream" }
Server stream response: ResponseMessage { message: "Stream response 1 to Hello from stream" }
Server stream response: ResponseMessage { message: "Stream response 2 to Hello from stream" }
Client stream response: ResponseMessage { message: "Received messages: \"Hello, from, client\"" }
Bidi stream response: ResponseMessage { message: "Echo: Hello" }       
Bidi stream response: ResponseMessage { message: "Echo: from" }        
Bidi stream response: ResponseMessage { message: "Echo: bidi" } 
相关推荐
蜡笔小流31 分钟前
Flask 第六课 -- 路由
后端·python·flask
怪人细胞36 分钟前
【远程调用PythonAPI-flask】
后端·python·flask
代码吐槽菌43 分钟前
基于SpringBoot的在线点餐系统【附源码】
java·开发语言·spring boot·后端·mysql·计算机专业
X² 编程说2 小时前
14.面试算法-字符串常见算法题(三)
java·数据结构·后端·算法·面试
请不要叫我菜鸡3 小时前
Go语言基础学习02-命令源码文件;库源码文件;类型推断;变量重声明
linux·后端·golang·类型推断·短变量·变量重声明·库源码文件
x-cmd3 小时前
x-cmd pkg | bat: cat 命令现代化替代品,终端用户必备工具
运维·python·rust·终端·命令行·bat·cat
AskHarries3 小时前
Spring Boot集成Akka Cluster快速入门Demo
java·spring boot·后端·akka
少喝冰美式3 小时前
【大模型教程】如何在Spring Boot中无缝集成LangChain4j,玩转AI大模型!
人工智能·spring boot·后端·langchain·llm·ai大模型·计算机技术
lucifer3113 小时前
Spring Boot 中的配置属性绑定机制详解
java·后端