J'écris du code pour sérialiser certaines données pour les envoyer sur le réseau. Actuellement, j'utilise cette procédure primitive:
void*
tamponhton
sur les données que je souhaite envoyer sur le réseaumemcpy
pour copier la mémoire dans le tamponLe 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.
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.
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);
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.
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?