前一篇,我们演示了 rust grpc 的应用,但 grpc 是基于 http 的,按理说其协议更重,同时也确见到网友验证过,相比 http 的 rpc,tpc 下的 rpc 性能确实更有优势。
同时,不同于 grpc 要编写一份 “中间文件” (protobuf 文件),tcp rpc 可以认为是 直接 rust 到 rust 的编程,没有 “中间文件” 的编写,编写期代码提示,语法校验,都是原生的。因此,本篇也进行一个 tcp rpc 的演示。
- Tcp rpc 的劣势也较明显,跨语言调用能力较弱,纯 rust 体系会更适用。
Tcp 版 rpc 方案不多,本例直接使用 tarpc
- Tarpc - https://github.com/google/tarpc,3k Star、940 Commits、2024-03 updated。
- Tarpc 的编解码库为 bincode-org/bincode,据说性能极高。
- 己知使用 tarpc 的项目有 solana。(Solana 同时使用了 tonic 及 tarpc)
目录结构
.
├── Cargo.toml
├── README.md
└── src├── client.rs├── lib.rs└── server.rs
创建项目
- 创建一个 lib 项目:
cargo new tarpc --lib
- Cargo.toml
[package]
name = "tarpc"
version = "0.1.0"
edition = "2021"
description = "An example server built on tarpc."[dependencies]
anyhow = "1.0"
futures = "0.3"
tarpc = { version = "0.34", features = ["full"] }
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread"] }
clap = { version = "4.4.18", features = ["derive"] } # 与 rpc 无关, 仅演示、从命令行读取参数[lib]
name = "service"
path = "src/lib.rs"[[bin]]
name = "tarpc_server"
path = "src/server.rs"[[bin]]
name = "tarpc_client"
path = "src/client.rs"
定义服务
- tarpc/src/lib.rs
#[tarpc::service]
pub trait Hello {async fn hello(name: String) -> String;
}
服务实现
- tarpc/src/server.rs
use std::net::{IpAddr, Ipv6Addr, SocketAddr};use futures::{future, prelude::*};
use tarpc::{context, server::{self, Channel, incoming::Incoming}, tokio_serde::formats::Json};use service::Hello;#[derive(Clone)]
struct HelloServer(SocketAddr);impl Hello for HelloServer {async fn hello(self, _: context::Context, name: String) -> String {format!("Hello, {name}! You are connected from {}", self.0)}
}#[tokio::main]
async fn main() -> anyhow::Result<()> {async fn spawn(fut: impl Future<Output=()> + Send + 'static) { tokio::spawn(fut); }let server_addr = (IpAddr::V6(Ipv6Addr::LOCALHOST), 50051); // IpAddr::from([0, 0, 0, 0])let mut listener = tarpc::serde_transport::tcp::listen(&server_addr, Json::default).await?;listener.config_mut().max_frame_length(usize::MAX);listener.filter_map(|r| future::ready(r.ok())) // Ignore accept errors..map(server::BaseChannel::with_defaults).max_channels_per_key(1, |t| t.transport().peer_addr().unwrap().ip()) // Limit channels to 1 per IP..map(|channel| {let server = HelloServer(channel.transport().peer_addr().unwrap());channel.execute(server.serve()).for_each(spawn)}).buffer_unordered(10) // Max 10 channels..for_each(|_| async {}).await;Ok(())
}
调用实现
- tarpc/src/client.rs
use std::net::SocketAddr;
use tarpc::{client, context, tokio_serde::formats::Json};
use service::HelloClient; // 由#[tarpc::service]提供#[tokio::main]
async fn main() -> anyhow::Result<()> {let mut transport = tarpc::serde_transport::tcp::connect("[::1]:50051", Json::default);transport.config_mut().max_frame_length(usize::MAX);let client = HelloClient::new(client::Config::default(), transport.await?).spawn();match client.hello(context::current(), format!("{}1", "Bob")).await {Ok(hello) => println!("{:?}", hello),Err(e) => panic!("{:?}", anyhow::Error::from(e)),}Ok(())
}
HelloClient
是由#[tarpc::service]
对pub trait Hello
提供的生成,直接引用就可以了。- 如遇 CLion IDE 对此引用 “漂红”,可尝试 JetBrains 新发布的 RustRover IDE,或使用 VScode + rust-analyzer 。
运行/测试
cargo run --bin tarpc_server
cargo run --bin tarpc_client
关于lib.rs共享
- 可以在各项目间直接复制,犹如 grpc 的 proto 一样;
- 可以建设私有 crates 仓库,并将服务定义的项目发布上去;
- 可以直接依赖 git 如:
xxx = { git = "http://github.com/xxx", branch = "master" }
。
完事~