再度 server.cpp から始めることにしよう。 もし精密に読んだとすれば、このファイルはチュートリアル5の実装から コメント一つしか違わないことに気付いただろう。
// $Id$ /* 我々は main() を可能な限りシンプルにしようとした。 そのうちの一つとしてワーカーオブジェクトに複雑な部分を詰め込んだ。 このケースでは我々はメインのソースファイルに acceptor ヘッダーを インクルードするだけだ。 ここでも実際の仕事に集中すればよい。 */ #include "client_acceptor.h" /* 以前と同様に、終了フラグをセットするだけのシンプルなシグナルハンドラを作る。 もちろん、よりエレガントに終了する方法はあるが、それは今の問題ではない。 なので、単純な方法を使うことにする。 */ static sig_atomic_t finished = 0; extern "C" void handler (int) { finished = 1; } /* サーバはクライアントを特定の TCP/IP ポートで listen しながら待つ。 デフォルトの ACE ポートは(少なくとも私のシステムでは) 10002 番であり、 このままでも我々の目的には問題無い。 当然ながら、実際の頑丈なアプリケーションではコマンドラインパラメータや 設定ファイルやその他のうまい方法で設定できるようにするだろう。 これもやはりシグナルハンドラと同様に今回の目的ではないので簡単に済ます。 */ static const u_short PORT = ACE_DEFAULT_SERVER_PORT; /* 最後に main である。 いくつかの C++ コンパイラは関数のシグネチャがプロトタイプ宣言と 合致しないからといって、うるさく文句を言うかもしれない。 そのため、今回はパラメータを使う予定は無いのだが、とりあえず宣言しておく。 */ int main (int argc, char *argv[]) { ACE_UNUSED_ARG(argc); ACE_UNUSED_ARG(argv); /* 以前のサーバでは、リアクターを取得するのにグローバルなポインタを使っていた。 私はその方法が嫌いだったので、今回はそれを main() に移すことにした。 Client_Handler オブジェクトを見る時に、このリアクターへのポインタを どのように管理しているか分かるだろう。 */ ACE_Reactor reactor; /* アクセプターはクライアントからの接続の面倒を見ることになる。 その他にも各クライアントからの接続に対して Client_Handler を作成して 割り当てる作業も行う。 今回は TCP/IP ポートを一つだけしか listen していないので、 用意するアクセプターも一つでよい。 もし必要ならば、好きなだけアクセプターを用意し、好きなだけのポートを 同時に listen することもできる。 (That's what we would do if we wanted to rewrite inetd for instance.) */ Client_Acceptor peer_acceptor; /* 通信のエンドポイントを示す ACE_INET_Addr を作る。 そしてアクセプターオブジェクトにその Addr を渡して開く。 これはアクセプターにどのポートで接続を listen するかを教えるためだ。 サーバは一般的には"well known"(よく知られた)ポートを listen する。そうでなければ、クライアントがサーバのアドレスを知るために なんらかのメカニズムを用意しなければならなくなる。 ここでアクセプターのオープンに失敗した時の ACE_ERROR_RETURN の使い方に 注目してほしい。このテクニックはチュートリアル中で何度も出てくることになる。 */ if (peer_acceptor.open (ACE_INET_Addr (PORT), &reactor) == -1) ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "open"), -1); /* チュートリアル 5 では、ここでリアクターへの登録が(アクセプターの open によって) 行われていた。このステップを忘れないようにしておくこと。 */ /* ここでシグナルハンドラをインストールする。 明示的にシグナルハンドラをリアクターに登録することもできる。 シグナルハンドラは、その実際の仕事の実行にのみ責任を持つようにできる。 我々のフラグをセットするだけのシンプルなハンドラは ACE_Event_Handler から 継承されてはいないが、コールバック関数として設定される。 */ ACE_Sig_Action sa ((ACE_SignalHandler) handler, SIGINT); /* ACE_ERROR_RETURN と同様に ACE_DEBUG マクロもよく使われる。 プログラム中でデバッグ出力を統一された方法で行うにはちょうどよい。 */ ACE_DEBUG ((LM_DEBUG, "(%P|%t) starting up server daemon\n")); /* このループは永久的にリアクターの handle_events() を 呼び出し続ける。handle_events() は登録されたハンドラの状態を監視し、 必要になれば適切なコールバック関数を実行する。 コールバック駆動のプログラミングは ACE の重大事項であり、これを使うべきだ。 もしシグナルハンドラが何かをキャッチしたら finished フラグがセットされ、 実行は終了する。 便利なことに、 handle_events() はシグナルによる割り込みの場合は while ループへと戻る。(もしシグナルによってイベントループに割り込まれたく ない場合には、ACE_Reactor の open() メソッド中にある restart フラグについて調べてみよう) */ while (!finished) reactor.handle_events (); ACE_DEBUG ((LM_DEBUG, "(%P|%t) shutting down server daemon\n")); return 0; } #if !defined(ACE_HAS_GNU_REPO) #if defined (ACE_HAS_EXPLICIT_TEMPLATE_INSTANTIATION) template class ACE_Acceptor <Client_Handler, ACE_SOCK_ACCEPTOR>; template class ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>; #elif defined (ACE_HAS_TEMPLATE_INSTANTIATION_PRAGMA) #pragma instantiate ACE_Acceptor <Client_Handler, ACE_SOCK_ACCEPTOR> #pragma instantiate ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> #endif /* ACE_HAS_EXPLICIT_TEMPLATE_INSTANTIATION */ #endif /* ACE_HAS_GNU_REPO */
それでは続いて Client_Acceptor で何が起こっているのか見ることにしよう