C – Serialisierungstechniken

Ich schreibe einen Code, um einige Daten zu serialisieren, um sie über das Netzwerk zu senden. Derzeit verwende ich diese primitive Prozedur:

  1. Erstelle einen void* buffer
  2. hton alle Byte-Ordnungsoperationen wie die hton Familie auf die Daten an, die ich über das Netzwerk senden möchte
  3. Verwenden Sie memcpy um den Speicher in den Puffer zu kopieren
  4. Sende den Speicher über das Netzwerk

Das Problem ist, dass bei verschiedenen Datenstrukturen (die oft void * -Daten enthalten, so dass Sie nicht wissen, ob Sie sich um die Byte-Reihenfolge kümmern müssen) der Code wirklich aufgebläht wird mit Serialisierungscode, der sehr spezifisch für jede Datenstruktur ist und nicht sein kann überhaupt wiederverwendet.

Was sind einige gute Serialisierungstechniken für C, die das einfacher / weniger hässlich machen?

Hinweis: Ich bin an ein bestimmtes Protokoll gebunden, sodass ich nicht frei wählen kann, wie ich meine Daten serialisieren soll.

   

Für jede Datenstruktur, haben Sie eine function serialize_X (wobei X der Name der Struktur ist), die einen pointers auf ein X und einen pointers auf eine undurchsichtige Pufferstruktur nimmt und die entsprechenden Serialisierungsfunktionen aufruft. Sie sollten einige Primitive wie serialize_int angeben, die in den Puffer schreiben und den Ausgabe-Index aktualisieren. Die Grundelemente müssen etwas wie reserve_space (N) aufrufen, wobei N die Anzahl der Bytes ist, die benötigt werden, bevor irgendwelche Daten geschrieben werden. reserve_space () wird den void * -Puffer neu zuweisen, um ihn mindestens so groß wie seine aktuelle Größe plus N Bytes zu machen. Um dies zu ermöglichen, muss die Pufferstruktur einen pointers auf die tatsächlichen Daten enthalten, den Index, um das nächste Byte zu schreiben (Ausgabe-Index) und die Größe, die den Daten zugeordnet ist. Mit diesem System sollten alle Ihre serialize_X-functionen ziemlich einfach sein, zum Beispiel:

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

Und der Framework-Code wird etwa so aussehen:

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

Von daher sollte es ziemlich einfach sein, alle von Ihnen benötigten serialize_ () -functionen zu implementieren.

EDIT: Zum Beispiel:

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

Bearbeiten: Beachten Sie auch, dass mein Code einige potenzielle Fehler hat. Die Größe des Pufferarrays ist in einer size_t gespeichert, aber der Index ist ein int (ich bin mir nicht sicher, ob size_t als vernünftiger Typ für einen Index angesehen wird). Außerdem gibt es keine Vorkehrungen für die Fehlerbehandlung und keine function zum Freigeben des Puffers, nachdem Sie fertig sind, also müssen Sie dies selbst tun. Ich gab nur eine Demonstration der grundlegenden Architektur, die ich verwenden würde.

Ich würde definitiv nicht versuchen, Serialisierung selbst zu implementieren. Es wurde schon unzählige Male durchgeführt und Sie sollten eine bestehende Lösung verwenden. zB protobufs: https://github.com/protobuf-c/protobuf-c

Es hat auch den Vorteil, mit vielen anderen Programmiersprachen kompatibel zu sein.

Es würde helfen, wenn wir wüssten, was die Protokollbeschränkungen sind, aber im Allgemeinen sind Ihre Möglichkeiten wirklich ziemlich begrenzt. Wenn die Daten so sind, dass Sie für jede Struktur eine Vereinigungsmenge einer Byte-Array-Größe von (struct) erstellen können, könnte das Dinge vereinfachen, aber aus Ihrer Beschreibung klingt es so, als hätten Sie ein wesentlicheres Problem: Wenn Sie pointers übertragen (Sie erwähnen void * data), dann ist es sehr unwahrscheinlich, dass diese Punkte auf dem empfangenden Rechner gültig sind. Warum würden die Daten zufällig am gleichen Ort im Speicher erscheinen?

Ich schlage vor, eine Bibliothek zu verwenden.

Da ich mit den bestehenden nicht zufrieden war, habe ich die Binn- Bibliothek geschaffen, um unser Leben zu erleichtern.

Hier ist ein Beispiel für die Verwendung:

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