それでは server.cpp について説明していく。
Kirthika による概要はこうだ。
ここでは、もう一度 ACE_Reactor によるイベントハンドリングサービスを実装する。 これら全ては単一スレッドによって実行される。
ここで学ぶことは次のマルチスレッドサーバモデルへの足がかりである。
// $Id$
/*
今回は main() をとても単純になるようにした。
その方法の一つは、複雑な部分をワーカーオブジェクトに押し込めてしまうことだ。
この場合はメインソースにおいて acceptor のヘッダをインクルードするだけでよい。
我々は「本来の仕事」をプログラムすることに集中できる。
*/
#include "client_acceptor.h"
/*
以前と同様に、単純なシグナルハンドラとして終了フラグを設定するものを準備する。
もちろん、当然ながら今回よりもエレガントな処理方法も存在するのだが、それは今回の主要目的ではないので、より簡単な方法を採ることにする。
*/
static sig_atomic_t finished = 0;
extern "C" void handler (int)
{
finished = 1;
}
/*
サーバは周知の TCP/IP ポートにおいてクライアントを待ち受ける。
(少なくとも私のシステムでは)ACE の標準ポートは 10002 であり、今回の用途には問題ない。
当然ながら、より強固なアプリではコマンドラインからパラメータとして受け取ったり、設定ファイルを読んだりといった賢い処理をするべきであろう。
上のシグナルハンドラの項でも述べた通り、今回の主要目的はそれではないので、ここでも簡単な方法を採ることにする。
*/
static const u_short PORT = ACE_DEFAULT_SERVER_PORT;
/*
最後はここでメイン関数を実装する。
いくつかの C++ コンパイラは関数シグネチャがプロトタイプと異なっているとうるさく文句を言うかもしれない。
今回はそれらのパラメータを利用しないのだが、いちおう定義だけはしておくことにする。
*/
int
main (int argc, char *argv[])
{
ACE_UNUSED_ARG(argc);
ACE_UNUSED_ARG(argv);
/*
以前のサーバでは Reactor にグローバルポインタを利用していた。
そういう書き方はあまり好きではないので、今回からは main() 内部で定義する。
Client_Handler オブジェクトの定義で、この reactor をどうやって取得するかを見せよう。
*/
ACE_Reactor reactor;
/*
acceptor はクライアントからの接続の面倒をみる。
その他にも、クライアントに対してそれぞれ Client_Handler を準備する役目もある。
我々は単一の TCP/IP ポートしか待ち受けないため、一つの acceptor を用意すれば十分である。
必要によっては、複数の acceptor によって複数のポートを待ち受けることもできる。
(例えば inetd を自分で書くような場合に必要になるだろう)
*/
Client_Acceptor peer_acceptor;
/*
接続の端点を準備するために ACE_INET_Addr を作成する。
それから、そのアドレスを利用して acceptor をオープンする。
そうしないと、どのポートで待ち受けてよいか分からないのだ。
サーバは一般には「ウェルノウン(良く知られた)ポート」を利用する。
そうでない場合には、何らかの方法でクライアントにサーバのポート番号を伝える必要がある。
acceptor のオープンに失敗した場合の ACE_ERROR_RETURN の利用法を覚えておくこと。
このテクニックはチュートリアル中で繰り返し利用されることになる。
*/
if (peer_acceptor.open (ACE_INET_Addr (PORT),
&reactor) == -1)
ACE_ERROR_RETURN ((LM_ERROR,
"%p\n",
"open"),
-1);
/*
ここまで来た場合、acceptor のオープンは成功している。
もし失敗した場合には上で exit しているはずである。
open() の嬉しい副作用としては、渡した reactor に(acceptorが)登録済みとなることである。
*/
/*
シグナルハンドラを準備する。
また、シグナルハンドラは reactor へ登録することもできる。
登録した場合には、ハンドラは実際の仕事を行う責任を持つ。
しかし、今回我々が用意するのは 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"));
/*
このループは reactor の handle_events() 関数を繰り返し呼ぶ。
handle_events() は登録されたイベントを監視し、要求に応じて適切なコールバック関数を起動する。
コールバック駆動は ACE でのプログラミングの重要事項であり、その方法はしっかり理解すべきだ。
シグナルハンドラが何かの信号を受け取ると終了フラグがセットされ、ループを抜けて終了する。
非常に便利なことに、handle_events() はシグナルによって割り込まれ、while ループまで制御を戻す。
(もしイベントループ中にシグナルによって割り込まれたくない場合には ACE_Reactor のオープン時に restart フラグを設定するとよい)
*/
while (!finished)
reactor.handle_events ();
ACE_DEBUG ((LM_DEBUG,
"(%P|%t) shutting down server daemon\n"));
return 0;
}
#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 */