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 */


ここで重要なのは、このオブジェクトの開発時に書いた(アプリケーション固有の)コード量が少ないことである。 実際のところ、アプリケーション固有のコードは Logging_Handler を作成して accept() に渡したくらいだ。 このコードを C++ のテンプレートで書けないかと思う読者もいるだろう。 事実上、ACE ツールキットではこれを次のように提供している。 今回の例ならば次のようになる。 これで上に示したようなコードと同様のものが生成される。 大きな違いといえば、テンプレートで生成される handle_input 関数は reactor へ ハンドルを登録しない点くらいだ。 長い目で見れば、これは登録処理を Logging_Handleropen() へ移し、 完全に汎用的な acceptor とするためには良い。

さて、これで我々は接続要求を受け入れる方法を理解した。 続いては確立した接続を処理する方法について学んでいこう。 また、クールなテンプレート利用法についても学んだが、しばらくはこの「手書き」の acceptor を使うことにする。 忘れてはいけないのは、これらはコネクションハンドラの open() 関数が違うくらいだと言うことだ。


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