今日、メモリ内のバイナリパケットに直接オーバーレイされるパック構造を使用しており、それを実行することを決定した日を失っています。これを機能させる唯一の方法は、次のとおりです。
- コンパイル環境に基づいてビット幅固有のタイプを慎重に定義する(
typedef unsigned int uint32_t
)
- 適切なコンパイラ固有のプラグマを挿入して、構造体メンバーの密なパッキングを指定します
- すべてが1バイトの順序である必要があります(ネットワークまたはビッグエンディアンの順序を使用)
- サーバーとクライアントの両方のコードを注意深く書く
始めたばかりの場合は、構造物でワイヤー上にあるものを表現しようとする混乱全体をスキップすることをお勧めします。各プリミティブ要素を個別にシリアル化するだけです。Boost Serializeのような既存のライブラリやTibCoのようなミドルウェアを使用しないことを選択した場合は、シリアル化メソッドの詳細を隠すバイナリバッファの周りに抽象化を記述することで、多くの頭痛の種を節約できます。次のようなインターフェースを目指します。
class ByteBuffer {
public:
ByteBuffer(uint8_t *bytes, size_t numBytes) {
buffer_.assign(&bytes[0], &bytes[numBytes]);
}
void encode8Bits(uint8_t n);
void encode16Bits(uint16_t n);
//...
void overwrite8BitsAt(unsigned offset, uint8_t n);
void overwrite16BitsAt(unsigned offset, uint16_t n);
//...
void encodeString(std::string const& s);
void encodeString(std::wstring const& s);
uint8_t decode8BitsFrom(unsigned offset) const;
uint16_t decode16BitsFrom(unsigned offset) const;
//...
private:
std::vector<uint8_t> buffer_;
};
各パケットクラスには、aByteBuffer
にシリアル化するか、aByteBuffer
とoffsetから逆シリアル化するメソッドがあります。これは、私が過去に戻って修正できることを絶対に望んでいることの1つです。バイトの交換を忘れたり、をパックしなかったりしたために発生した問題のデバッグに時間を費やした回数を数えることができませんstruct
。
避けるべきもう1つのトラップは、union
バイトを表すためにaを使用するかmemcpy
、バイトを抽出するためにunsignedcharバッファーにingを使用することです。常にビッグエンディアンをネットワークで使用している場合は、簡単なコードを使用してバイトをバッファーに書き込むことができ、内容について心配する必要はありませんhtonl
。
void ByteBuffer::encode8Bits(uint8_t n) {
buffer_.push_back(n);
}
void ByteBuffer::encode16Bits(uint16_t n) {
encode8Bits(uint8_t((n & 0xff00) >> 8));
encode8Bits(uint8_t((n & 0x00ff) ));
}
void ByteBuffer::encode32Bits(uint32_t n) {
encode16Bits(uint16_t((n & 0xffff0000) >> 16));
encode16Bits(uint16_t((n & 0x0000ffff) ));
}
void ByteBuffer::encode64Bits(uint64_t n) {
encode32Bits(uint32_t((n & 0xffffffff00000000) >> 32));
encode32Bits(uint32_t((n & 0x00000000ffffffff) ));
}
数値表現は常に論理的にビッグエンディアンであるため、これはプラットフォームにとらわれないままです。このコードは、プリミティブ型のサイズに基づいたテンプレートを使用するのにも非常に適しています(考えてみてくださいencode<sizeof(val)>((unsigned char const*)&val)
)...それほどきれいではありませんが、非常に簡単に記述および保守できます。