メッセージサーバー

サンプルメッセージサーバー(message_raw/msgserver)は、クライアントから来るメッセージを受信し、カウントするだけのものである。

サーバークラス

class Server: public nine::MessageServer
{
   public:
      virtual bool initialize(nine::MessageServerConfig*);
      void run();

      virtual bool handleMessage(nine::Message* pmsg);
      void handleTest(TestMessage*);

      virtual void onAcceptorListen(int id, bool succeed);
      virtual void onAcceptorAccept(int id, int cid);
      virtual void onAcceptorClose(int id);
      virtual void onAcceptorError(int id);

      inline virtual void onCommunicatorConnect(int id, bool succeed);
      inline virtual void onCommunicatorAccept(int id, int cid);
      inline virtual void onCommunicatorClose(int id);
      inline virtual void onCommunicatorError(int id);
      inline virtual void onCommunicatorReceiveTimeout(int id);
      inline virtual void onCommunicatorSendTimeout(int id);

メッセージサーバーの実装は、MessageServer クラスを継承して実装するのが基本である。

この MessageServer は TransceiverHandler も継承している。

初期化

MessageServer は、初期化のために MessageServer::initialize(const MessageServerConfig*) 関数を提供している。

  class MessageServer {
    ...
    virtual bool initialize(const MessageServerConfig* pCfg);
  }

これを直接呼び出しても良いが、実際のアプリケーションでは独自の初期化処理も行いたいであろう。 具象クラスでも初期化関数を提供し、内部で MessageServer の initialize() を呼ぶようにするとよい。

bool Server::initialize(nine::MessageServerConfig* p)
{
   if (! super::initialize(p)) { return false; }

   m_listen = false;
   return true;
}

サンプルの initialize() では、まずベースクラスの initialize() を呼んでからユーザークラス用の初期化処理を行っている。

MessageServer#initialize() では、自動的に Transceiver に自身を TransceiverHandler として登録する。このため、TransceiverHandler 登録処理を記述する必要はない。

MessageServer::initialize() へ渡す引数の型は MessageServerConfig として定義されている。

struct MessageServerConfig
{
      MessageServerConfig();
      MessageServerConfig(const MessageServerConfig&);
      MessageServerConfig& operator=(const MessageServerConfig&);

      int nAcceptors; //!< Acceptor の数。デフォルトは 0.
      int nCommunicators; //!< Communicator の数。デフォルトは 0.
//      int nHttpCommunicators; //!< HttpCommunicator の数。デフォルトは 0.
      int nConcurrency;  //!< turn() の並列数。0の場合は並列化しない。デフォルトは 0.
      int sockSendBufferSize; //!< ソケットの送信バッファサイズ。負だとOS設定値を使う。デフォルトは -1.
      int sockRecvBufferSize; //!< ソケットの受信バッファサイズ。負だとOS設定値を使う。デフォルトは -1.
      int nineSendBufferSize; //!< 送信バッファのサイズ。デフォルトは 10000.
      int nineRecvBufferSize; //!< 受信バッファのサイズ。デフォルトは 10000.
      int turnTrafficKB; //!< 一回の turn() で送受信する最大データサイズ。
      bool enableWinsockAio; //!< nine1.2.1 より。1.2以降導入のWindows非同期I/O を使うなら真。デフォルトは偽。
};

TransceiverConfig と同名のメンバは、TransceiverConfig と同じ意味である。

TransceiverConfig に無いメンバは次のものである:
int turnTrafficKB
一回の turn() で送受信する最大データサイズ。
長大なバッファにいっぱいのデータを受信した場合、一回の turn() で全てを処理しようとすると時間がかかってしまう。これを避けるため、一回の turn() で処理するバイト数を指定できる。

listen

サーバー側は接続を待ち受ける必要がある。

MessageServer は内部に持つ Transceiver の listenLater を呼ぶだけの関数を提供しているので、これを使う。

int main(int argc, char** argv)
{
   ...
   server.listenLater(opt.host, opt.port);
   ...
}

メッセージハンドラ

メッセージを受信すると、ハンドラと呼ばれる関数がコールされる。 ユーザーはハンドラ関数を実装することで、メッセージの処理を実装することができる。

ハンドラ関数には、個別のメッセージを処理するハンドラ関数と、全てのメッセージを受け取るディスパッチ関数がある。

ディスパッチ関数で全てのメッセージを受け取り、そのメッセージIDを識別して個別のハンドラ関数へ渡すという処理になる。

ディスパッチャは handleMessage という仮想関数である。 ユーザーはこれをオーバーライドして実装する。
bool Server::handleMessage(nine::Message* pMsg)
handleMessage の引数には、受信したメッセージオブジェクトが与えられる。 型は抽象ベースクラスの nine::Message 型であり、個別具体的な型にはなっていない。 メッセージタイプID を見て具象クラスへ static_cast し、対応する処理関数へ渡す必要がある。
bool Server::handleMessage(nine::Message* pMsg)
{
   if (pMsg->getMessageId() == TestMessage::_MESSAGE_ID) {
      return handleTest( static_cast< TestMessage* >(pMsg) );
   } else {
      return super::handleMessage(pMsg);
   }
}
メッセージが増えてくると、ディスパッチの記述が冗長になる。少しでも簡潔にするために、ディスパッチ用のテンプレート関数 tryDispatch が定義されている。
      template <typename T, typename M> bool tryDispatch(void(T::*pf)(M*), Message* pMsg)
tryDispatch を用いれば、次のような記述ですむようになる:
bool Server::handleMessage(nine::Message* pMsg)
{
   if (tryDispatch(&Server::handleTest, pMsg)) {
      return true;
   } else {
      return super::handleMessage(pMsg);
   }
}
tryDispatch を使う場合、個別ハンドラ関数のシグネチャは固定的になる。 具体的には、引数は具象メッセージ一つで、返り値は void になる。
  void handleTest(TestMessage*);

turn

メッセージタイプの nineアプリケーションは、MessageServer::turn() を繰り返し呼ぶ必要がある。 サンプルでは、turn() を繰り返す呼ぶための関数 run() を定義している。
void Server::run()
{
   while (true) {
      turn();
      nine::msleep(1);
   }
}

MessageServer::turn() は内部で Transceiver::turn() を呼んでいるので、Transceiver::turn() を明示的に呼ぶ必要はない。