web-dev-qa-db-fra.com

C - Techniques de sérialisation

J'écris du code pour sérialiser certaines données pour les envoyer sur le réseau. Actuellement, j'utilise cette procédure primitive:

  1. créer un void* tampon
  2. appliquer des opérations de commande d'octets telles que la famille hton sur les données que je souhaite envoyer sur le réseau
  3. utilisez memcpy pour copier la mémoire dans le tampon
  4. envoyer la mémoire sur le réseau

Le problème est qu'avec diverses structures de données (qui contiennent souvent des données void * afin que vous ne sachiez pas si vous devez vous soucier de l'ordre des octets), le code devient vraiment gonflé de code de sérialisation très spécifique à chaque structure de données et ne peut pas être réutilisé du tout.

Quelles sont les bonnes techniques de sérialisation pour C qui rendent cela plus facile/moins laid?

-

Remarque: je suis lié à un protocole spécifique, je ne peux donc pas choisir librement comment sérialiser mes données.

31
ryyst

Pour chaque structure de données, disposez d'une fonction serialize_X (où X est le nom de la structure) qui prend un pointeur vers un X et un pointeur vers une structure de tampon opaque et appelle les fonctions de sérialisation appropriées. Vous devez fournir des primitives telles que serialize_int qui écrivent dans le tampon et mettent à jour l'index de sortie. Les primitives devront appeler quelque chose comme reserve_space (N) où N est le nombre d'octets requis avant d'écrire des données. reserve_space () réallouera le tampon void * pour le rendre au moins aussi grand que sa taille actuelle plus N octets. Pour rendre cela possible, la structure du tampon devra contenir un pointeur vers les données réelles, l'index dans lequel écrire l'octet suivant (index de sortie) et la taille allouée pour les données. Avec ce système, toutes vos fonctions serialize_X devraient être assez simples, par exemple:

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

Et le code cadre sera quelque chose comme:

#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;
    }
}

De là, il devrait être assez simple d'implémenter toutes les fonctions serialize_ () dont vous avez besoin.

EDIT: Par exemple:

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: Notez également que mon code a des bugs potentiels. La taille du tableau de tampons est stockée dans un size_t mais l'index est un int (je ne sais pas si size_t est considéré comme un type raisonnable pour un index). En outre, il n'y a aucune disposition pour la gestion des erreurs et aucune fonction pour libérer le tampon après avoir terminé, vous devrez donc le faire vous-même. Je faisais juste une démonstration de l'architecture de base que j'utiliserais.

34
jstanley

Je suggère d'utiliser une bibliothèque.

Comme je n'étais pas satisfait de celles existantes, j'ai créé la bibliothèque Binn pour nous faciliter la vie.

Voici un exemple d'utilisation:

  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);
5
Bernardo Ramos

Je dirais certainement n'essayez pas d'implémenter la sérialisation vous-même. Cela a été fait un million de fois et vous devez utiliser une solution existante. par exemple. protobufs: https://github.com/protobuf-c/protobuf-c

Il présente également l'avantage d'être compatible avec de nombreux autres langages de programmation.

4
Assaf Lavie

Cela aiderait si nous savions quelles sont les contraintes de protocole, mais en général vos options sont vraiment assez limitées. Si les données sont telles que vous pouvez faire une union d'une taille de tableau d'octets (struct) pour chaque structure, cela pourrait simplifier les choses, mais d'après votre description, il semble que vous ayez un problème plus essentiel: si vous transférez des pointeurs (vous mentionnez void * data), il est très peu probable que ces points soient valides sur la machine réceptrice. Pourquoi les données apparaîtront-elles au même endroit en mémoire?

2
Charlie Martin