ACE Tutorial 001
初心者のための ACE ツールキットガイド
それでは acceptor オブジェクトについて見ていこう。
Kirthika は次のようなアナロジー(比喩)を使っている。
リアクタ は 受付係、
イベントハンドラ群 は それぞれ異なった要求を満たすための部門、
サーバポート は 入口のドア、
アクセプタ は 門番 となる。
つまり、何らかの要求がある人(つまりクライアント)が門番(接続要求を待つアクセプタ)の 管理しているドア(ポート)を通って受付(リアクタ)へ行き、要求を満たすために 適切な部門(イベントハンドラ)へと案内されるということである。
// $Id$ #ifndef _CLIENT_ACCEPTOR_H #define _CLIENT_ACCEPTOR_H /* SOCK_Acceptor はソケット接続の取り扱いを知っている。 これを Logging_Acceptor の心臓部として使うことにする。 */ #include "ace/SOCK_Acceptor.h" #if !defined (ACE_LACKS_PRAGMA_ONCE) # pragma once #endif /* ACE_LACKS_PRAGMA_ONCE */ /* Event_Handler が ACE_Reactor へ登録すべきものである。 イベントが起きるとリアクタは Event_Handler を呼び出す。 */ #include "ace/Event_Handler.h" /* クライアントが接続してきたら、それを処理するために Logging_Handler を利用する。 ここでは、その宣言をインクルードしておく。 */ #include "logger.h" /* この Logging_Acceptor は ACE_Event_Handler から派生させる。 これは reactor に acceptor を他のイベントハンドラと同様に扱わせるためだ。 */ class Logging_Acceptor : public ACE_Event_Handler { public: /* この単純な例の場合、コンストラクタやデストラクタは必要ない。 実際のアプリケーションでは必要に応じて実装すること。 */ /* これが main() 内部から呼ばれる open() 関数の実体である。 ここでは次の2点が重要となる。 (1)acceptor をオープンし、クライアントの接続要求を受け付ける (2)接続要求が届いた時のために reactor に(acceptor)自身を登録する */ int open (const ACE_INET_Addr &addr, ACE_Reactor *reactor) { /* acceptor に対して open() を呼び出す。 この時には main() から渡されたポートアドレスを受け渡す。 二番目のパラメータは、acceptor にポートアドレスの再利用が可能だと 教えるためのものだ。 これはタイムアウト以外で終了してしまった場合の問題を回避するために必要である。 */ if (this->peer_acceptor_.open (addr, 1) == -1) return -1; /* 利用中のリアクタを覚えておく。 これはクライアント接続を処理するハンドラの登録のため、後で必要になる。 */ reactor_ = reactor; /* ここで(main() から)渡されたリアクタへと(acceptorを)登録する準備ができた。 今回はリアクタのポインタはグローバルなので、本来は引数で渡す必要は無いのだが 実際の利用の際を考慮してこのようにしてある。 この登録の際に this ポインタを利用できるように、このクラスを ACE_Event_Handler から派生させたことに注意してほしい。 また、クライアントからの接続要求を受け付けるため、リアクタへの 登録時に ACCEPT_MASK を指定している。 */ return reactor->register_handler (this, ACE_Event_Handler::ACCEPT_MASK); } private: /* マルチOS 用の抽象化として ACE は接続のエンドポイントに ハンドルというコンセプトを利用している。 Unix ではこれらは伝統的にファイルディスクリプタ(か int)である。 他のOSにおいては、他の形式となりうる。 リアクタは内部利用のためにハンドルを必要とする。 このハンドルは acceptor のものであるため、次のように提供する。 */ ACE_HANDLE get_handle (void) const { return this->peer_acceptor_.get_handle (); } /* 接続要求が届いた場合、reactor は handle_input() をコールバックする。 これは接続を確立するための処理となる。 */ virtual int handle_input (ACE_HANDLE handle) { /* リアクタから提供されるハンドルは、先の呼び出しで登録しておいたものだ。 より複雑な状況のため、複数の接続を一つのハンドラに関して登録できる。 このパラメータはそのような場合に利用するためのものだ。 そのため、今回の例では使う必要が無いので、単純に無視するよう ACE_UNUSED_ARG() マクロを利用する。 */ ACE_UNUSED_ARG (handle); Logging_Handler *svc_handler; /* 接続後のリクエストに対する返事のため、Logging_Handler を作成する。 この新しいオブジェクトはクライアントが切断するまでの面倒をみる。 ここで ACE_NEW_RETURN が、new オペレータの失敗時に -1 を返すため どのように使われているか覚えておくと良い。 */ ACE_NEW_RETURN (svc_handler, Logging_Handler, -1); /* 接続を確立するため、コネクションハンドラ(今回は Logging_Handler)の インスタンスを渡して acceptor の accept() メソッドを呼び出す。 これによって接続の取り扱い責任が acceptor からコネクションハンドラへ移行する。 */ if (this->peer_acceptor_.accept (*svc_handler) == -1) ACE_ERROR_RETURN ((LM_ERROR, "%p", "accept failed"), -1); /* 繰り返すが、ほとんどのオブジェクトは利用前に open() される必要がある。 今回はコネクションハンドラに、登録対象となる reactor のポインタを 渡してやる。 もし open() に失敗した場合は強制的に close() を行う。 */ if (svc_handler->open (reactor_) == -1) svc_handler->close (); return 0; } protected: /* acceptor オブジェクトのインスタンスである */ ACE_SOCK_Acceptor peer_acceptor_; /* reactor を覚えておくためのポインタ変数 */ ACE_Reactor *reactor_; }; #endif /* _CLIENT_ACCEPTOR_H */
さて、これで我々は接続要求を受け入れる方法を理解した。 続いては確立した接続を処理する方法について学んでいこう。 また、クールなテンプレート利用法についても学んだが、しばらくはこの「手書き」の acceptor を使うことにする。 忘れてはいけないのは、これらはコネクションハンドラの open() 関数が違うくらいだと言うことだ。