2. 基础篇
2.1 gRPC 基础
gRPC 简介
gRPC 是一种高性能、开源的远程过程调用(RPC)框架,由 Google 开发。与 REST 相比,gRPC 具有更高的性能和更强的类型安全性。
性能:gRPC 使用 HTTP/2 协议,支持多路复用和流式传输。 类型安全:gRPC 使用 Protocol Buffers 作为接口定义语言(IDL),确保类型安全。
gRPC 支持四种通信模式:
Unary:客户端发送一个请求,服务器返回一个响应。 Server Streaming:客户端发送一个请求,服务器返回一个流式响应。 Client Streaming:客户端发送一个流式请求,服务器返回一个响应。 Bidirectional Streaming:客户端和服务器都可以发送流式请求和响应。
Protocol Buffers
Protocol Buffers(简称 Protobuf)是一种语言无关、平台无关、可扩展的序列化结构数据格式。
定义.proto
文件
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
生成代码
使用tonic-build
生成 Rust 代码。在build.rs
文件中添加以下内容:
fn main() {
tonic_build::compile_protos("proto/service.proto").unwrap();
}
运行cargo build
,Tonic 将会生成对应的 Rust 代码。
2.2 Tonic 核心概念
Service
服务接口定义了客户端和服务器之间的通信协议。
定义服务接口
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
实现服务接口
use tonic::{transport::Server, Request, Response, Status};
use helloworld::greeter_server::{Greeter, GreeterServer};
use helloworld::{HelloRequest, HelloReply};
pub mod helloworld {
tonic::include_proto!("helloworld");
}
#[derive(Debug, Default)]
pub struct MyGreeter {}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
println!("Got a request: {:?}", request);
let reply = HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
Channel
客户端连接通道用于与服务器进行通信。
客户端连接通道
use helloworld::greeter_client::GreeterClient;
use helloworld::HelloRequest;
pub mod helloworld {
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(HelloRequest {
name: "Tonic".into(),
});
let response = client.say_hello(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
配置连接参数
use tonic::transport::{Channel, ClientTlsConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let tls = ClientTlsConfig::new()
.domain_name("example.com");
let channel = Channel::from_static("https://example.com")
.tls_config(tls)?
.connect()
.await?;
let mut client = GreeterClient::new(channel);
// 调用服务
}
Request 和 Response
请求和响应的数据结构定义了客户端和服务器之间的数据交换格式。
请求和响应的数据结构
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
处理请求和响应
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
let name = request.into_inner().name;
let reply = HelloReply {
message: format!("Hello {}!", name),
};
Ok(Response::new(reply))
}
}
2.3 异步编程
Rust 异步编程基础
Rust 的异步编程基于async
/await
语法,Future
和Stream
是异步编程的核心概念。
async
/await
:用于编写异步代码。Future
:表示一个可能尚未完成的计算。Stream
:表示一系列异步值。
Tonic 中的异步编程
Tonic 支持异步编程,你可以使用async
/await
处理请求和响应。
使用async
/await
处理请求
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
let name = request.into_inner().name;
let reply = HelloReply {
message: format!("Hello {}!", name),
};
Ok(Response::new(reply))
}
}
处理流式请求和响应
#[tonic::async_trait]
impl Greeter for MyGreeter {
type SayHelloStream = Pin<Box<dyn Stream<Item = Result<HelloReply, Status>> + Send>>;
async fn say_hello_stream(
&self,
request: Request<Streaming<HelloRequest>>,
) -> Result<Response<Self::SayHelloStream>, Status> {
let mut stream = request.into_inner();
let output = async_stream::stream! {
while let Some(req) = stream.next().await {
let req = req?;
let reply = HelloReply {
message: format!("Hello {}!", req.name),
};
yield Ok(reply);
}
};
Ok(Response::new(Box::pin(output)))
}
}
2.4 错误处理
Rust 错误处理
Rust 的错误处理基于Result
和Option
类型,?
操作符用于简化错误传播。
Result
:表示一个可能失败的计算。Option
:表示一个可能不存在的值。?
操作符:用于简化错误传播。
Tonic 错误处理
Tonic 在处理 gRPC 请求时可能会遇到错误,你可以使用Result
类型来处理这些错误。
处理 gRPC 错误
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
let name = request.into_inner().name;
if name.is_empty() {
return Err(Status::invalid_argument("name is required"));
}
let reply = HelloReply {
message: format!("Hello {}!", name),
};
Ok(Response::new(reply))
}
}
自定义错误类型
#[derive(Debug)]
enum MyError {
GrpcError(Status),
// 其他错误类型
}
impl From<Status> for MyError {
fn from(err: Status) -> MyError {
MyError::GrpcError(err)
}
}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, MyError> {
let name = request.into_inner().name;
if name.is_empty() {
return Err(Status::invalid_argument("name is required").into());
}
let reply = HelloReply {
message: format!("Hello {}!", name),
};
Ok(Response::new(reply))
}
}
通过这篇实战教程,你已经深入了解了 Tonic 的核心概念和高级用法。Tonic 不仅提供了高效的 gRPC 服务构建功能,还支持异步编程和强大的错误处理机制,使得在 Rust 项目中使用 gRPC 变得更加灵活和强大。
无论身在何处
有我不再孤单孤单
长按识别二维码关注我们