ACE Tutorial 001
初心者のための ACE ツールキットガイド
ここからはメインのプログラムループに入ろう。 これは作っていった最終版のコードであるが、単純なものなのでこれから着手するにはちょうどいいだろう。
The main main のプログラムはいたって単純である。 実際の作業は ACE から派生したクラスが行う。
Kirthika Parameswaran はチュートリアル1の概要を次のように述べている。
これは単純なログサーバの例である。 リアクタは一つ以上のクライアントからのリクエストの処理に、クライアントごとのスレッドではなく、全体で単一のスレッドを利用している。 Reactor は「コールバック」と呼ばれる技法を用いてイベントに対応し、それらを(リアクタへ登録済みの)適切な Event_Handler へと分配する。 リアクタはこのように入って来るイベント群を処理するための無限イベントループを回し続ける。
Logging_Acceptor クラスはサーバの指定ポートを開き、接続要求が届くのを待つ。 この Acceptor は Event_Handler の一種であり、リアクタへ登録される。 この方法はとても単純であり、リアクタにとっては他の Event_Handler と何も変わりはしない。
接続要求が届くと、Acceptor はそれを受け入れて接続を確立する。 リアクタはそれをハンドラに渡し、処理を継続できるように(ハンドラを)自身へと登録する。 これは ACE_Event_Handler::ACCEPT_MASK を指定して行われる。
Logging_Client は、クライアントからのリクエストを handle_input() 関数によって処理するための別の Event_Handler である。 これは ACE_Event_Handler::READ_MASK を指定して Reactor へと登録される。
それぞれの Event_Handler は handle_close() メソッドか、明示的な remove_handler() 呼び出しによって Reactor から登録抹消される。
これでサーバアプリケーションはクライアントからのリクエストを待ち、処理を実行できるようになる。
ACCEPT_MASK は ACE_Event_Handler クラスで定義されている。 これは Reactor へ登録しようとしているハンドラが「接続をacceptする」ものであることを伝える。 これは ACE_Acceptor コンポーネントが使う指定である。
READ_MASK も ACE_Event_Handler クラスで定義される。 こちらは Reactor へ登録するハンドラが、確立された接続からの「データを読む」ものであると伝える。
// $Id$ /* acceptor が定義されているヘッダをインクルード */ #include "ace/Reactor.h" /* 単純化のためリアクタをグローバルに定義する。 以降のチュートリアルではもっと賢く適切な方法を取っている。 しかし、このチュートリアルでは接続の受諾とハンドリングを紹介するため リアクタの全能力を使ってはいない。 */ ACE_Reactor *g_reactor; /* Acceptor オブジェクトの定義を取り込むためヘッダをインクルードする。 Acceptor はサーバがクライアントからの接続依頼を accept するための 処理を抽象化する。 */ #include "acceptor.h" /* TCP/IP サーバは接続要求待ちのために一つのポート(のみ)を監視できる。 よく知られた(有名な)サービスは常に同じポートアドレスを利用する。 あまり知られていないサービスの場合は設定ファイルやコマンドラインから 受け付けるポート番号を指定される。 この例では単純に適当な値をハードコーディングしておく。 */ static const u_short PORT = ACE_DEFAULT_SERVER_PORT; int main (int, char *[]) { /* Reactor のインスタンスを作成する。 繰り返すが、本来はグローバルポインタを利用するのは良い方法ではない。 今回の例では単純性を重視する目的でこのような方法をとった。 後から、もっとちゃんとした方法を紹介する。 なお、ここで new が失敗した時に1を返す目的で利用している ACE_NEW_RETURN マクロの用法を覚えておくと良いだろう。 */ ACE_NEW_RETURN (g_reactor, ACE_Reactor, 1); /* Reactor と同様に ADDR オブジェクトの詳細も省くことにする。 これはネットワークアドレスの取り扱いを抽象化するための方法を提供する。 ここでは、サーバが接続を待つ TCP/IP ポート指定のためにアドレスオブジェクトを 作成しているという事実のみ覚えておけば良い。 */ ACE_INET_Addr addr (PORT); Logging_Acceptor *peer_acceptor; /* ここで Acceptor オブジェクトを作成する。 この時点ではまだ接続は確立されることはない。 それは下の行の役目である。 */ ACE_NEW_RETURN (peer_acceptor, Logging_Acceptor, 1); /* ここで Acceptor オブジェクトが開始される。 大抵の ACE オブジェクトは本来の使用の際に open() 関数が呼ばれる。 この open() 呼び出しにより acceptor がどのポートで接続待ちするか アドレスオブジェクトを渡して決定する。 また、同様に g_reactor へ登録処理が必要であることも教える。 */ if (peer_acceptor->open (addr, g_reactor) == -1 ) ACE_ERROR_RETURN ((LM_ERROR, "Opening Acceptor\n"), -1); ACE_DEBUG ((LM_DEBUG, "(%P|%t) starting up server logging daemon\n")); /* Reactor の handle_events メンバ関数は、登録済みの全ての オブジェクトを監視し、何かのイベントが起きた時には適切な メンバ関数を呼ぶという責任を持つ。 一つのイベントが処理されるたびに handle_events 関数からは処理が返る。 全てのイベントを処理するために、ここでは無限ループを利用する。 ここでは無限ループへ入ることにしたため、プログラム終了の際には Ctrl-C を押してやる必要がある。 */ for (;;) g_reactor->handle_events (); return 0; }
この通り、メインプログラムは実にシンプルである。