メッセージクラス

メッセージクラス定義方法

サーバー・クライアントで交換するメッセージの構造を定義する。 通信プロトコルを設計し、一回の送受信で送るものをメッセージとして定義するとよい。

ベースクラスとしてクラステンプレートが提供されているので、それを使って継承する。

template <int KIND, int TYPE, typename IMPL>
class MessageTmpl: public Message
KIND
4bit の数値で、メッセージの大分類を区別する。
0 は nine 内部で使っているので、自分で使う場合は 1から15までを使うこと。
TYPE
KIND 内でユニークな値で、12bit で指定する。
IMPL
これから実装しようとしているクラスを指定する。
さらに、KIND=1 の記述を簡略した UserMessage テンプレートが提供されているので、 簡単なアプリケーションではこれを使うと良い。
template <int TYPE, typename IMPL>
class UserMessage: public MessageTmpl< 1, TYPE, IMPL > { };
このサンプルでも UserMessage を利用する。
class TestMessage: public nine::UserMessage&lt; 0x35, TestMessage >
{
      typedef nine::Buffer Buffer;
   public:
      virtual int getMarshalSize() const;
      virtual void marshal(Buffer* pBuf) const;
      virtual void unmarshal(Buffer* pBuf);
ここで、TYPE=0x35 に特に意味は無い。

プロトコル設計

任意長の文字列を送受信することとしたい。

文字列を直接扱う API もあるが、ここでは長さ+文字列の中身を直接扱うことにする。

メッセージの内容は次のようになる:
  • 文字列の長さ (uint16)
  • 文字列(NULL含まず)
マーシャル表現はそのまま並べたものでよいだろう:
 | 文字列の長さ (uint16) | 文字列(NULL含まず) |

メッセージ実装

続いて、このプロトコル設計に対応するメッセージを実装する。

データは可変長であるから、ヒープに確保した方が良いだろう。 そのデータのポインタと、データの長さの二つのメンバが必要である。 クラス設計原則にならい、メンバは隠蔽してセット・ゲット関数を提供する。

class TestMessage: public nine::UserMessage< 0x35, TestMessage >
{
   public:
      void setString(const char* sz);
      const char* getString() const;

   protected:
      char* m_szStr;
      uint16_t m_len;
};

marshal() 関数実装

marshal() 関数で、メッセージのマーシャル表現を Buffer に書きだす。

メッセージ定義で決めたように、ストリームへは文字列長と中身を順に書く。

void TestMessage::marshal(Buffer* pBuf) const
{
   pBuf->writeUInt16(m_len);
   pBuf->writeArray(m_szStr, m_len);
}

getMarshalSize() 関数

メッセージを marshal() した時のサイズを返す関数 getMarshalSize() を実装する必要がある。

int TestMessage::getMarshalSize() const
{
   return (m_len + 2);
}
ここでは次のことを期待してサイズを計算している:
  • writeUInt16() で 2byte 書かれること
  • writeArray() が引数サイズと同じだけ書かれること
  • ヘッダやパディングが付かないこと
この仮定は現行バージョン(1.x)では有効であるが、将来のメジャーバージョンアップでは変更される可能性がある。

unmarshal()関数実装

marshal() と対になる、Buffer からデータを読み込んでメッセージ内容を更新する関数である。

void TestMessage::unmarshal(Buffer* pBuf)
{
   uint16_t size;
   pBuf->readUInt16(&size);
   m_szStr = new char[size + 1];
   m_szStr[size] = '\0';
   pBuf->readArray(m_szStr, size);
}

簡単のために new だけしているが、既に確保されてたら delete を行うべきであろう。