Komische Daten vom Server

Soundthesizer, Zusitool und andere Zusatzsoftware

Moderatoren: Andreas Damm, Jens Haupert

Nachricht
Autor
Bernhard
Beiträge: 22
Registriert: 11.09.2009 15:44:19

Re: Komische Daten vom Server

#41 Beitrag von Bernhard »

Roland Ziegler hat geschrieben:Für die Little-Endian/Big-Endian-Umwandlung habe ich meist Streams eingesetzt. Überhaupt lassen sich mit Streams alle möglichen Tricksereien während der Serialisierung/Deserialsierung durchführen. So könnte man auch die unterschiedliche String-Repräsentation dorthin auslagern und die Anwendungsschicht davon befreien. Meine Klasse IOBuffer ist ein solcher Stream in Einfachstform.
Die Klasse Int32StringReceiver verwendet als Puffer einen einfachen Python-String, den man nicht zuletzt aufgrund seiner mächtigen Slicing-Methoden fast als Stream betrachten könnte. Zur tatsächlichen Deserialisierung des Längenpräfixes nutzt sie die Funktion unpack aus dem Modul struct. Einen "echten" Stream in Form einer Unterklasse von StringIO.StringIO [1] verwende ich bei der Behandlung des DATA-Befehls. Solange im Stream Daten zur Verfügung stehen, liest die Deserialisierungs-Methode der Nachrichtenklasse Data abwechselnd ein ID-Byte und übergibt ihn dann an die zugeordnete Datentyp-Klasse, die sich so viele Bytes nimmt wie sie braucht.

[1] Zwecks einfacherer Verarbeitung lasse ich meine Klasse beim Lesen über das Ende des Streams einen EOFError werfen. Standardmäßig wird das Ergebnis je nach Position abgeschnitten oder überhaupt ein leerer String geliefert.

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Komische Daten vom Server

#42 Beitrag von Roland Ziegler »

protonmw (Marc) hat geschrieben:da ich wie gesagt erst Einsteiger bin, verstehe ich die Serialisierung noch nicht ganz. Kannst du das bitte nochmal erläutern? (Für jemanden der eher auf µC-Basis mit Bits und Bytes wirbelt :D )
Vielleicht hole ich besser noch einen Schritt weiter aus. Wenn man sich zum ersten Mal mit OO beschäftigt, begegnet einem die Klasse als zentrales Gestaltungselement. Und man erfährt, Klassen fassen Eigenschaften und Verhalten zusammen. Und das findet man einleuchtend. Aber dann denkt man nach und erkennt, ganz so einfach ist es doch nicht, denn die entscheidende Frage wird nicht beantwortet: Wie komme ich zu meinen Klassen?

Ohne jetzt ganze Entwurfstechnologien überflüssig machen zu wollen, aber bei einfachen Aufgabenstellungen hilft schon das Beschreiben der Aufgabe und das Ausformulieren von Lösungsgedanken. Man wird Sätze mit Substantiven und Verben bilden. Schaut man genauer hin, könnten sich hinter den Substantiven die Namen von Klassen verbergen und hinter Verben die Namen von Methoden. Hinter Adjektiven könnten Eigenschaften von Klassen stecken. Bei unserem überschaubaren Projekt wird mit Sicherheit sehr früh das Substantiv "Telegramm" genannt werden, ein potentieller Kandidat für eine Klasse. Und eine Eigenschaft wäre der im Telegramm enthaltene Mess- oder Anzeigewert (der nicht notwendigerweise als Adjektiv auftauchen muss). Verhalten per Verb finden wir eher: Senden und empfangen.

Damit ist unsere Telegramm-Klasse grob umrissen. Sie enthält einen Wert (Eigenschaft) und man soll das Telegramm senden oder empfangen können (Verhalten).

Sockets gibt es auch noch. Wieder ein Kandidat für eine Klasse. Der Socket hat auch wieder Eigenschaften, z.B verbunden oder nicht und mit wem. Und natürlich Verhalten. Der Socket soll ebenfalls senden und empfangen können, und zwar zum und vom anderen Socket. Was soll er vesenden oder empfangen? Telegramme. Unsere Telegramme. Die in Klassen.

Die Grundfunktion unseres Socket ist vorgegeben, durch das API. Und das kennt keine Telegramm-Klassen, das kennt nur Bytes.

Sollen wir also unsere gerade gefundene Telegramm-Klasse mit der doch praktischen Eigenschaft "Wert" wieder aufgeben und uns an diese wenig anschaulichen Bytes anpassen? Unschön. Und da andere schon vorher auf das Problem gestoßen sind, dass die anschaulichere und praktische Repräsentation von Daten in Klassen nicht unbedingt kompatibel zu dem ist, was über lange Drähte tatsächlich übertragen wird, kam man auf die Idee einer zweckgerichteten, aber ein wenig abstrahierten Wandlung. Diese hier nennt man Serialisierung (bzw. Deserialiserung in Umkehrrichtung). Bei der Serialisierung werden die wahlfrei zugreifbaren Eigenschaften des Objekts einer Klasse in eine Datensequenz verwandelt, auf die nur seriell zugegriffen werden kann. Dazu kann man wieder eigene Klassen benutzen benutzen. Die nennt man dann z.B. Stream.

Das anfangs genannte und angestrebte Verhalten unserer Telegramm-Klasse, sich selbst versenden oder empfangen zu können, läuft also darauf hinaus, die eigenen Eigenschaften in ein vom weiterem Transport vorgegebenes sequentielles Format zu wandeln. Genau das machen wir mit besagter Serialisierung.

Und durch die Hintertür haben wir gleichzeitig ein weiteres nicht unwesentliches Merkmal der OO mit eingeführt: Das der der Abstrahierung. Wir können nachvollziehen, dass die Umwandlung von seriellen Bytes in wahlfrei zugreifbare Eigenschaften notwendig ist. Wir nennen es abstrakt "Serialisierung" und können das notwendige dafür in sogar in eine eigene Klasse zusammenfassen, jenen Stream.

Ich hoffe, mit dieser Erläuterung wird das Vorgehen klarer.

protonmw (Marc)
Beiträge: 300
Registriert: 06.05.2009 10:59:49
Wohnort: Freiberg(Sachs)

Re: Komische Daten vom Server

#43 Beitrag von protonmw (Marc) »

Danke dafür,

allerdings war das schon (mehr oder weniger) klar. Mir fehlt jetzt einfach der Übergang von Bytes zu Klassendaten. Wie (und wo in deinem gesendeten Beispiel) wird das gemacht?
MfG Marc

"Wir genießen das Leben in vollen Zügen!"

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Komische Daten vom Server

#44 Beitrag von Roland Ziegler »

Das Interface ISerialize definiert die Methoden serialize() und deserialize(). Es wird von der Kopf- und den Telegramm-Klasse(n) implementiert und nimmt als Parameter die Mini-Ausführung eines Stream-Puffers, bei mir genannt IOBuffer. Dort gibt es extract() und insert(). Und dortherinnen verbirgt sich die C/C++-spezifische Umsetzung zwischen Bytes und Zieldaten. (In Java oder C# ginge das so nicht und sähe etwas anders aus.)

protonmw (Marc)
Beiträge: 300
Registriert: 06.05.2009 10:59:49
Wohnort: Freiberg(Sachs)

Re: Komische Daten vom Server

#45 Beitrag von protonmw (Marc) »

Ich glaub ich habs verstanden! Du gibst übergibst z.B. dem extract() einen Zeiger auf ein Objekt vom Datentyp den ich habe möchte (z.B. DWORD) und übergibst die Größe (4Byte) und dann werden die Bytes einfach kopiert. Richtig?
MfG Marc

"Wir genießen das Leben in vollen Zügen!"

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Komische Daten vom Server

#46 Beitrag von Roland Ziegler »

Korrekt. Und die Umsetzung ist wirklich spezifisch für C/C++. Das Prinzip ist aber für alle OO-Sprachen gleich. Ist gedacht für die Basisdatentypen der Sprache. Bei komplexeren Strukturen (Objektbäumen) wird man serialize/deserialize als Kaskade aufrufen, siehe die Serialisierung des String-Telegramms.

protonmw (Marc)
Beiträge: 300
Registriert: 06.05.2009 10:59:49
Wohnort: Freiberg(Sachs)

Re: Komische Daten vom Server

#47 Beitrag von protonmw (Marc) »

Nächste Frage:

Code: Alles auswählen

void CMFCSocketDlg::OnBnClickedButton1()
{
  IHeader & header = getHeader ();
  IFactory & factory = getFactory();

  unsigned char charbuf [] = {1, 9, 0, 4, 0, 'T', 'e', 's', 't'};
  unsigned bufferLength = sizeof(charbuf);

  IOBuffer iobuf (charbuf, bufferLength);

  unsigned headerLen = header.getHeaderLength ();
  if (headerLen > bufferLength)
    return; // zu kurz. Beim Socket an dieser Stelle dafür sorgen, dass genügend Daten da sind.

  header.deserialize (iobuf);

  IDatagram * pDatagram = factory.create (header);
  if (!pDatagram)
    return;

  unsigned bodyLen = pDatagram->getBodyLength ();
  if (headerLen + bodyLen > bufferLength)
    return; // zu kurz

  pDatagram->deserialize (iobuf);

  char * str = 0;
  StringDatagram * pStringDatagram = dynamic_cast<StringDatagram *>(pDatagram);
  if (pStringDatagram) {
    str = pStringDatagram->getString();
  }


  delete pDatagram;
}
Es liegt ja charbuf vor (der z.B. durch socket.receive() gefüllt werden könnte...). Jetzt erzeugst du den intelligenten Puffer iobuf und übergibst dem ctor nen Zeiger auf charbuf und die Länge. Soweit ok.

Als nächstes kommt der Aufruf unsigned headerLen = header.getHeaderLength ();
Was ist jetzt "header" bzw. wie funktioniert das mit dem Interface? Oder besser gesagt, kannst du bitte mal die Zeile IHeader & header = getHeader (); erklären?
MfG Marc

"Wir genießen das Leben in vollen Zügen!"

Benutzeravatar
Roland Ziegler
Beiträge: 5508
Registriert: 04.11.2001 22:09:26
Wohnort: 32U 0294406 5629020
Kontaktdaten:

Re: Komische Daten vom Server

#48 Beitrag von Roland Ziegler »

Die Socket-Verarbeitung (idealerweise in einer eigenen Klasse) weiß, dass Telegramme aus Kopf und Datenteil bestehen. Aber sie weiß nicht - und soll es auch nicht wissen - wie genau Kopf und Datenteil aufgebaut sind. Lediglich die Länge in Bytes ist von Bedeutung. Entsprechend kennt die Socket-Verarbeitung auch den Kopf (Header) nur über seine Schnittstelle.

Die Implementierung von IHeader, also Header, füllt die Methode getHeaderLength () aus. In meinem Beispiel ist diese Länge 3. Die ist natürlich konstant, denn auf konstanter Kopflänge basiert die ganze Idee mit dem Lesezugriff in zwei Schritten.

Der Aufruf der Schnittstellenmethode von IHeader wird umgelenkt auf die tatsächliche Implementierung in Header, Stichwort: Polymorphie.

Da die Socket-Verarbeitung universell sein soll, und nicht abhängig von einer konkreten Ausprägung einer Telegrammfamilie, kann man hier keine direkte Konstante verwenden. Auch eine statische Methode würde nicht funktionieren, weil nicht polymorph. In C# würde man getHeaderLength als Property schreiben, was aber nur der Lesbarkeit dient und genauso ein polymorpher Methodenaufruf ist.
Zuletzt geändert von Roland Ziegler am 03.03.2010 10:23:28, insgesamt 1-mal geändert.

Antworten