LoginSignup
13
8

More than 3 years have passed since last update.

Googleが公開しているRustのRPCフレームワークtarpcを使ってみた

Last updated at Posted at 2020-12-23

tarpcとは

tarpcはRustでRPCを書くためのフレームワークで、Googleのオープンソースプロジェクトです。

公式には以下のように書いてあります。

tarpc is an RPC framework for rust with a focus on ease of use. Defining a service can be done in just a few lines of code, and most of the boilerplate of writing a server is taken care of for you.

簡単にサービス定義がかけることを重視しているようです。

また、

Disclaimer: This is not an official Google product.

とあるように、Googleが会社として開発しているわけではなく、Googleの1従業員が20%ルールとか、趣味とかで作ったものをGoogleの許可を得て公開しているものと思われます。

どんな感じで使うのか

以下に完全なサンプルがあるのでこれをベースにすれば簡単にできそうです。

注意:
このexample-serviceというサンプルでは、lib.rsにサービス定義が書いてあるんですが、これをserviceという名前空間にセットしています。
なので、client.rsにあるservice::WorldClientというのはlib.rsに書かれたWorldClient (#[tarpc::service]により自動生成)のことを意味しています。
サーバーのほうにはWorldServerみたいなのは自動実装されません。

サービス定義

サービス定義は以下のような感じです。通常traitではasyncなfunctionは定義出来ない(async-traitを使う)ので、#[tarpc::service]がいい感じに中身を展開してサービス定義にしてくれているようです。

#[tarpc::service]
pub trait World {
    /// Returns a greeting for name.
    async fn hello(name: String) -> String;
}

サービスの実装(サーバー側)

サービスは以下のような感じで実装します。README.mdにあるサンプルより簡単になっています。特に返り値がFutureじゃなくStringになっているあたり進化していますね。

#[derive(Clone)]
struct HelloServer(SocketAddr);

#[tarpc::server]
impl World for HelloServer {
    async fn hello(self, _: context::Context, name: String) -> String {
        format!("Hello, {}! You are connected from {:?}.", name, self.0)
    }
}

サンプルなので、引数と接続情報を返す意味のない関数になっています。

各バイナリの実装

あとは上記サービス定義を使ってサービスの実行ファイルを作ればよいです。

クライアント

trapc::serde_transport::tcpにRustではおなじみのserdeによるシリアライゼーションを利用したtcp通信が用意されています。

    let mut transport = tarpc::serde_transport::tcp::connect(server_addr, Json::default);
    transport.config_mut().max_frame_length(4294967296);
    let mut client =
        service::WorldClient::new(client::Config::default(), transport.await?).spawn()?;
    let hello = client.hello(context::current(), name).await?;

サーバー

こちらはクライントに比べるとちょっと複雑です、というか用意されているサンプルが最小限ではなくいろんなオプションを指定しています。

    let mut listener = tarpc::serde_transport::tcp::listen(&server_addr, Json::default).await?;
    listener.config_mut().max_frame_length(4294967296);
    listener
        .filter_map(|r| future::ready(r.ok()))
        .map(server::BaseChannel::with_defaults)
        .max_channels_per_key(1, |t| t.as_ref().peer_addr().unwrap().ip())
        .map(|channel| {
            let server = HelloServer(channel.as_ref().as_ref().peer_addr().unwrap());
            channel.respond_with(server.serve()).execute()
        })
        .buffer_unordered(10)
        .for_each(|_| async {})
        .await;

実行してみる

ビルド

gh repo clone google/tarpc
cd tarpc/example-service
cargo build --release

サーバーの実行

../target/release/server --port 12345

クライントの実行

$ ../target/release/client --name OTL --server_addr 127.0.0.1:12345
Hello, OTL! You are connected from 127.0.0.1:61610.

その他のサンプル

example-service以外にもtarpc/examplesにいろんなサンプルが用意されていますので一読の価値ありです。

終わりに

async-traitと同時に使えると面白いなー、と思って調査を始めたのですが、関数名、trait名がconflictしてしまうので不可能そうでした。
Rustだとserdeでシリアライズの問題がほぼ完全にとけているのでこういうフレームワークもマクロを利用して簡単に(?)作れそうだなぁ、と思いました。
使ってみた、というより試してみた、って感じの内容になってしまっていますが、examplesのほうは結構充実しているのでいろいろできそうです。pubsubとかありました。

以上、元Googlerのスマイルロボティクス株式会社代表取締役社長OTLこと小倉がお送りしました〜。

13
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
8