ccfの計画
今実装中のccfの高水準なAPIについてメモ。(前に書いたccfの件はwavyのちょっとしたラッパ程度でしかない)
connection
まず基本のconnection
template <typename IMPL> class rpc_connection : public connection<IMPL> { // ... void process_message(msgobj msg, auto_zone& z); // process_messageから呼ばれる void process_request(method_t method, msgobj param, msgid_t msgid, auto_zone& z); // process_requestから呼ばれる void dispatch_request(method_t method, msgobj param, responder& response, auto_zone& z); // process_messageから呼ばれる void process_response(msgobj result, msgobj error, msgid_t msgid, auto_zone& z); };
(このrpc_connectionだけでもかなり便利で、他のところで使い回せる)
その上に「コネクション」と「セッション」を分離する層をかぶせる。セッションはこちらから送るRPC要求のメッセージIDにコールバック関数を紐づけておいて、応答を受け取ったときに紐づけられたコールバック関数を呼び出す…などなどするクラス。
template <typename IMPL> class managed_connection : public rpc_connection<IMPL> { // 引数にセッションマネージャを取る managed_connection(int fd, basic_session_manager* manager, shared_session session, address locator); // rpc_connection<IMPL>::process_requestをフック void process_request(method_t method, msgobj param, msgid_t msgid, auto_zone& z); // rpc_connection<IMPL>::process_responseをフック void process_response(msgobj result, msgobj error, msgid_t msgid, auto_zone& z); // ...
コンストラクタでsession_managerとsessionを受け取って、process_requestをsession_managerに中継し、process_responseをsessionに中継する。
コネクションとセッションを分離する目的は以下:
- コネクションをRPCセッションの下に隠蔽する
- 明示的に「まず接続して、接続し終わったらコールバック関数を呼んでもらって…」とやらなくてもメッセージが送れる
- ↑メッセージを送ろうとした段階でセッションを作って、後で(非同期に)そのセッションにコネクションを登録する
- 相手先ホスト=セッション(!=コネクション)になる
- 複数のコネクションで負荷分散したり(1つのホストが複数のNICを持っている場合)
- bondingでactive-activeは色々アレなのでアプリケーションレベルで
- ネットワークが瞬断したとき、再接続してそのまま正常に続行できる
- 要求->応答のコンテキストも途切れない
sessionは↓こんな感じで:
class sessoin { // RPC呼び出し template <typename Message> void call(Message& parameter, shared_zone life, callback_t callback, unsigned short timeout = 10); // コネクションを追加・削除 void add_connection(int fd, const address& locator); // 削除はmanaged_connection::~managed_connectionから自動で呼ばれる void remove_connection(int fd); // managed_connection::process_responseから呼ばれる void process_response(msgobj result, msgobj error, msgid_t msgid, auto_zone& z); };
callでRPC呼び出しする。templateのMessageはMessagePackでシリアライズ可能な任意のクラス…なのだが、static const uint32_t Message::idがRPCのメソッドIDになっているなどの制約がある。
で、そのMessageクラスを生成するRPCスタブジェネレータ*2がある(作りかけ)。書き方はこんな感じで:
@message Get # これ自体がMessagePackでシリアライズ可能になる std::string key; # MessagePackでシリアライズ可能な任意のクラス uint32_t flags = 0; # デフォルト引数に対応 @end = 1 # これが static const uint32_t Get::id になる
一方session_managerは↓こんな感じ:
template <typename Identifier, typename IMPL> class session_manager : public basic_session_manager { // identifierに対応するsessionを取り出すか、無ければ作る shared_session get_session(const Identifier& id); // managed_connectionから呼ばれる virtual void dispatch(shared_session from, method_t method, msgobj param, session_responder response, auto_zone& z) = 0; // get_sessionで新しいsessionが作られたら呼ばれる // session_created(const identifer_t& addr, shared_session s); };
session_managerを継承してdispatchを実装してやればRPCサーバーが完成する。引数に session_responder というのが渡されていて、.result(true); などとやるとRPC応答を返送してくれる。
Identifierにはアプリケーションによって独自の「ノード識別子」を定義できる。1つのノード識別子に対して1つのセッションが対応する。アドレスが違っても*3識別子が==なら、複数のコネクションを1つのセッションにまとめられる。
Identiferから相手のIP+ポートが引けるなら、session_createdの中で非同期にccf::core::connectしたりできる…が、それは派生クラスの実装に任される。ここもCuriously Recurring Template Pattern。
しかしフツーに相手に接続するだけでいいなら、Identifierには単なるIP+ポートを使うだろうし、勝手にconnect(2)もしてほしい。というわけで典型的な用途向けにsession_managerを継承したクラスがいくつかある:
class server : public session_manager<address, server>; class client : public session_manager<address, client>; class custer : public session_manager<未定, cluster>;
ここから先は未実装。