Сериализация / десериализация структуры в символ * в C

c++ c serialization

17054 просмотра

7 ответа

38 Репутация автора

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

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 или любую другую библиотеку.

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

Автор: Ankur Chauhan Источник Размещён: 31.10.2009 07:19

Ответы (7)


1 плюс

23548 Репутация автора

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);

РЕДАКТИРОВАТЬ: исправлен код, написанный в ступоре. Также, как отмечено в комментарии, любой такой пакет не является переносимым из-за различий в порядке байтов.

Автор: Jherico Размещён: 31.10.2009 07:39

0 плюса

44083 Репутация автора

Чтобы ответить на ваш вопрос в целом, в 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));
Автор: Charles Salvia Размещён: 31.10.2009 07:50

6 плюса

17358 Репутация автора

Решение

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

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

Автор: Adrien Plisson Размещён: 31.10.2009 07:59

3 плюса

1734 Репутация автора

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

Чтобы убедиться, что компилятор будет делать все, что вы ему скажете, вы должны указать ему «упаковать» структуру. Директива, которую я здесь использовал, предназначена для 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);
}
Автор: 246tNt Размещён: 31.10.2009 09:17

0 плюса

2290 Репутация автора

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

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

Например, допустим , что 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;
Автор: Steve Melnikoff Размещён: 31.10.2009 12:27

3 плюса

216149 Репутация автора

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

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, что обычно дает некоторое пространство.

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

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

Автор: Matthieu M. Размещён: 31.10.2009 04:12

0 плюса

5246 Репутация автора

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

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

Автор: Stef Размещён: 31.10.2009 04:39
32x32