WebSocket サーバーチュートリアル

サンプルコード

ライブラリ添付の sample/http_raw/websocket_echo を参照しながら、WebSocket サーバーの具体的な実装手順を説明する。

サンプルプログラムは、クライアントから送られたデータをそのまま返答するだけのサーバーである。 プログラムの概略は次のようになっている:

Serverクラス
nine::HttpServer を継承。
Handler オブジェクトをフレームワークに渡す。
Handlerクラス。
nine::WebSocketHandler を継承。
エコーサーバーの動作を実装。

Server の初期化

Server::init で Serverオブジェクトの初期化を行う。Handler オブジェクトは外部から貰う。

bool Server::init(Handler* h)
{
   {
      typedef nine::HttpServer super;
      nine::HttpServerConfig cfg;
      cfg.nAcceptors = 1;
      cfg.nCommunicators = 20000;
      cfg.keepAliveTimeoutSec = 0;
      if (! super::initialize(&cfg)) { return false; }
   }

まずは、HttpServer の初期化を行うため、super::initialize() を呼ぶ。 HttpServerConfig の内容は固定的に設定している。

続いて、引数でもらった Handler オブジェクトのポインタをメンバ変数に記憶している:
   m_pHandler = h;
   return true;
}

WebSocketHandler の指定

HttpHandler と同様の方法で、WebSocket のデータを扱う WebSocketHandler を指定する。

前節の init で記憶しておいた、m_pHandler メンバをそのまま返している。

nine::WebSocketHandler* Server::authorizeWebSocket(int cid, const nine::HttpRequest* pReq)
{
   return m_pHandler;
}

受信ハンドラ

WebSocket フレームの全体を受信すると WebSocketHandler の onWebSocketReceive() 関数が呼ばれる:
      virtual void onWebSocketReceive(WebSocketChannel*, const WebSocketHeader*, Buffer* pBuf) = 0;
WebSocketChannel
このフレームを受信した接続オブジェクト
WebSocketHeader
WebSocketフレームのメタ情報
Buffer
WebSocketフレームのペイロード全体
サンプルでは、受信したデータをそのまま送信する。実装は次のようになっている:
void Handler::onWebSocketReceive(nine::WebSocketChannel* pChannel, const nine::WebSocketHeader*, nine::Buffer* pIn)
{
   size_t size = pIn->getReadableSize();
   nine::Buffer* pOut = pChannel->beginFrame00(size);

   nine::Buffer::IoVec iov;
   while (0 < pIn->getReadBuffers(&iov, 1)) {
      pOut->writeArray(iov.ptr, iov.size);
      pIn->skipRead(iov.size);
   }
   pChannel->endFrame();
   size_t size = pIn->getReadableSize();
関数に与えられた Buffer オブジェクト(pIn) は、受信したフレーム全体が入っている。 よって、getReadableSize() でフレームペイロードのサイズを得ることができる。
   nine::Buffer* pOut = pChannel->beginFrame00(size);
WebSocket の送信は、beginFrame00() で開始する。 パラメータにはフレームペイロードのサイズを渡す。 戻り値として、指定したペイロードサイズを書き込めるだけの Buffer オブジェクトを受け取る。 後はフレームの内容をこの Buffer へ書けばよい。

なお、Frame00 に書ける内容は 0x00 以外の UTF-8 文字列である。 nine は内容が正しいかどうかを検出しないので、アプリケーションの責任で正しい内容を書き込む必要がある。 このサンプルプログラムでは、受信フレームの内容をそのまま送信している。受信フレームも 0x00 フレームであり、その中身は適切なものになっていると期待できる。

   nine::Buffer::IoVec iov;
   while (0 < pIn->getReadBuffers(&iov, 1)) {
      pOut->writeArray(iov.ptr, iov.size);
      pIn->skipRead(iov.size);
      size -= iov.size;
   }
受信した Buffer 内容を、送信用の Buffer へ書き出している。
   pChannel->endFrame();
送信フレームの内容を書き終えたら、endFrame() を呼ぶ。