Komische Daten vom Server

Soundthesizer, Zusitool und andere Zusatzsoftware

Moderatoren: Andreas Damm, Jens Haupert

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

Re: Komische Daten vom Server

#21 Beitrag von protonmw (Marc) »

Alt sind beide Rechner schon... aber nicht so alt. ;)

Zusi PC ist ein AMD Athlon 3600 mit Win7x64, Laptop ist ein 2 GHz Irgendwas mit XP.

Ich hab die Pakete mal mitgesnifft. Da sind Pausen bis zu 1/2 Sekunde drin. Ich weiß aber auch nicht woran es liegt. Naja wenigstens ists nicht nur bei mir so! :D

@Jens: Wie hast du das denn beim ZusiDisplay gelöst? (speziell PZB Blinken/Wechselblinken??)
MfG Marc

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

Benutzeravatar
Jens Haupert
Beiträge: 4919
Registriert: 23.03.2004 14:44:34
Aktuelle Projekte: http://www.zusidisplay.de
Wohnort: Berlin
Kontaktdaten:

Re: Komische Daten vom Server

#22 Beitrag von Jens Haupert »

protonmw hat geschrieben:@Jens: Wie hast du das denn beim ZusiDisplay gelöst? (speziell PZB Blinken/Wechselblinken??)
Hallo,

also verpasste Pakete sind mir bisher nicht aufgefallen, früher als die LMs direkt anhand der Daten angezeigt wurden hat es eigentlich immer gepasst.

Da in den Displays teilweise Hilfetexte und Bedienhinweise angezeigt werden, je nach dem in welchem "Zustand" sich die Zugbeeinflussung befindet, habe ich meine Anzeige von den Daten abstrahiert. Dh. das Display versucht jetzt anhand der Daten den Zustand zu erkennen und wechselt diesen dann. Die Anzeigen werden direkt vom ermittelten Zustand gesteuert. Da ich diverse Korrekturmechanismen ergänzt habe, da man aus den LMs nicht perfekt auf den Zustand schließen kann, fallen verpasste Pakete kaum auf, da der Algorithmus ständig versucht den Zustand zu korrigieren (sieht man teilweise auch deutlich, wie bei einer Änderung der LMs durch mehrere Zustände in schneller Folge gewechselt wird).

MfG Jens

Schorsch
Beiträge: 8
Registriert: 18.01.2010 15:34:05

Re: Komische Daten vom Server

#23 Beitrag von Schorsch »

Hi,
was für die Allgemeinheit noch von Interesse sein könnte:
Mir ist aufgefallen, dass die Daten von Zusi auf Windows XP mit einer Zeitdifferenz von ca. 125ms ausgegeben werden, wohingegen Zusi auf Windows 7 hierfür nur ca. 100ms braucht. Ist dies anderen auch schon aufgefallen?

Zu den Pausen von ca. 0,5 Sekunden ist mir bei allgemeiner Vermessung von TCP über LAN mit Hilfe von einfachen Ping-Pong Server und Clients und kleinen Datenpaketen von bis zu 128 Byte aufgefallen, dass es ab und an zu "Hängern" in der Verbindung kam. Bis jetzt habe ich aber noch keine genaue Erklärung dafür herausfinden können, das einzige was half, war die Datenpaketgröße hochzusetzten (z.B. 512 Byte: keine merkbaren Fehler aufgetreten).

Gruß
Georg

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

Re: Komische Daten vom Server

#24 Beitrag von Roland Ziegler »

Ich würde ja doch mal die Implementierung untersuchen. Das schöne an TCP ist ja, die Verbindung ist sicher, solange der Socket steht. Das betrifft OSI2. Da kann auch nichts verloren gehen. Nun kann man zwar nicht unbedingt davon ausgehen, dass die Gegenstelle auf Anwendungsebene (OSI7) immer korrekt arbeitet, und möglicherweise ankommende Pakete trotz formaler oder inhaltlicher Mängel akzeptiert und beantwortet, möglicherweise auch nicht immer ganz einwandfrei. Da aber bislang eine Reihe von Client-Implementierungen mit dem derzeitigen Server gut zusammenarbeiten, und eine neue Implementierung nicht, dann würde es nahe liegen, sich diese neue Implementierung noch einmal genauer anzuschauen.

Ein wichtiges Detail wird bei TCP-Sockets mit aufgesetzter Anwender-Block-Struktur immer wieder gerne übersehen: die Streaming-Eigenschaft. Es besteht keinerlei Garantie, dass komplette Blöcke des Senders auch als komplette Blöcke beim Empfänger eintreffen. Die Verteilung auf Datenpakete kann man zwar auf Sendeseite durch Parameter beeinflussen, aber letztlich nicht exakt bestimmen. Und das soll man auch nicht, denn das ist Aufgabe der unteren Schichten. Für jeden Empfänger bedeutet das, sich von der Idee des Empfangs immer ganzer Blöcke zu lösen. Stattdessen wiederholt man seine Leseversuche, bis entweder der ganze Block eingetroffen ist, ein (großzügiger) Timeout eintritt, oder der Socket seinerseits Fehler meldet. Leider berücksichtigen so manche Erstlingswerke das Splittingverhalten nicht. Dazu gehören nicht nur die Erzeugnisse von Hobby-Programmierern.

Auf der C#-Baustelle hatten wir das vor nicht allzu langer Zeit schon mal etwa ausführlicher: http://forum.zusi.de/viewtopic.php?f=39&t=9253
Zuletzt geändert von Roland Ziegler am 24.02.2010 17:53:11, insgesamt 1-mal geändert.

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

Re: Komische Daten vom Server

#25 Beitrag von protonmw (Marc) »

Ich will natürlich auf gar keinen Fall nich ausschließen, dass es bei meinem Client hakt.

Hat jemand von euch Ahnung von C++ mit MFC? Derjenige könnte ja mal einen Blick in meinen Code werfen... :rolleyes:
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

#26 Beitrag von Roland Ziegler »

Ich nutze durchaus noch die MFC, allerdings immer nur den reinen Fensteroberflächenteil.

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

Re: Komische Daten vom Server

#27 Beitrag von protonmw (Marc) »

Dann schau dir das mal bitte an. Wenn gewünscht kann ich dir auch das ganze Projekt (Visual Studio 2008) zukommen lassen.

EDIT: Code zur besseren Übersicht wieder entfernt ;)
Zuletzt geändert von protonmw (Marc) am 26.02.2010 09:25:58, insgesamt 2-mal geändert.
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

#28 Beitrag von Roland Ziegler »

Projekt per Email ist wohl geschickter. Im Editor liest sich besser.

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

Re: Komische Daten vom Server

#29 Beitrag von Roland Ziegler »

Vielen Dank.

Auf den ersten Blick kann ich nichts erkennen, was prinzipiell falsch ist. Allerdings sind die zahlreichen Byte-Operation alle sehr fehlerempfindlich. Ich würde das grundsätzlich vermeiden und stattdessen einen sauberen OO-Weg gehen. Auch mein C++-Beispiel neulich ist nicht gut (hatte ich ja geschrieben).

Sauberer OO-Weg: Eine Klassenstruktur für die Telegramme entwerfen, mit einer abstrakten Basisklasse und einer konkreten Klasse für die unterschiedlichen Telegrammtypen. Die Klassen implementieren eine Schnittstelle zur Serialisierung/Deserialisierung. Zudem gibt es eine Factory, die beim Empfang anhand der ID ein Objekt einer konkreten Telegrammklasse erzeugt.

Jetzt der Socket-Empfang selbst. Zunächst grundsätzlich. Das Design sollte sich nicht von der Arbeitsweise des Sockets treiben lassen sondern umgekehrt von den Bedürfnissen der Anwendung. Die Anwendung will ganze Telegramme.

Alle Telegramme haben gemeinsam, dass sie aus einem festen Kopf und einem variablen Datenteil bestehen. Die Länge des variablen Teils ergibt sich aus den Informationen im Kopf.

Der Socket-Lesemechanismus sollte also mit zwei expliziten Lesezugriffen auskommen, einem für den Kopf, einem für den Datenteil. Socket-Kommunikation erwartet einen allgemeinen und nicht-typsicheren Byte-Puffer. Vom Puffer in Telegrammklasse und umgekehrt arbeitet aber der Serialisierer. Das beschränkt die fehlerträchtigen Bytes auf ein eng abgegrenztes Terrain.

Also:

Vom Socket Kopf als Bytes lesen in Puffer. Feste Länge. Deserialisieren im Kopf-Objekt. Das Kopf-Objekt liefert die ID. Mittels Factory nun das ganze Telegramm erzeugen, anhand der ID. Dem Konstruktor das Kopf-Objekt mitgeben.

Länge des variablen Datenteils steht im Kopfobjekt. Datenteil vom Socket in Puffer einlesen. Im vorher erzeugten Telegramm deserialsieren. Telegramm nach oben liefern.



Wie aber umgehen mit dem asynchronem Verhalten? Hier kann diese MFC-Klasse vielleicht tatsächlich helfen (ich würde es ohne MFC machen, heißt aber nicht, dass es nicht geht und in der MFC-Umgebung nicht zweckmäßig ist.) Die MFC-Klasse CSocket liefert ein Ereignis bei Datenempfang über Socket. Dieses Ereignis kommt mittels interner Mechanismen im Ereignisbehandlungsthread der MFC-Anwendung an. Das ist vom Grundsatz schon ein recht kompliziertes Muster mit der Kombination aus "Reactor" und "Active Object", aber durchaus handlich und sauber, so man denn MFC auch für diese Schicht verwenden möchte.

Das Ereignis bedeutet aber nur, dass Daten (oder eine Störungsmeldung) eingetroffen sind, gibt aber nicht die Datenmenge an. Die findet man durch den Lesezugriff heraus.

Es muss also eine eigene Socket-Nutzungsklasse geben, die dafür sorgt, dass immer vollständig gelesen wird. Diese eigene Socket-Klasse wird also sinnvollerweise das Ereignis registrieren. Jetzt hat man theoretisch zwei Möglichkeiten. Man verlässt sich auf das Ereignis, liest also immer nur genausoviel Daten, wie gerade da sind, und schreibt den aktuellen Zählerstand in eine Member-Variable. Dann wartet man auf das nächste Ereignis. Erst wenn alle Daten da sind, geht es mit dem Anwendungstelegramm weiter. Der andere Ansatz würde in der Ereignisbehandlungsfunktion solange erneut zu lesen versuchen, bis tatsächlich alle Daten da sind. Der Nachteil ist sofort zu erkennen. Die Ereignisbehandlungsfunktion kann blockieren. Das ist von Übel und scheidet daher für eine gute Praxis aus.

Vorher schrieb ich, dass es eine Ebene höher genau zwei Leseaufrufe geben soll, einen für den Kopf, einen für den Datenteil. Die Socket-Nutzungsklasse muss sich also nicht nur den aktuellen Zählerstand für die Bytes merken, sondern auch, in welchem der bedien Leseaufrufs sie steckt. Das ist etwas unschön an diesem Modell, das mit mit der MFC-Ereignisbehandlung arbeitet. Mein Ansatz wäre ein separater Thread (für C# neuelich so geschildert) und dann Methode #2 des vorigen Abschnitts. Nachteil: Man wird multi-threaded. Im Single-Thread-Ansatz wird der Lesevorgang also gegebenenfalls unterbrochen, wenn die Daten nicht vollständig sind. Man muss sich den aktuellen Zustand für einen späteren Wiedereinstieg vollständig merken, in Member-Variablen. (Das Modell dafür nennt man Co-Routine.)

Detail am Rande: Umkopieren von Bytes sollte man vermeiden. In C/C++ gibt man entsprechenden API-Funktionen immer denselben Puffer vor, ab dem zweiten Versuch dann mit einem Offset, der dem aktuellen Zählerstand entspricht.

Die Socket-Nutzungsklasse, von der hier die Rede ist, muss die konkreten Telegrammklassen gar nicht kennen. Es reicht vollkommen, wenn sie die Methoden einer abstrakten Basisklasse nutzen kann. Sie muss wissen, dass es einen Kopf und einen Datenteil gibt, sie muss die Längen für beide ermitteln können, und sie muss auf eien Factory zugreifen können, die die konkreten Telegramme erzeugt. Dies alles auf abstrakter Ebene (In Java oder C# würde man das per Interface regeln).

Man braucht demnach:
  • Eine abstrakte Klasse, die serialize/deserialize-Methoden für einen Byte-Puffer ermöglicht
  • Abstrakte Basisklasse für Kopf (darf pure virtual sein, quasi ein Interface). Muss ID und feste Länger ausgeben können. Muss die serialize/deserialize-Methoden unterstützen.
  • Abstrakte Basisklasse für variablen Datenteil, wieder mit den serialize/deserialize-Methoden
  • Abstrakte Factory, die Telegrammklassen erzeuegn kann. Hat eine create-Methode, die den abstrakten Kopf als Parameter nimmt und ein abstraktes Telegrammobjekt zurück liefert.
  • Socket-Nutzungsklasse, die die zuvor genannten abstrakten Telegrammklassen nutzt. Diese Klasse wird das MFC-Ereignis "Daten vorhanden" abonnieren. Sie liest den Kopf, erzeugt per Factory das konkrete Telegramm (was sie nicht kennt) und liest den variablen Datenteil. Dies ggf in mehreren Anläufen. Erst wenn ein ganzes Telegramm vorliegt, geht das per Callback nach oben.
  • Schließlich konkrete Klassen für den Kopf und jede Art von Telegramm, die vorkommen kann.
  • Hierzu eine konkrete Factory.
Die Anwendung erzeugt ein Objekt der Socket-Nutzungsklasse, gibt sich selbst als Callback-Ziel für fertige Telegramme an, sorgt für genügend Info, dass die Socketklasse das "Daten vorhanden" Ereignis nutzen kann, und gibt der Socketklasse einen Telegrammkopf sowie die konkrete Factory mit.

Diese Lösung ist
  • rein hierarchisch,
  • kennt keine zirkularen Abhängigkeiten trotz scheinbarem gegenseitigem Bezug zwischen Telegramm- und Socketklasse,
  • verteilt sauber die Aufgaben gemäß dem OO-Paradigma,
  • ist wenig fehleranfällig, da extrem typsicher,
  • lässt sich entsprechend einfach debuggen,
  • und hat sich, in der Lösung mit separatem Thread, x-fach bewährt

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

Re: Komische Daten vom Server

#30 Beitrag von Roland Ziegler »

Nachtrag:

Das Unschöne am obigen Entwurf ist die MFC-Ereignisbehandlung und die notwendige Co-Routine.

Es ist vermutlich einfacher, gleich einen eigenen Thread zu machen, auf die Socket-Ereignisse zu verzichten, auf den Socket direkt, ohne CSocket, zuzugreifen und mit Timeout zu lesen, und das Lesen bei "Would block" wiederholen. Das ist meine Methode #2. Die Brücke zu MFC schlägt man, in dem man das fertige Telegramm per PostMesssge an MFC schickt und damit auch die Multi-Threading-Risiken minimiert.

Damit ist man dann komplett auf der Schiene, die ich favorisiere. Wie schon geschrieben, ich benutze MFC immer nur für Oberflächen. Der Rest, das muss man einfach feststellen, macht häufig genug zu große Einschränkungen bei den Patterns, die vorgegeben werden. (Dabei sollte man bedenken, die MFC stammen aus einer Zeit, in der man OO-Patterns gerade anfing.)

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

Re: Komische Daten vom Server

#31 Beitrag von protonmw (Marc) »

:achdufresse ... und nochmal :achdufresse ... immernoch :achdufresse ...

Ne Spass beiseite. Ich habe ca. die Hälfte davon verstanden. Leider bin ich noch nicht so tief in die Materie eingestiegen.
Vielleicht wäre es sinvoll mal eine Klasse gemeinsam zu Entwickeln, die dann jeder in seinem Clientprojekt nutzen kann. Bin ja nicht der Einzige der mit C++ programmiert, oder?
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

#32 Beitrag von Roland Ziegler »

Konzentrieren wir uns zunächst mal auf die Deserialisierung und die Erzeugung von Telegrammen per Factory. Es kommt also noch gar kein Socket vor. Soll auch zunächst zweitrangig sein. Wir wollen erst mal weg von den Bytes und hin zu anfassbaren Objekten.

Ganz einfaches Beispiel. Gegeben sei ein Puffer aus jenen Bytes, der unser Telegramm enthalten soll. Wir kennen vorläufig nur einen Telegrammtypen, einen mit einem String-Datenteil.

Beteiligt seien die abstrakte Form des Telegrammkopfes IHeader, die abstrakte Fabrik IFactory, und eine Hilfsklasse zur Serialiserung/Deserialisierung. In diesem Beispiel liege der Puffer schon vollständig gefüllt vor. Der Header habe 3 Byte mit 1 Byte ID und 2 Byte Länge, der Datenteil 2 Byte Länge für den String und dann den String selbst, ohne Schluss-Null.

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;
}
Aus dem rohen Byte-Puffer wird zunächst unser intelligenter IOBuffer erzeugt. Dann lesen wir den Kopf, ohne zu wissen wie dieser Kopf tatsächlich aussieht.

Mit dem Kopf kennen wir ID und Länge vom Datenteil.

Die Factory erzeugt für uns ein konkretes Telegramm, wobei wir nicht wissen, wie die Factory konkret aussieht. Aus dem Header oder dem erzeugten Telegramm erfahren wir, wie lang der Datenteil ist.

Wir lesen den Datenteil.

Als kleine Verletzung des Ansatzes machen wir jetzt mal eien konkrete Typumwandlung, um den Text unseres Telegramms zu lesen. Es sollte "Test" drinstehen.

Ganz zum Schluss nicht vergessen, das Telegramm zu löschen, um Speicherlecks zu vermeiden.

Ich schicke Dir heute Abend den ganzen Code zu dem Beispiel.


Vorab noch weitere Code-Fragmente:

Die Deserialisierung im konkreten Kopf:

Code: Alles auswählen

bool Header::deserialize (IOBuffer & buffer) {
  bool succ = buffer.extract (&m_id, sizeof(m_id));
  if (succ)
    succ = buffer.extract (&m_totalLength, sizeof(m_totalLength));
  return succ;
}
Um im konkreten Datentelegramm für Strings mit Umwandlung für C/C++ interne Speicherung

Code: Alles auswählen

  if (m_string != 0)
    delete [] m_string;

  unsigned short len;
  bool succ = buffer.extract (&len, sizeof(len));
  if (!succ)
    return false;

  m_string = new char [len+1];

  succ = buffer.extract (m_string, len);
  m_string[len] = 0;

  return succ;
Und noch die konkrete Fabrik:

Code: Alles auswählen

IDatagram * Factory::create (IHeader & header) {
  unsigned id = header.getId ();

  switch (id) {
    default:
      return 0;
    case 1:
      return new StringDatagram (header);
  }

}
Drunter schlummert die Hilfsklasse. Das Extrahieren funktioniert so:

Code: Alles auswählen

// extract from buffer
bool IOBuffer::extract (void* dst, unsigned size) {


  if (m_rdpos + size > m_maxlen )
    return false;

  memcpy (dst, (void*)&(m_buf[m_rdpos]), size);

  m_rdpos += size;

  return true;
}

Andreas Karg
Beiträge: 4718
Registriert: 28.04.2002 12:56:00
Kontaktdaten:

Re: Komische Daten vom Server

#33 Beitrag von Andreas Karg »

OT: Roland, hast du schon mal eine Professur in Erwägung gezogen? Ohne mich jetzt näher mit der Verständlichkeit deines Beispiels beschäftigt zu haben, erinnert mich Marcs Reaktion doch sehr an die Meinige auf die Vorträge von Herrn Prof. Dr. mont. habil. Ewald Werner im Fach Werkstoffkunde.
Der Mann ist unter den Studenten nur als "der Exmatrikulator" bekannt, sich dessen bewusst und vermutlich auch noch stolz darauf.
(Eigentlich ist weder der Prof noch seine Vorlesung wirklich so schlimm, aber er pflegt sein Image mit fiesen Sprüchen doch recht gut.)

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

Re: Komische Daten vom Server

#34 Beitrag von Roland Ziegler »

Ich denke, einer der Unterschiede zwischen "Programmieren" und "Software-Entwicklung" ist das Vorhandensein von Design. Hier hat sich seit der Einführung der OO sehr viel getan. Standardaufgaben werden mit Mustern gelöst ("Musterlösung" :mua ), engl Pattern. Und nichts anderes geschieht bei meinem Ansatz.. Ich hätte auch ein UML-Diagramm zur Verdeutlichung zeichnen können. :] (UML in einfacher Form wird ja bereits an manchen Schulen gelehrt.)

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

Re: Komische Daten vom Server

#35 Beitrag von protonmw (Marc) »

Na Ok. UML haben wir im gerade beendeten Semester in Softwaretechnologie gehabt.
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

#36 Beitrag von Roland Ziegler »

Beispiel ist raus. Die nächsten Tage werde ich leider nicht dazu kommen. Mein Simpel-Beispiel aus dem Fahrpult-Unterforum von neulich zeigt aber die Zugriffsmethoden auf den Socket, aus dem Win32-Socket-API, ohne MFC (aufwändig ist ja immer nur die Leserichtung). Die beiden Beispiele sollte man miteinander verknüpfen. Am besten mit dem separaten Thread. Die eigene Socket-Nutzungs-Klasse soll dann permanent versuchen, neue Telegramme zu lesen, immer in den gezeigten zwei Schritten Header und Body, wobei vor jedem Schritt genügend Daten vorhanden sein müssen, d.h zu diesem Zeitpunkt vom Socket eingelesen werden.

Code: Alles auswählen

int bytesRecv = ::recv( *pSock, (char*)&charbuf[charbufReadIdx], lenToRead, 0 );
if (bytesRecv > 0)
  charbufReadIdx += bytesRecv;
Mein Lese-Beispiel entspricht nicht ganz dem Zusi-Telegramm. Doch eine notwendige Anpassung betrifft nur die konkreten Klassen, nicht die abstrakten Basisklassen (die reine Interfaces sind).

Statt der Klasse IOBuffer hätte man auch C++-Streams nutzen können. Aber mit meiner Klasse wird vielleicht deutlicher, wie so was intern funktioniert. Und gerade für den Einsteiger ist die C++Standardbibliothek doch recht komplex.

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

Re: Komische Daten vom Server

#37 Beitrag von protonmw (Marc) »

Ist angekommen, vielen Dank erstmal!

Werde auch paar Tage brauchen mich da rein zu fitzen... :rolleyes:
MfG Marc

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

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

Re: Komische Daten vom Server

#38 Beitrag von Bernhard »

Für meine Implementierung von Server und Clients in Python verwende ich das Event-basierte Netzwerk-Framework Twisted, das mehrere bereits andernorts erwähnte, von Schmidt et al. skizzierte patterns wie zum Beispiel Reactor und Acceptor-Connector umsetzt. Ein wesentliches Merkmal von Twisted ist die Trennung von Transporten und Protokollen, wobei generell nur letztere selbst implementiert werden müssen. Natürlich kann man aber auch dabei auf praktische Basisklassen im Framework zurückgreifen.

Im Fall von Zusis Datenausgabe ist dies die abstrakte Protokollklasse Int32StringReceiver aus dem Modul twisted.protocols.basic, die Strings mit vier Byte Längenprefix verarbeitet [1]. Stellt der Reactor - zum Beispiel unter Linux durch einen Aufruf von select(2) - fest, daß Daten am Socket zur Verfügung stehen, werden diese von der Transport-Klasse gelesen und mittels Aufruf der Methode dataReceived an die Protokoll-Klasse weitergegeben. Int32StringReceiver implementiert in dieser Methode im wesentlichen den von Christopher beschriebenen Algorithmus zum Lesen kompletter Datenpakete, der mit einer beliebigen Anzahl übergebener Bytes umgehen kann. Ist ein Paket komplett, wird es ohne das nun nutzlos gewordene Längenpräfix an die Methode stringReceived übergeben, wo in einer konkreten Unterklasse die eigentliche Behandlung beginnen kann.

Wie von Roland so eindrucksvoll beschrieben, übergebe ich die kompletten Datenpakete einer Factory, die das passende Objekt aus einer Nachrichten-Typhierarchie erzeugt, welches anschließend mittels Reflection der Methode "do_<Klassenname>" (z. B. "do_AckHello") zur Bearbeitung übergeben wird. Etwas unschön finde ich in diesem Zusammenhang die Nicht-Abgeschlossenheit des Protokolls, da der Befehl 000A genau genommen nur Daten des Befehlsvorrats "Führerstandsinstrumente" bezeichnet. Für weitere Befehlsvorräte sind zumindest Änderungen an der Factory und je nach Implementierung auch neue Nachrichten-Klassen notwendig. Die Integration der Serverfunktionalität in Zusi selbst böte die Gelegenheit, diese und andere kleine Schwächen des Protokolls auszumerzen.

Abgesehen von der Typhierarchie für die im System auftretenden Nachrichten verwende ich noch eine weitere Hierarchie für die verwendeten Datentypen (vgl. TCP-Server: Strings). Auch diese Klassen besitzen Methoden zur Serialisierung/Deserialisierung, die sie teilweise von ihrer abstrakten Basisklasse erben.

[1] Eine kleine Anpassung ist notwendig, damit die Klasse mit little endian Längenpräfixen umgehen kann. Standard ist network order (big endian).

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

Re: Komische Daten vom Server

#39 Beitrag von Roland Ziegler »

Wasser auf meine Mühlen. Danke.

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.

Ich werde mir noch mal "CSocket" in MFC näher anschauen. Die Doku ist spärlich, aber es gibt ja den Quellcode, der seit der Neuimplementierung vor ein paar Jahren auch einigermaßen lesbar ist. Die Vermutung liegt nahe, dass dort so eine Art Mini-Reactor werkelt und auf select triggert. Wie sonst sollte die Ereignisdetektion zum Datenempfang erfolgen? (Connector/Acceptor kann ich dort übrigens nicht finden.)

Man muss die MFC immer aus der Zeit heraus verstehen. Es war der Versuch von Microsoft, durchschnittlichen Entwicklern den Zugang zu GUI-Anwendungen zu erleichtern. Das Ergebnis war auch nur durchschnittlich, der damaligen Qualifikation der Zielgruppe entsprechend. Die Wirkung aber war zeitweise fatal, was die MFC sehr in Verruf gebracht hat, die wesentliche Mitschuld eines großen Teils der Anwender aber zunächst außer acht ließ. Spätere Klassenbibliotheken von MS waren trotzdem um ein vielfaches gescheiter. Und wenn man sich anschaut, welche Welten zwischen den MFC und dem .Net-Framework liegen, dann sieht man die Software-Entwicklungsgeschichte dieser Firma. Java war hier sicher in vielerlei Hinsicht inspirierend. Wenn man sich anschaut, auf welche hohem Niveau die Diskussion über das Entity-Framework in .Net stattgefunden hat, dann kann MS einigermaßen stolz sein, selbst wenn die Kritik für die erste Version herb war. Das ist schon ein weiter Weg vom Quick-and-Dirty-Image der Visual-Basic-Zeit zu den Abstraktionsmodellen der Informatik heute.

Wenn sich CSocket in der Thread-Serialisierung so verhält, wie ich vermute, dann sollte ich vielleicht doch noch mal die Möglichkeit der Co-Routine näher untersuchen. Eigentlich gibt es ja nur zwei Ein-/Ausstiegspunkte, beim Lesen des Kopfes, und beim Lesen des Datenteils.

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

Re: Komische Daten vom Server

#40 Beitrag von protonmw (Marc) »

Roland,

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 )
MfG Marc

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

Antworten