ACE Tutorial 005
On the road to a multithreaded server


最後に、いくらかのコードを書く必要のある client_handler.cpp を見ていく。 このファイルには他と比べて多くのコードが書かれている。


// $Id$


/*
  client_handler.h で言及したように、我々は Client_Acceptor ポインタをいじる必要がある。
  そのためには Client_Acceptor オブジェクトの宣言も必要である。

  我々は client_handler.h のインクルードが、client_acceptor.h でも行われているため冗長だと知っている。
  しかしながら、二重インクルードによる問題は回避されており、何のファイルを利用しているか明示的に示すことは重要だ。

  一方、ACE ヘッダーファイルについては、ここで直接インクルードすることはしない。
*/

#include "client_acceptor.h"
#include "client_handler.h"


/*
  コンストラクタは何もしない。
  これは一般的には良い考えである。
  例外をスローできるようになるまで、コンストラクタが失敗したことを報告する良い方法は無かった。
*/

Client_Handler::Client_Handler (void)
{
}


/*
  デストラクタも同様に何もしない。
  これはデザインにもよる。
  我々は実際の作業を destroy() に任せるとしたことを思い出してほしい。
  そうした場合にはデストラクタが呼び出された時にすべきことは何もない。
*/

Client_Handler::~Client_Handler (void)
{
  // 削除の際に peer が閉じられることを確実にする。
  // これは既に閉じられていても発生するが、明示的に行って問題はない。
  // doesn't hurt to be explicit.
  this->peer ().close ();
}


/*
  destroy() メソッドについては述べるべきことがある。
  この方法を採るのは、デストラクタで実際の作業を行うのは悪い方法であるからだ。
  また、メソッドの返り値は void であるが、問題があったことを呼び出し元へ知らせるためには int を返した方がよい。
  しかしながら、 void を返すとしても問題発生時に例外をスローすることができる。
*/

void
Client_Handler::destroy (void)
{
  
  /*
    リアクタへ、このハンドラに関係する全てを忘れるように伝える。
    引き渡す引数は open() メソッドで利用した時と同様のものである。
    また、それに加えて handle_close() が呼ばれないように DONT_CALL フラグを設定する。
    ここで handle_close() が呼ばれてしまうとマズい再帰呼び出しが発生する。
  */

  this->reactor ()->remove_handler (this,
                                    ACE_Event_Handler:: READ_MASK | ACE_Event_Handler::DONT_CALL);

  
  /*
    ここで自分自身を削除する。
    これによってオブジェクトが綺麗に終了し、メモリリークの発生を抑えることができる。
  */

  delete this;
}


  /*
    ここで自分自身を削除する。
    これによってオブジェクトが綺麗に終了し、メモリリークの発生を抑えることができる。
  */

int
Client_Handler::open (void *_acceptor)
{
  
/*
  以前に述べたように、open() メソッドは新規の接続を受け付けた時に Client_Acceptor から呼び出される。
  その際、Client_Acceptor へのポインタが void* にキャストされて渡される。
  これはグローバルデータの利用を抑止するために役立つ。
*/

  /*
    void* から Client_Acceptor* へキャストする。
    本来は ACE_*_cast マクロを利用すべきである。
    コンパイラからの警告無しに void* とその他のポインタをキャストする場合には自分が何をしようとしているのか理解して行う必要がある。
    そして、こういう時こそが新しいスタイルのキャストの使い時なのだ。
   */
  Client_Acceptor *acceptor = (Client_Acceptor *) _acceptor;

  
  /*
    リアクタへの参照をここで設定する。
  */

  this->reactor (acceptor->reactor ());

  
/*
  以前に述べたように、open() メソッドは新規の接続を受け付けた時に Client_Acceptor から呼び出される。
  その際、Client_Acceptor へのポインタが void* にキャストされて渡される。
  これはグローバルデータの利用を抑止するために役立つ。
*/
  /*
    接続中のクライアントのアドレスを得るために変数を宣言する。
    この後のデバッグメッセージ表示に利用する。
  */

  ACE_INET_Addr addr;

  
  /*
    ACE_Svc_Handler 基底クラスは、内部の ACE_SOCK_Stream にアクセスするための peer() 関数を備える。
    そのオブジェクトに対して get_remote_addr() を呼び出すと、相手方の ACE_INET_Addr を得られる。
    ほとんどの ACE メソッドと同様に、エラーの場合には -1 が返ってくる。
    一度 ACE_INET_Addr オブジェクトを入手すれば、ホスト名、IP アドレス、ポート番号などを調査できる。
    一つ注意すべき点は、ACE_INET_Addr への get_host_name() メソッドは、名前解決できなかった場合に空文字列を返す場合があるということだ。
    一方、get_host_addr() は常にドット区切りの10進文字列をIPアドレスとして返す。
   */

  if (this->peer ().get_remote_addr (addr) == -1)
    return -1;

  
  /*
    クライアントのアドレスを入手できた場合には、しっかりと有効なクライアントへ接続されていると考えられる。
    中には接続してすぐにクライアントが無効となるような場合もあるかもしれない。
    どのような場合であっても、上のテストを行うことで接続が有効であることを確認することができる。

    続いて、何かメッセージが届いた時に通知してくれるように、リアクタへ自身を登録する。
    思い出してほしいのだが、我々はオブジェクトが作成された最初の時点で acceptor からリアクタを設定している。
    単一スレッドによる実装の場合は、これで問題無く動作する。

  */

  if (this->reactor ()->register_handler (this,
                                          ACE_Event_Handler::READ_MASK) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       "(%P|%t) can't register with reactor\n"),
                      -1);

  
  /*
    ここで接続先のホスト名を表示するために ACE_INET_Addr オブジェクトを利用する。
    もう一度言うが、DNS の設定ミスや他の原因によってアドレスからホスト名が解決できない場合は空の文字列が返る事がある。
  */

  ACE_DEBUG ((LM_DEBUG,
              "(%P|%t) connected with %s\n",
              addr.get_host_name ()));

  /* 成功時には 0(ゼロ) を返す。 */
  return 0;
}


/*
  我々は open() メソッドの中でリアクタへ自身を登録し、読むべきデータが届いた場合に通知してくれるように設定した。
  そのようなイベントが発生した場合、リアクタは(登録したこのオブジェクトの) handle_input() メソッドを起動する。
  今回は _handle パラメータは利用していないが、これはリアクタによって通知されたイベントの駆動元がどこかを特定するために有用になる。
*/

int
Client_Handler::handle_input (ACE_HANDLE handle)
{
  
  /*
    いくつかのコンパイラはパラメータを利用していないと警告を出す可能性がある。
    このマクロはそれを抑止し、無駄なメッセージを出させないようにする。
  */

  ACE_UNUSED_ARG (handle);

  
  /*
    ここでデータを受け取るためのバッファを準備する。
    これは単純なテストアプリケーションなので、適当な小さい値をサイズとして利用する。
  */

  char buf[BUFSIZ];

  
  /*
    データエリア(用意したバッファ)のポインタを引数にして process() メソッドを呼ぶ。
    該当のメソッドにはデータとのやりとりを任せる。
    データの読み込みと処理を任せ、返り値をそのままこのメソッドから返すようにする。
    しかしながら、アプリケーションの仕様が少量のバイトデータを読んでから処理を決定する場合などには、ここでそれを決定するのが良いかもしれない。
  */

  return this->process (buf, sizeof (buf));
}


/*
  handle_input() が -1 を返したり、リアクタが何か問題を検知した場合には handle_close() が呼ばれる。
  リアクタフレームワークが(-1 が返った場合の)除去処理をしてくれるため、ここでは destroy() を呼ぶ必要が無い。
  その代わりに自身を delete することが必要である。
*/

int
Client_Handler::handle_close (ACE_HANDLE handle,
                              ACE_Reactor_Mask mask)
{
  ACE_UNUSED_ARG (handle);
  ACE_UNUSED_ARG (mask);

  delete this;
  return 0;
}


/*
  最後にアプリケーションロジックレベルの記述を行う。
  他の部分に細々とした処理を任せることができたので、ここではアプリケーションが本来行うべきことだけ実施すればよい。
  ここではクライアントのデータを読み込み、それについて処理を行う。
  実際のアプリケーションでは main() でより多くのコマンドラインオプションの処理を行った後で、残りの作業をここで実施することになるだろう。
*/

int
Client_Handler::process (char *rdbuf,
                         int rdbuf_len)
{
    ssize_t bytes_read = -1;

  
  /*
    用意されたバッファを使って、クライアントからのデータを読み込む。
    ここで(recv() が -1を返すなど)エラーした場合は、おそらく接続が切れたと思ってよい。
    同様に読み込んだバイト数が 0 だった場合も何らかの問題が起きたのだろう。
    リアクタはゼロバイトの読み込みを除いて、読み込み以外の処理をここへ振り分けることはない。

    また、データを受信した場合にはデバッグメッセージとして誰でも読めるように出力する。
  */

    switch ( (bytes_read = this->peer ().recv (rdbuf, rdbuf_len)) )
    {
    case -1: // 状況を表示して終了
      ACE_ERROR_RETURN ((LM_ERROR,
                         "(%P|%t) %p bad read\n",
                         "client"),
                        -1);
    case 0: // 状況を表示して終了
      ACE_ERROR_RETURN ((LM_ERROR,
                         "(%P|%t) closing daemon (fd = %d)\n",
                         this->get_handle ()),
                        -1);
    default: // データを表示
        // 表示の前に NULL 終端処理を行う。
      rdbuf[bytes_read] = 0;
      ACE_DEBUG ((LM_DEBUG,
                  "(%P|%t) from client: %s",
                  rdbuf));
    }

   
   /*
     recv() メソッドの他に recv_n() という同様のメソッドがあることにも触れておく。
     recv_n() は指定したバイト数が達成されるまで読み込みを続ける。
     これは受信すべきサイズが既知の場合には非常に有用である。
     このアプリケーションの場合には不幸にもクライアントが送信したバイト数を知る術が無い。
     recv() は「指定バイト数以下で届いた分だけ」のバイトを受信する。
     こちらはクライアントがどれほどの情報を送るのか不明な場合に役立つ。
   */


  return 0;
}


[インデックスへ] [次へ進む]