C – Serialisierungstechniken

Lesezeit: 6 Minuten

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

  1. ein … kreieren void* Puffer
  2. Wenden Sie alle Byte-Order-Operationen wie die an hton Familie über die Daten, die ich über das Netzwerk senden möchte
  3. verwenden memcpy um den Speicher in den Puffer zu kopieren
  4. Senden Sie den Speicher über das Netzwerk

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

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

Hinweis: Ich bin an ein bestimmtes Protokoll gebunden, daher kann ich nicht frei wählen, wie ich meine Daten serialisieren möchte.

  • Wenn Sie nicht bereits ein festes Protokoll hatten XDR ist eine ziemlich weit verbreitete Wahl. Es ist jedoch schwer zu wissen, was man sagen soll, wenn man die Darstellung nicht ändern kann.

    – Flexo

    14. Mai 2011 um 14:49 Uhr

Benutzeravatar von jstanley
jstanley

Haben Sie für jede Datenstruktur eine serialize_X-Funktion (wobei X der Strukturname ist), die einen Zeiger auf ein X und einen Zeiger 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 Ausgabeindex aktualisieren. Die Primitiven müssen so etwas wie reserve_space(N) aufrufen, wobei N die Anzahl der Bytes ist, die erforderlich sind, bevor Daten geschrieben werden. reserve_space() weist den void*-Puffer neu zu, um ihn mindestens so groß wie seine aktuelle Größe plus N Bytes zu machen. Um dies zu ermöglichen, muss die Pufferstruktur einen Zeiger auf die eigentlichen Daten, den Index zum Schreiben des nächsten Bytes (Ausgangsindex) und die den Daten zugewiesene Größe enthalten. Mit diesem System sollten alle Ihre serialize_X-Funktionen 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 Rahmencode wird in etwa so aussehen:

#define INITIAL_SIZE 32

struct Buffer {
    void *data;
    size_t 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 hier aus sollte es ziemlich einfach sein, alle benötigten serialize_() -Funktionen 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 enthält. Es gibt keine Fehlerbehandlung und keine Funktion zum Freigeben des Puffers, nachdem Sie fertig sind, also müssen Sie dies selbst tun. Ich habe nur eine Demonstration der grundlegenden Architektur gegeben, die ich verwenden würde.

  • Ich liebe dieses kleine Skript, das ich im ersten Jahr der Akademie studieren musste XD

    – dynamisch

    14. Mai 2011 um 15:04 Uhr


  • Es ist auch eine gute Idee, der Datei einige magische Bytes und eine Versionsnummer voranzustellen, um eingehende Daten, die Sie definitiv nicht verarbeiten können, schnell zu unterscheiden. Es ist auch möglich, jeder Struktur eine Version zu geben. Und zu guter Letzt: Seien Sie paranoid, wenn Sie Daten “von außen” parsen, da Unachtsamkeit Ihr Programm angreifbar oder zumindest instabil macht.

    – Datenwolf

    14. Mai 2011 um 15:27 Uhr

  • Verwenden b->data + b->next ist nicht tragbar, weil b->next Typ hat void *.

    – Dietrich Ep

    24. Juni 2011 um 23:40 Uhr

  • Natürlich muss gesagt werden, dass dieser Ansatz vollständig plattformspezifisch ist und daher nicht maschinenübergreifend oder in Netzwerkszenarien verwendet werden kann.

    – Noldorin

    25. Februar 2014 um 22:09 Uhr

  • @vexe: Ja, sowohl Byte- als auch Bit-Endianness für den Anfang. Auch einfach das verwendete Darstellungsformat, insbesondere für negative Zahlen (2er- oder 1er-Komplement?).

    – Noldorin

    28. August 2015 um 19:50 Uhr

Benutzeravatar von Assaf Lavie
Assaf Lavie

Ich würde sagen, versuchen Sie auf keinen Fall, die Serialisierung selbst zu implementieren. Es wurde zigmal gemacht und Sie sollten eine vorhandene Lösung verwenden. zB protobufs: https://github.com/protobuf-c/protobuf-c

Es hat auch den Vorteil, dass es mit vielen anderen Programmiersprachen kompatibel ist.

  • +1 – Die Verwendung einer externen Serialisierung von Drittanbietern bringt weitere Vorteile mit sich, z. B. Tools, die Streams untersuchen und die von ihnen beschriebenen Objekte direkt anzeigen können.

    – Flexo

    14. Mai 2011 um 15:27 Uhr

Benutzeravatar von Bernardo Ramos
Bernhard Ramos

Ich schlage vor, eine Bibliothek zu verwenden.

Da ich mit den bestehenden nicht zufrieden war, habe ich die erstellt Binn Bibliothek, um unser Leben einfacher zu machen.

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

  • Ich habe die binn-Bibliothek ausprobiert und sie ist ziemlich gut. Aber mir ist aufgefallen, dass es viele Serialisierungsbibliotheken gibt, die in einigen Fällen funktionieren, in anderen jedoch sehr schlecht. Je nach Inhalt würde ich also empfehlen, einige auszuprobieren.

    – Anton Krug

    16. Februar 2018 um 10:24 Uhr

Es wäre hilfreich, wenn wir wüssten, was die Protokolleinschränkungen sind, aber im Allgemeinen sind Ihre Optionen wirklich ziemlich begrenzt. Wenn die Daten so sind, dass Sie eine Vereinigung eines Byte-Arrays sizeof(struct) für jede Struktur erstellen können, könnte dies die Dinge vereinfachen, aber Ihrer Beschreibung nach scheint es, als hätten Sie ein wesentlicheres Problem: Wenn Sie Zeiger übertragen (Sie erwähnen void * data), dann ist es sehr unwahrscheinlich, dass diese Punkte auf dem empfangenden Rechner gültig sind. Warum erscheinen die Daten zufällig an derselben Stelle im Speicher?

Benutzeravatar von dash-o
Strich-o

Für “C”-Programme, wenn es nicht viele gute Optionen für die “automatische” Serialisierung gibt. Bevor Sie “aufgeben”, schlagen Sie vor, das SUNRPC-Paket (rpcgen und Freunde) zu überprüfen. Es hat:

  • Benutzerdefiniertes Format, die “XDR”-Sprache (im Grunde eine Teilmenge von “C”) zur Beschreibung der Datenstruktur.
  • RPC-Generierung – Ermöglicht die automatische Generierung der Client- und Serverseite der Serialisierung.
  • Laufzeitbibliothek, die mit (fast) allen Unix-Umgebungen ausgeliefert wird.

Das Protokoll und der Code entsprechen dem Internetstandard.

1386990cookie-checkC – Serialisierungstechniken

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy