Сегодня мы используем упакованные структуры, которые накладываются непосредственно на двоичный пакет в памяти, и я сожалею о том дне, когда решил это сделать. Единственный способ заставить это работать:
- тщательное определение конкретных типов ширины в битах на основе среды компиляции (
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_;
};
Каждый из ваших пакетов классов будет иметь метод для сериализации в ByteBuffer
или десериализации из ByteBuffer
и смещения. Это одна из тех вещей, которые я абсолютно хочу, чтобы я мог вернуться в прошлое и исправить. Я не могу сосчитать, сколько раз я потратил время на отладку проблемы, вызванной забыванием менять местами байты или отсутствием упаковки struct
.
Другая ловушка, которую следует избегать, - это использование union
для представления байтов или 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)
) ... не так красиво, но очень, очень легко писать и поддерживать.