WebSocketメッセージハンドラチュートリアル

WebSocketBroker

WebSocketBroker クラスは、mpchat_server で WebSocketMessageHandler を利用している部分である。 定義は次のようになっている:

class WebSocketBroker: public nine::WebSocketMessageHandler
{
   public:
      bool initialize(ChatService*);

      virtual void turn();
      
      virtual bool handleMessage(nine::Message*);
      virtual void onSessionOpen(int sid, nine::WebSocketChannel* pChannel);
      virtual void onSessionClose(int sid);

   private:
      void notifyLog(int cid);
      ChatService* m_pService;
      struct Comm {
            inline Comm() { }
            bool connected;
            int lastIndex;
      };
      std::vector< Comm > m_comms;
};

HttpServer への登録

WebSocketBroker は WebSocketMessageHandler であり、WebSocketHandler である。 WebSocketHandler は、HttpServer の authorizeWebSocket() 関数の戻り値としてフレームワークに渡すことで利用されるようになる。

この処理は Server の initialize() 内で実装されている:
bool Server::initialize(const ServerConfig* pCfg)
{
      ...
      if (! m_wsBroker.initialize(&m_service)) {
         return false;
      }
      m_httpServer.setHandler("/ws", &m_wsBroker);

m_httpServer は PathHttpServer である。 setHandler() により、WebSocketBroker は "/ws" のリクエストパスに対応するよう設定される。

initialize

initialize() では、親クラスである WebSocketMessageHandler の初期化を行っている。
bool WebSocketBroker::initialize(ChatService* pService)
{
   {
      typedef nine::WebSocketMessageHandler super;
      nine::WebSocketMessageHandlerConfig cfg;
      cfg.nSessions = 10;
      cfg.sessionTimeoutSec = 0;

      if (! super::initialize(&cfg)) {
         return false;
      }
   }

   m_comms.resize(10);
   for (int i=0; i<m_comms.size(); ++i) {
      m_comms[i].connected = false;
   }

   m_pService = pService;
   return true;
}
上のコードは具体的には次のような設定を行っている:
  • WebSocketセッションの同時接続数は 10
  • タイムアウトは行わない。

notifyLog()

notifyLog() は private 関数であり、このクラスの他の関数から呼ばれる。 処理内容は、ChatService からログを取得し、LogNotify メッセージに入れて post することである。

void WebSocketBroker::notifyLog(int cid)
{
   LogNotify msg;
   m_pService->get(m_comms[cid].lastIndex, &msg);

   int n = msg.get_chat_length();
   if (n == 0) {
      return;
   }
   if (! post(cid, &msg)) {
      return;
   }
   
   m_comms[cid].lastIndex = msg.get_chat(n-1).get_index();
}

ChatService::get() では、受け取るログの最初の番号を指定できる。 WebSocketBroker はこの番号を lastIndex に記憶しておき、get() に渡している。

WebSocket は常時接続されているが、送信バッファがいっぱいの場合などで post() は失敗する。 post() 成功時に、最後に送信したメッセージ番号を更新するようにしている。

handleMessage()

handleMessage() は、要求に含まれていたメッセージを処理するハンドラである。

WebSocket はサーバープッシュが実現できるので、プロトコル設計のうち LogRequest は使う必要がない。 サンプルでも、ChatRequest のみをを処理している:
bool WebSocketBroker::handleMessage(nine::Message* pMsg)
{
   int cid = pMsg->getCommunicatorId();
   int msgid = pMsg->getMessageId();
   if (msgid == ChatRequest::MESSAGE_ID) {
      ChatRequest* p = static_cast< ChatRequest* >(pMsg);
      m_pService->add(p->get_chat());
      notifyLog(cid);
      return true;
   } else if (msgid == LogRequest::MESSAGE_ID) {
      return true;
   }
   return false;
}

ChatRequest は、クライアントが新規発言をした際に送られるメッセージである。 ChatRequest は同時に発言ログの要求も行う。

サーバー側では、
  • 発言を発言ログへ追加する
  • 新しいログを返す
という処理を行う。コードは次のようになる:
   if (msgid == ChatRequest::_MESSAGE_ID) {
      ChatRequest* p = static_cast< ChatRequest* >(pMsg);
      m_pService->add(p->get_chat());
      notifyLog(cid);
      return true;
WebSocketBroker は最後に送信したログを記憶しているので、ChatRequest のログ番号指示は無視している。