Cap'n Protoを勉強してみる(3)
サンプルコード:calculator
のサンプルコードcalculatorを見ていきます。calculator-client.c++
capnp::EzRpcClient client(argv[1]);
Calculator::Client calculator = client.getMain<Calculator>();
のっけから確信はもてないですが、サーバーアドレスを渡してクライアントクラスを作って、さらに今回扱うcalculatorに特化したクライアントクラスを作っています。恐らく、EzRpcClientが通信のためのクラスで、Calculator::Clientがデータのシリアライズをするためのクラスでしょうか。
ちょっと違和感がありましたが、C#もHttpClientで通信をカバーしてデータのシリアライズはJsonのライブラリを使っていたことを考えると似ているのかもしれません。
EzRpcClient
EzRpc~を使用して通信を行うコードのようです。
EzRpcClient
は"Cap'n Proto RPCクライアントをセットアップするための超簡単なインターフェース"とヘッダに書かれています。ただ、一部制限があるようです。
- パブリックなシングルトン機能の小さなセットのみをエクスポートしています。 これは、接続間で状態を保持する必要のない一時的なサービスでは問題ありませんが、長期的なリソースに関しては Cap'n Proto のパワーを隠してしまいます。
- EzRpcClient/EzRpcServer は自動的に
kj::EventLoop
をセットアップして、そのスレッドに対してカレントな状態にします。 スレッドごとに1つのkj::EventLoop
しか存在できないので、独自のイベントループをセットアップしたい場合は、これらのインターフェースを使用することはできません。 (ただし、1つのスレッドで複数のEzRpcClient / EzRpcServerオブジェクトを安全に作成することができます; これらはイベントループを1つ以上作らないようにします) - これらのクラスは、単純な2者間接続のみをサポートし、多者間VatNetworksはサポートしません。
- これらのクラスは、暗号化されていない生のソケット上の通信のみをサポートしています。 もし、抽暗号化をサポートするストリームを構築したい場合は、低レベルのインターフェースを使用する必要があります。
capnp::EzRpcClient client(argv[1]);
は以下のように第一引数にサーバーアドレスを取るとあります。
EzRpcClient(const struct sockaddr* serverAddress, uint addrSize,
ReaderOptions readerOpts = ReaderOptions());
アドレスは kj/async-io.h
にある kj::Network
によって解析されるとあるのでクラスのコンストラクタを見るとIPv4、IPv6、Unix domain socketが使えるようです。文字列のパースは以下の関数で行っているようです。
static Promise<Array<SocketAddress>> parse(
LowLevelAsyncIoProvider& lowLevel, StringPtr str, uint portHint, _::NetworkFilter& filter) {
// TODO(someday): Allow commas in `str`.
SocketAddress result;
if (str.startsWith("unix:")) {
StringPtr path = str.slice(strlen("unix:"));
KJ_REQUIRE(path.size() < sizeof(addr.unixDomain.sun_path),
"Unix domain socket address is too long.", str);
KJ_REQUIRE(path.size() == strlen(path.cStr()),
"Unix domain socket address contains NULL. Use"
" 'unix-abstract:' for the abstract namespace.");
result.addr.unixDomain.sun_family = AF_UNIX;
strcpy(result.addr.unixDomain.sun_path, path.cStr());
以下、長いので省略
Calculator::Client
Calculator::Client calculator = client.getMain<Calculator>();
ではCalculatorをテンプレート引数にしてCalculator::Clientを取り出しています。
getMainとは何なのか?
template <typename Type>
inline typename Type::Client EzRpcClient::getMain() {
return getMain().castAs<Type>();
}
getMain
の戻り値をcastしています。
Capability::Client EzRpcClient::getMain() {
KJ_IF_MAYBE(client, impl->clientContext) {
return client->get()->getMain();
} else {
return impl->setupPromise.addBranch().then([this]() {
return KJ_ASSERT_NONNULL(impl->clientContext)->getMain();
});
}
}
KJ_IF_MAYBE
はMaybeモナドみたいですね。Maybeモナドはざっくりいうとstd::optionalのようなもので、nullptrかT型の値を持っている何かです。KJ_IF_MAYBE(client, impl->clientContext)
で、client
にimpl->clientContext
を代入しつつnullptrが判定していますね。こういう書き方はかっこいいと思います。
clientContext
はkj::AsyncIoStream
でgetMain()
でCapability::Client
が取得できます。
う~ん。さっぱりわからん。
capnpのCapabilityをみると以下のコメントがありました。
// A capability without type-safe methods. Typed capability clients wrap `Client` and typed
// capability servers subclass `Server` to dispatch to the regular, typed methods.
タイプセーフのメソッドを持たないcapability。 型付きcapabilityのクライアントは Client
をラップし、型付きcapabilityのサーバは Server
をサブクラスにして、通常の型付きメソッドにディスパッチするようにします。
capabilityは機能や容量を意味するのでCalculatorという具体的な機能(capability)を持ったClientを作っているということと一旦理解しました。
ここでreturn getMain().castAs<Type>();
の後半を見ると
template <typename T>
inline typename T::Client Capability::Client::castAs() {
return typename T::Client(hook->addRef());
}
hookの参照カウントを増やしながらCalculator::Client
に渡しています。hookがPipelineHookになっています。
これでCalculator::Client
に通信のためのHookが渡されているということを理解しておきます。
Author And Source
この問題について(Cap'n Protoを勉強してみる(3)), 我々は、より多くの情報をここで見つけました https://zenn.dev/yae/articles/e39281bd083afa著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol