Сериализация / десериализация структуры в символ * в C - PullRequest
4 голосов
/ 31 октября 2009

У меня есть структура

struct Packet {
    int senderId;
    int sequenceNumber;
    char data[MaxDataSize];

    char* Serialize() {
        char *message = new char[MaxMailSize];
        message[0] = senderId;
        message[1] = sequenceNumber;
        for (unsigned i=0;i<MaxDataSize;i++)
            message[i+2] = data[i];
        return message;
    }

    void Deserialize(char *message) {
        senderId = message[0];
        sequenceNumber = message[1];
        for (unsigned i=0;i<MaxDataSize;i++)
            data[i] = message[i+2];
    }

};

Мне нужно преобразовать это в символ *, максимальная длина MaxMailSize> MaxDataSize для отправки по сети, а затем десериализовать его на другом конце

Я не могу использовать tpl или любую другую библиотеку.

Есть ли способ сделать это лучше, мне это не очень удобно, или это лучшее, что мы можем сделать.

Ответы [ 7 ]

6 голосов
/ 31 октября 2009

, так как это должно быть отправлено по сети, я настоятельно советую вам преобразовать эти данные в порядок байтов сети перед передачей и обратно в порядок байтов хоста при получении. Это связано с тем, что порядок следования байтов не везде одинаков, и, если ваши байты не в правильном порядке, их может быть очень трудно изменить (в зависимости от языка программирования, используемого на принимающей стороне). Функции упорядочения байтов определены вместе с сокетами и имеют имена htons(), htonl(), ntohs() и ntohl(). (в названии: h означает «хост» или ваш компьютер, n означает «сеть», s означает «короткое» или 16-битное значение, l означает «длинное» или 32-битное значение).

тогда вы сами по себе с сериализацией, C и C ++ не имеют автоматического способа выполнить это. некоторые программы могут генерировать код для вас, например, реализацию ASN.1 asn1c, но их сложно использовать, потому что они включают в себя гораздо больше, чем просто копирование данных по сети.

3 голосов
/ 31 октября 2009

В зависимости от того, достаточно ли у вас места или нет ... вы можете просто использовать потоки:)

std::string Serialize() {
  std::ostringstream out;
  char version = '1';
  out << version << senderId << '|' << sequenceNumber << '|' << data;
  return out.str();
}

void Deserialize(const std::string& iString)
{
  std::istringstream in(iString);
  char version = 0, check1 = 0, check2 = 0;
  in >> version;
  switch(version)
  {
  case '1':
    senderId >> check1 >> sequenceNumber >> check2 >> data;
    break;
  default:
    // Handle
  }
  // You can check here than 'check1' and 'check2' both equal to '|'
}

Я с готовностью признаю, что это занимает больше места ... или что могло бы.

На самом деле, в 32-битной архитектуре int обычно покрывает 4 байта (4 символа). Сериализация их с использованием потоков занимает больше 4 символов, если значение превышает 9999, что обычно дает некоторое пространство.

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

Версионирование, вероятно, хорошая идея, оно стоит недорого и допускает незапланированную последующую разработку.

3 голосов
/ 31 октября 2009

У вас может быть класс, представляющий объект, который вы используете в своем программном обеспечении, со всеми тонкостями и функциями-членами и всем, что вам нужно. Затем у вас есть «сериализованная» структура, которая больше описывает то, что в конечном итоге окажется в сети.

Чтобы компилятор сделал все, что вы ему скажете, вам нужно дать команду «упаковать» структуру. Директива, которую я здесь использовал, предназначена для gcc, смотрите документацию по компилятору, если вы не используете gcc.

Затем подпрограмма сериализации и десериализации просто конвертирует между ними, обеспечивая порядок байтов и такие подробности.

#include <arpa/inet.h>   /* ntohl htonl */
#include <string.h>      /* memcpy */

class Packet {
    int senderId;
    int sequenceNumber;
    char data[MaxDataSize];
public:
    char* Serialize();
    void Deserialize(char *message);
};

struct SerializedPacket {
    int senderId;
    int sequenceNumber;
    char data[MaxDataSize];
} __attribute__((packed));

void* Packet::Serialize() {
    struct SerializedPacket *s = new SerializedPacket();
    s->senderId = htonl(this->senderId);
    s->sequenceNumber = htonl(this->sequenceNumber);
    memcpy(s->data, this->data, MaxDataSize);
    return s;
}

void Packet::Deserialize(void *message) {
    struct SerializedPacket *s = (struct SerializedPacket*)message;
    this->senderId = ntohl(s->senderId);
    this->sequenceNumber = ntohl(s->sequenceNumber);
    memcpy(this->data, s->data, MaxDataSize);
}
1 голос
/ 31 октября 2009
int senderId;
int sequenceNumber;
...    
char *message = new char[MaxMailSize];
message[0] = senderId;
message[1] = sequenceNumber;

Вы перезаписываете значения здесь. senderId и sequenceNumber являются целыми числами и занимают больше, чем sizeof (char) байтов на большинстве архитектур. Попробуйте что-то вроде этого:

char * message = new char[MaxMailSize];
int offset = 0;
memcpy(message + offset, &senderId, sizeof(senderId));
offset += sizeof(senderId);
memcpy(message + offset, &sequenceNumber, sizeof(sequenceNumber));
offset += sizeof(sequenceNumber);
memcpy(message + offset, data, MaxDataSize);

EDIT: исправлен код, написанный в ступоре. Кроме того, как отмечено в комментарии, любой такой пакет не является переносимым из-за различий в порядке байтов.

0 голосов
/ 31 октября 2009

Вы можете использовать буфер протокола для определения и сериализации структур и классов. Это то, что Google использует внутри, и имеет очень маленький механизм передачи.

http://code.google.com/apis/protocolbuffers/

0 голосов
/ 31 октября 2009

Как упоминалось в других публикациях, senderId и sequenceNumber имеют тип int, который, вероятно, будет больше чем char, поэтому эти значения будут усечены.

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

Например, предположим, что senderId и sequenceNumber имеют длину 2 байта, и протокол требует, чтобы старший байт шел первым:

char* Serialize() {
    char *message = new char[MaxMailSize];

    message[0] = senderId >> 8;
    message[1] = senderId;

    message[2] = sequenceNumber >> 8;
    message[3] = sequenceNumber;

    memcpy(&message[4], data, MaxDataSize);

    return message;
}

Я бы также рекомендовал заменить цикл for на memcpy (если доступно), так как он вряд ли будет менее эффективным и сделает код короче.

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

    message[0] = (senderId >> 8) & 0xFF;
0 голосов
/ 31 октября 2009

Чтобы ответить на ваш вопрос в целом, C ++ не имеет механизма отражения, и поэтому ручная сериализация и десериализация функций, определенных для каждого класса, - лучшее, что вы можете сделать. При этом написанная вами функция сериализации будет искажать ваши данные. Вот правильная реализация:

char * message = new char[MaxMailSize];
int net_senderId = htonl(senderId);
int net_sequenceNumber = htonl(sequenceNumber);
memcpy(message, &net_senderId, sizeof(net_senderId));
memcpy(message + sizeof(net_senderId), &net_sequenceNumber, sizeof(net_sequenceNumber));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...