サーバー・クライアントで交換するメッセージの構造を定義する。 通信プロトコルを設計し、一回の送受信で送るものをメッセージとして定義するとよい。
ベースクラスとしてクラステンプレートが提供されているので、それを使って継承する。
template <int KIND, int TYPE, typename IMPL> class MessageTmpl: public Message
template <int TYPE, typename IMPL>
class UserMessage: public MessageTmpl< 1, TYPE, IMPL > { };
class TestMessage: public nine::UserMessage< 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含まず) |
続いて、このプロトコル設計に対応するメッセージを実装する。
データは可変長であるから、ヒープに確保した方が良いだろう。 そのデータのポインタと、データの長さの二つのメンバが必要である。 クラス設計原則にならい、メンバは隠蔽してセット・ゲット関数を提供する。
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() 関数で、メッセージのマーシャル表現を Buffer に書きだす。
メッセージ定義で決めたように、ストリームへは文字列長と中身を順に書く。
void TestMessage::marshal(Buffer* pBuf) const
{
pBuf->writeUInt16(m_len);
pBuf->writeArray(m_szStr, m_len);
}
メッセージを marshal() した時のサイズを返す関数 getMarshalSize() を実装する必要がある。
int TestMessage::getMarshalSize() const
{
return (m_len + 2);
}
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 を行うべきであろう。