Десериализация байтового массива в структуру - PullRequest
5 голосов
/ 06 февраля 2009

Я получаю передачу по сети, которая представляет собой массив символов / байтов. Он содержит заголовок и некоторые данные. Я хотел бы отобразить заголовок на структуру. Вот пример:

#pragma pack(1)

struct Header
{
    unsigned short bodyLength;
    int msgID;
    unsigned short someOtherValue;
    unsigned short protocolVersion;
};

int main()
{
    boost::array<char, 128> msgBuffer;
    Header header;

    for(int x = 0; x < sizeof(Header); x++)
        msgBuffer[x] = 0x01; // assign some values

    memcpy(&header, msgBuffer.data(), sizeof(Header));

    system("PAUSE");    

    return 0;
}

Будет ли это всегда работать при условии, что структура никогда не содержит полей переменной длины? Есть ли платформо-независимый / идиоматический способ сделать это?

Примечание:

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

Ответы [ 6 ]

5 голосов
/ 06 февраля 2009

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

Эта вторая ссылка специфична для GCC, но это относится ко всем компиляторам.

Я рекомендую читать поля побайтно и собирать более крупные поля (целые и т. Д.) Из этих байтов. Это дает вам контроль над порядком байтов и дополнением.

5 голосов
/ 06 февраля 2009

Некоторые процессоры требуют, чтобы определенные типы были правильно выровнены. Они не примут указанную упаковку и сгенерируют аппаратную ловушку.

И даже на распространенных x86-упакованных структурах код может выполняться медленнее.

Также вам придется позаботиться о работе с различными платформами с прямым порядком байтов.

Кстати, если вам нужен простой и независимый от платформы механизм связи с привязками ко многим языкам программирования, взгляните на YAMI .

2 голосов
/ 06 февраля 2009

Директива #pragma pack(1) должна работать на большинстве компиляторов, но вы можете проверить, определив, насколько большой должна быть ваша структура данных (в вашем случае 10, если моя математика верна) и используя printf("%d", sizeof(Header));, чтобы убедиться, что упаковка выполняется сделано.

Как уже говорили другие, вам все же нужно опасаться Endianness, если вы идете между архитектурами.

1 голос
/ 06 февраля 2009

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

#include <array>
#include <algorithm>

//#pragma pack(1) // not needed

struct Header
{
    unsigned short bodyLength;
    int msgID;
    unsigned short someOtherValue;
    unsigned short protocolVersion;
    float testFloat;

    Header() : bodyLength(42), msgID(34), someOtherValue(66), protocolVersion(69), testFloat( 3.14f ) {}
};

int main()
{
    std::tr1::array<char, 128> msgBuffer;
    Header header;

    const char* rawData = reinterpret_cast< const char* >( &header );

    std::copy( rawData, rawData + sizeof(Header), msgBuffer.data()); // assuming msgBuffer is always big enough

    system("PAUSE");    

    return 0;
}

Если типы на ваших тарелочных формах различаются, вы должны использовать псевдонимы (typedef) для каждого типа, чтобы быть уверенными, что размер каждого используемого типа одинаков.

1 голос
/ 06 февраля 2009

Я категорически не согласен с идеей чтения байта за байтом. Если вы позаботитесь об упаковке структуры в объявлении структуры, вы можете скопировать в структуру без проблем. Для проблемы endiannes повторное чтение побайтно решает проблему, но не дает общего решения. Этот метод очень хромает. Я делал что-то подобное раньше для аналогичной работы, и все работало без сбоев.

Подумай об этом. У меня есть структура, у меня также есть соответствующее определение этой структуры. Вы можете создать это вручную, но я написал для этого парсер и использовал его и для других вещей.

Например, определение структуры, которую вы дали выше, это "s i s s". (s = short, i = int) Затем я передаю адрес структуры, это определение и опцию упаковки структуры этой структуры специальной функции, которая имеет дело с endiannes и вуаля, как это делается.

SwitchEndianToBig (& header, "s i s s", 4); // 4 = опция упаковки структуры

0 голосов
/ 06 февраля 2009

Я знаю, с кем общаюсь, поэтому мне не нужно беспокоиться о порядке байтов. Но мне все равно нравится держаться подальше от специфичных для компилятора команд.

Так как насчет этого:

const int kHeaderSizeInBytes = 6;

struct Header
{
    unsigned short bodyLength;
    unsigned short msgID;
    unsigned short protocolVersion; 

    unsigned short convertUnsignedShort(char inputArray[sizeof(unsigned short)])
        {return (((unsigned char) (inputArray[0])) << 8) + (unsigned char)(inputArray[1]);}

    void operator<<(char inputArray[kHeaderSizeInBytes])
    {
        bodyLength = convertUnsignedShort(inputArray);
        msgID = convertUnsignedShort(inputArray + sizeof(bodyLength));
        protocolVersion = convertUnsignedShort(inputArray + sizeof(bodyLength) + sizeof(msgID));
    }
};

int main()
{
    boost::array<char, 128> msgBuffer;
    Header header;

    for(int x = 0; x < kHeaderSizeInBytes; x++)
        msgBuffer[x] = x;

    header << msgBuffer.data();

    system("PAUSE");    

    return 0;
}

Избавляется от прагмы, но это не такая общая цель, как хотелось бы. Каждый раз, когда вы добавляете поле к заголовку, вы должны изменять функцию <<. Можете ли вы как-то перебрать структурные поля, получить тип поля и вызвать соответствующую функцию? </p>

...