Подтвердить что ты не робот

C - методы сериализации

Я пишу код для сериализации некоторых данных для отправки по сети. В настоящее время я использую эту примитивную процедуру:

  • создать буфер void*
  • применять любые операции упорядочения байтов, такие как семейство hton для данных, которые я хочу отправить по сети.
  • используйте memcpy для копирования памяти в буфер
  • отправить память через сеть

Проблема заключается в том, что с различными структурами данных (которые часто содержат данные void *, поэтому вы не знаете, нужно ли вам заботиться о порядке байтов), код становится действительно раздутым с кодом сериализации, который очень специфичен для каждой структуры данных и может 're reused вообще.

Каковы хорошие методы сериализации для C, которые делают это проще/менее уродливым?

-

Примечание. Я привязан к определенному протоколу, поэтому я не могу свободно выбирать, как сериализовать мои данные.

4b9b3361

Ответ 1

Для каждой структуры данных используйте функцию serialize_X (где X - это имя структуры), которая принимает указатель на X и указатель на непрозрачную структуру буфера и вызывает соответствующие функции сериализации. Вы должны указать некоторые примитивы, такие как serialize_int, которые записывают в буфер и обновляют выходной индекс. Примитивам нужно будет вызвать что-то вроде reserve_space (N), где N - это количество байтов, которое требуется перед записью любых данных. reserve_space() перераспределяет буфер void *, чтобы сделать его как минимум большим, чем текущий размер плюс N байтов. Чтобы сделать это возможным, структура буфера должна содержать указатель на фактические данные, индекс для записи следующего байта (индекс вывода) и размер, который выделяется для данных. В этой системе все ваши функции serialize_X должны быть довольно простыми, например:

struct X {
    int n, m;
    char *string;
}

void serialize_X(struct X *x, struct Buffer *output) {
    serialize_int(x->n, output);
    serialize_int(x->m, output);
    serialize_string(x->string, output);
}

И код рамки будет выглядеть примерно так:

#define INITIAL_SIZE 32

struct Buffer {
    void *data;
    int next;
    size_t size;
}

struct Buffer *new_buffer() {
    struct Buffer *b = malloc(sizeof(Buffer));

    b->data = malloc(INITIAL_SIZE);
    b->size = INITIAL_SIZE;
    b->next = 0;

    return b;
}

void reserve_space(Buffer *b, size_t bytes) {
    if((b->next + bytes) > b->size) {
        /* double size to enforce O(lg N) reallocs */
        b->data = realloc(b->data, b->size * 2);
        b->size *= 2;
    }
}

Из этого, должно быть довольно просто реализовать все функции serialize_(), которые вам нужны.

EDIT: Например:

void serialize_int(int x, Buffer *b) {
    /* assume int == long; how can this be done better? */
    x = htonl(x);

    reserve_space(b, sizeof(int));

    memcpy(((char *)b->data) + b->next, &x, sizeof(int));
    b->next += sizeof(int);
}

EDIT: Также обратите внимание, что у моего кода есть некоторые потенциальные ошибки. Размер массива буфера хранится в size_t, но индекс является int (я не уверен, что size_t считается разумным типом для индекса). Кроме того, нет никаких предустановок для обработки ошибок и нет функции освобождения буфера после того, как вы закончите, поэтому вам придется сделать это самостоятельно. Я просто продемонстрировал базовую архитектуру, которую я бы использовал.

Ответ 2

Я бы сказал, что определенно не пытайтесь реализовать сериализацию самостоятельно. Это было сделано за миллион раз, и вы должны использовать существующее решение. например protobufs: https://github.com/protobuf-c/protobuf-c

Это также имеет то преимущество, что оно совместимо со многими другими языками программирования.

Ответ 3

Это помогло бы, если бы мы знали, что такое ограничения протокола, но в целом ваши варианты действительно очень ограничены. Если данные таковы, что вы можете сделать объединение байтового массива sizeof (struct) для каждой структуры, это может упростить вещи, но из вашего описания это звучит так, как будто у вас есть более существенная проблема: если вы передаете указатели (вы упоминаете void *), то эти точки вряд ли будут действительны на принимающей машине. Почему данные будут отображаться в одном месте в памяти?

Ответ 4

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

Поскольку я был недоволен существующими, я создал библиотеку Binn, чтобы облегчить нашу жизнь.

Вот пример его использования:

  binn *obj;

  // create a new object
  obj = binn_object();

  // add values to it
  binn_object_set_int32(obj, "id", 123);
  binn_object_set_str(obj, "name", "Samsung Galaxy Charger");
  binn_object_set_double(obj, "price", 12.50);
  binn_object_set_blob(obj, "picture", picptr, piclen);

  // send over the network
  send(sock, binn_ptr(obj), binn_size(obj));

  // release the buffer
  binn_free(obj);