Должны ли данные полезной нагрузки сетевого пакета быть выровнены по соответствующим границам? - PullRequest
4 голосов
/ 15 апреля 2009

Если в качестве полезной нагрузки сетевого пакета используется следующий класс:

класс полезной нагрузки { char field0; int field1; char field2; int field3; };

Может ли использование такого класса, как Payload, оставить получателя данных подверженным проблемам выравнивания при получении данных через сокет? Я бы подумал, что класс нужно либо переупорядочить, либо добавить отступы, чтобы обеспечить выравнивание.

Либо изменить порядок:

class Payload
{
    int  field1;
    int  field3;
    char field0;
    char field2;
};

или добавить отступы:

class Payload
{
    char  field0;
    char  pad[3];
    int   field1;
    char  field2;
    char  pad[3];
    int   field3; 
};

Если переупорядочение по какой-то причине не имеет смысла, я думаю, что было бы предпочтительнее добавить заполнение, так как это позволило бы избежать проблем с выравниванием, даже если это увеличило бы размер класса.

Каков ваш опыт с такими проблемами выравнивания в сетевых данных?

Ответы [ 6 ]

8 голосов
/ 16 апреля 2009

Правильное слепое игнорирование выравнивания может вызвать проблемы. Даже в одной операционной системе, если 2 компонента были скомпилированы с разными компиляторами или разными версиями компилятора.

Лучше ...
1) Передайте ваши данные через какой-то процесс сериализации.
2) Или передайте каждый из ваших примитивов индивидуально, при этом обращая внимание на порядок байтов == Endianness

Хорошее место для начала будет Повысить сериализацию .

4 голосов
/ 16 апреля 2009

Сегодня мы используем упакованные структуры, которые накладываются непосредственно на двоичный пакет в памяти, и я сожалею о том дне, когда решил это сделать. Единственный способ заставить это работать:

  1. тщательное определение конкретных типов ширины в битах на основе среды компиляции (typedef unsigned int uint32_t)
  2. вставка соответствующих прагм, специфичных для компилятора, для определения плотной упаковки элементов структуры
  3. требует, чтобы все было в одном байтовом порядке (используйте порядок по сети или порядок байтов)
  4. тщательно пишет код сервера и клиента

Если вы только начинаете, я бы посоветовал вам пропустить весь беспорядок, пытаясь изобразить то, что находится на связи со структурами. Просто сериализуйте каждый элемент примитива отдельно. Если вы решите не использовать существующую библиотеку, такую ​​как 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)) ... не так красиво, но очень, очень легко писать и поддерживать.

4 голосов
/ 16 апреля 2009

Вы должны посмотреть на буферы протокола Google или Boost :: serialize, как сказал другой автор.

Если вы хотите свернуть свои собственные, сделайте это правильно.

Если вы используете типы из stdint.h (то есть: uint32_t, int8_t, и т. Д.), И убедитесь, что каждая переменная имеет "собственное выравнивание" (то есть ее адрес делится равномерно по размеру (int8_t s где угодно, uint16_t s на четных адресах, uint32_t s на адресах, кратных 4), вам не придется беспокоиться о выравнивании или упаковке.

На предыдущем задании все структуры, отправленные через нашу базу данных (Ethernet, CANbus, или byteflight, или последовательные порты), были определены в XML. Был синтаксический анализатор, который проверял выравнивание переменных внутри структур (предупреждая вас, если кто-то написал плохой XML), а затем генерировал заголовочные файлы для различных платформ и языков для отправки и получения структур. Это сработало на самом деле хорошо для нас, нам никогда не приходилось беспокоиться о написании кода вручную для анализа или упаковки сообщений, и было гарантировано, что на всех платформах не будет глупого небольшого кода ошибки. Некоторые из наших уровней связи данных были довольно ограничены по пропускной способности, поэтому мы реализовали такие вещи, как битовые поля, с парсером, генерирующим надлежащий код для каждой платформы. У нас также были перечисления, что было очень приятно (вы удивитесь, насколько легко человеку испортить битовые поля кодирования в перечислениях вручную).

Если вам не нужно беспокоиться о том, что он работает на 8051 и HC11 с C, или над уровнями каналов данных, которые сильно ограничены в пропускной способности, вы не собираетесь придумывать что-то лучше, чем буферы протокола, вы просто потратите много время пытается быть наравне с ними.

2 голосов
/ 16 апреля 2009

Мой опыт показывает, что следующие подходы должны быть предпочтительными (в порядке предпочтения):

  1. Используйте интегрированную среду высокого уровня, такую ​​как Tibco, CORBA, DCOM или любую другую, которая решит все эти проблемы за вас.

  2. Напишите свои собственные библиотеки на обеих сторонах соединения, которые знают о упаковке, порядке байтов и других проблемах.

  3. Связь только с использованием строковых данных.

Попытка отправки необработанных двоичных данных без какого-либо посредничества почти наверняка вызовет множество проблем.

1 голос
/ 16 апреля 2009

Если у вас нет естественного выравнивания в структурах, компиляторы обычно вставляют отступы, чтобы выравнивание было правильным. Однако, если вы используете прагмы, чтобы «упаковать» структуры (удалить отступы), могут быть очень вредные побочные эффекты. На PowerPC невыровненные числа с плавающей точкой генерируют исключение. Если вы работаете со встроенной системой, которая не обрабатывает это исключение, вы получите сброс. Если является подпрограммой для обработки этого прерывания, она может DRASTICALLY замедлить ваш код, потому что она будет использовать программную подпрограмму для обхода несоответствия, которая будет молча наносить вред вашей производительности .

1 голос
/ 16 апреля 2009

Вы практически не можете использовать класс или структуру для этого, если вам нужна какая-либо переносимость. В вашем примере целые числа могут быть 32-разрядными или 64-разрядными, в зависимости от вашей системы. Скорее всего, вы используете машину с прямым порядком байтов, но старые Mac от Apple имеют большой порядок байтов. Компилятор свободен для дополнения, как ему нравится.

В общем случае вам понадобится метод, который записывает каждое поле в буфер по одному байту за раз, после того как вы убедитесь, что порядок байтов правильный с n2hll, n2hl или n2hs.

...