Tonic is a Rust library for implementing gRPC clients and servers, supporting async/await syntax. With a focus on high performance, interoperability, and flexibility, it is a powerful Rust-based tool that can be used to build systems in production environments. In this article, I'll take a deep dive into Tonic's components, features, and how to get started quickly.
What is gRPC?
gRPC is a high-performance, open-source, general-purpose remote procedure call (RPC) framework designed to efficiently connect distributed systems. gRPC uses HTTP/2 as the transport protocol and Protobuf (Protocol Buffers) as the interface description language.
Tonic's architecture
Tonic consists of three main parts:
- Common gRPC implementation: Supports any HTTP/2 implementation and any encoding implemented through a series of generic traits.
- High-performance HTTP/2 implementation: Based on the hyper library, this is an HTTP/1.1 and HTTP/2 client and server built on a robust tokio stack.
- Prost-based code generation tool: for building clients and servers from protobuf definitions.
Key features:
- Bidirectional streaming: Supports simultaneous client and server streaming.
- High-performance Async IO: Leverage Rust's async/await syntax to implement high-performance asynchronous I/O operations.
- Interoperability: Interoperability with other gRPC implementations is supported.
- TLS support: TLS support based on rustls.
- Load balancing: Built-in load balancing.
- Custom metadata: You can add custom metadata to your request.
- Authentication: Supports multiple authentication mechanisms.
- Health check: The built-in health check feature is provided.
Installation and configuration
Tonic's minimum supported Rust version (MSRV) is 1.70. Here are the basic installation steps on different operating systems:
Ubuntu
sudo apt update && sudo apt upgrade -y
sudo apt install -y protobuf-compiler libprotobuf-dev
Alpine Linux
sudo apk add protoc protobuf-dev
macOS
Make sure Homebrew is already installed.
brew install protobuf
Windows
Download the latest version of the protoc-xx.y-win64.zip from here, unzip it and add its path to the system PATH. Then verify the installation in the command prompt:
protoc --version
Get started quickly
Tonic provides a wealth of examples to help you get started, including a simple "hello world" and a more complex "routeguide" example. Next, we'll show you how to create a basic gRPC service.
Create a project
First, create a new project:
cargo new tonic-hello-world
cd tonic-hello-world
To add a Tonic dependency in Cargo.toml:
[dependencies]
tonic = "x.x.x"
prost = "x.x.x"
tokio = { version = "x.x", features = ["full"] }
Create a Protobuf definition
创建一个Protobuf文件proto/helloworld.proto:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Configure the Tonic build plugin in Cargo.toml:
[build-dependencies]
tonic-build = "x.x.x"
Write build scripts
Create a build.rs file to generate Rust code:
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/helloworld.proto")?;
Ok(())
}
Implement gRPC services
Create a src/main.rs file and implement the service:
use tonic::{transport::Server, Request, Response, Status};
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloRequest, HelloReply};
use std::sync::Arc;
pub mod hello_world {
tonic::include_proto!("helloworld");
}
#[derive(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 = hello_world::HelloReply {
message: format!("Hello {}!", request.into_inner().name).into(),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
println!("GreeterServer listening on {}", addr);
Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
Run the service
Run the following command at the root of your project to generate Rust code and start the service:
cargo build
cargo run
Now, your gRPC service is running on [::1]:50051!
Advanced features
Bi-directional flow
Tonic supports bidirectional streaming, which is important for real-time communication applications. Here's an example of a simple bidirectional flow implementation:
use std::pin::Pin;
use tonic::{Request, Response, Status};
use tonic::transport::Server;
use tonic::Request::Stream;
use tokio_stream::Stream as TokioStream;
use futures_core::Stream;
use tokio_stream::wrappers::ReceiverStream;
pub mod chat {
tonic::include_proto!("chat");
}
#[derive(Default)]
pub struct ChatService {}
#[tonic::async_trait]
impl chat::chat_server::Chat for ChatService {
type ChatStream = Pin<Box<dyn TokioStream<Item = Result<chat::ChatReply, Status>> + Send>>;
async fn chat(
&self,
request: Request<Stream<chat::ChatRequest>>,
) -> Result<Response<Self::ChatStream>, Status> {
println!("Chat request received");
let mut stream = request.into_inner();
let (tx, rx) = tokio::sync::mpsc::channel(4);
tokio::spawn(async move {
while let Some(req) = stream.message().await.unwrap() {
let reply = chat::ChatReply {
message: format!("RE: {}", req.message),
};
tx.send(Ok(reply)).await.unwrap();
}
});
Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::ChatStream))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50052".parse().expect("Failed to parse address");
let chat_service = ChatService::default();
println!("ChatService listening on {}", addr);
Server::builder()
.add_service(chat::chat_server::ChatServer::new(chat_service))
.serve(addr)
.await?;
Ok(())
}
conclusion
Tonic's high performance and async/await features provide strong support for efficient gRPC communication. Whether it's a simple RPC call or a complex bidirectional flow communication, Tonic has demonstrated its performance in terms of high performance, interoperability, and flexibility.