我们今天使用直接覆盖在内存中的二进制数据包上的打包结构,我为我决定这样做的那一天感到后悔。我们让它工作的唯一方法是:
- 根据编译环境仔细定义位宽特定类型 (
typedef unsigned int uint32_t
)
- 插入适当的编译器特定的编译指示以指定结构成员的紧密封装
- 要求一切都按一个字节顺序排列(使用网络或大端顺序)
- 仔细编写服务器和客户端代码
如果您刚刚开始,我建议您跳过尝试用结构表示线路上的内容的整个混乱。只需分别序列化每个原始元素。如果您选择不使用 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
和偏移量反序列化。这是我绝对希望我能回到过去并纠正的事情之一。我无法计算由于忘记交换字节或未打包struct
.
要避免的另一个陷阱是使用 aunion
表示字节或memcpy
使用无符号字符缓冲区提取字节。如果您总是在线上使用 Big-Endian,那么您可以使用简单的代码将字节写入缓冲区,而不必担心这些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) ));
}
这仍然很好地与平台无关,因为数字表示在逻辑上始终是 Big-Endian。这段代码也非常适合使用基于原始类型大小的模板(想想encode<sizeof(val)>((unsigned char const*)&val)
)......不是那么漂亮,但非常非常容易编写和维护。