Client

client

まずクライアント実装を見る。

sample/client は以下のような動作を行う:
  • 複数の接続を行う
  • 全ての接続が完了したら、最初の接続に対してデータを送信する
  • 送信を無限に繰り返す

クラス宣言

class Client: public nine::TransceiverHandler
{
   public:
      Client();
      virtual ~Client();
      void init(int nClients);
      bool connectLater(const char* host, uint16_t port);
      void run();

   public:
      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);

   private:
      int m_nConnects;
      int m_n;
      std::vector< int > m_ids;
      nine::Transceiver* m_pTransceiver;
};

クラス定義

ストリームタイプの通信では、Transceiver と TransceiverHandler が必要になる。

ここではクラス図の StreamApplication のように、TransceiverHandler を継承したクラスが Transceiver を保有するようにする。

class Client: public nine::TransceiverHandler
{
   ...
   private:
      nine::Transceiver* m_pTransceiver;
};

Transceiver の生成と TransceiverHandler の割り当て

続いて Transceiver を生成し、TransceiverHandler を割り当てるコードを実装する。
Client::Client()
   : m_pTransceiver(NULL)
{ }

Client::~Client()
{
   if (m_pTransceiver) {
      nine::Transceiver::Destroy(m_pTransceiver);
   }
}

void Client::init(int n)
{
   m_n = n;
   m_pTransceiver = nine::Transceiver::Create(0, n);
   m_pTransceiver->setHandler(this);
   m_nConnects = 0;
}

init() で Transceiver を作成し、~Client() で破棄している。 生成の直後に setHandler によって、自身をハンドラとして登録している。

生成時の Create 関数の引数は、前から順に Acceptorの数と Communicatorの数である。

Acceptor の数とは、接続の受け入れ口の数である。クライアント専門ならばゼロ、サーバーでも通常は一つで良い。

Communicator の数とは、実際にデータを送受信する接続の数である。明示的に接続をした場合はもちろん、Acceptor が受け入れた接続も含める。 例えば、同時接続数 10000 をこなすサーバーでは 10000 を指定する必要がある。

このサンプルでは、接続を受け付けないので Acceptor はゼロ個である。 クライアント接続は複数張れるようにしているので、Communicator の数は引数n個要求している。

TransceiverHandler の実装

TransceiverHandler はいわゆるインターフェースクラスで、実装すべき関数を純粋仮想関数で指定している。 Client で定義されている多数の on 関数がそれである。

   public:
      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);

サンプル実装では onCommunicatorConnect で接続数をカウントしているだけである。

void Client::onCommunicatorConnect(int id, bool succeed)
{
   ++m_nConnects;
   printf("connect: %s\n", (succeed)? "succeed": "failed");
   nine::Communicator* p = m_pTransceiver->getCommunicator(id);
   if (p) {
//      p->dump(true);
   }
}

実アプリケーションでは、ここで接続毎の初期化処理を行うことになるだろう。

Transceiver::turn の呼び出し

繰り返し turn() を呼ぶ。 サンプルでは単純に wait 付きの無限ループを使っている。

void Client::run()
{
   while (true) {
      m_pTransceiver->turn();
      ...
      nine::msleep(5);
   }
}

接続の開始

接続は単に Transceiver::connectLater() を呼べば良い。 後の Transceiver::turn() で実際に接続を行う。

Client::connectLater() は多数の接続全ての connectLater を呼んでいる。

bool Client::connectLater(const char* host, uint16_t port)
{
   m_ids.resize(m_n);
   for (int i=0; i<m_n; ++i) {
      m_ids[i] = m_pTransceiver->connectLater(host, port);
      if (m_ids[i] < 0) {
         return false;
      }
   }
   return true;
}

データの送信

Communicator::getSendBuffer() で Buffer を取得し、Buffer の書き込み関数を使って書き込みを行う。

Communicator は connectLater() で得られた id を引数に、Transceiver::getComunicator() で取得できる。 まずは Communicator 取得部分を見てみる。

void Client::run()
{
   while (true) {
      m_pTransceiver->turn();
      if (m_nConnects == m_n) {
         nine::Communicator* p = m_pTransceiver->getCommunicator(m_ids[0]);
         ...

ここの m_ids[0] は、上の connectLater() でセットしてある。

続いて Buffer に書きこみを行う。
         if (p != NULL) {
            nine::Buffer* pBuf = p->getSendBuffer();
            pBuf->writeArray("hello", 5);
         }
Buffer操作は Buffer操作ドキュメントを参照のこと。 ここではサンプルコードの部分だけを簡単に説明する。

writeArray("hello", 5) で、"hello" 文字列を書き込んでいる。 writeArray() は単純にバイト列を書き込む関数なので、文字列を書き込む際はデータ表現に必要である。 ここでは生のC言語文字列なので、1文字1バイトのバイト列として扱って問題ない。 書き込み文字数は終端NULLを含まずに 5文字となっていることに注意しておく。

データの受信

Client サンプルではデータ受信は実装していないが、もちろんクライアントでもデータの受信は可能である。 実装方法は Server サンプルを参照されたい。

注意すべき点として、受信したデータは読み込まれるまでメモリに保持されるようになっている。 メモリを無駄にしないよう、データを受信するならば必ず読み込み処理を実装するべきである。