staging.inyokaproject.org

[Framework] Event-basierte Netzwerkkommunikation

Status: Ungelöst | Ubuntu-Version: Kein Ubuntu
Antworten |

Glocke

Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Hiho,

ich arbeite seit einer Weile mit C++ und SDL. Bisher hatte ich nichts vergleichbares gefunden, deswegen habe ich für mein Problem selber einen Lösungsansatz entwickelt: https://github.com/cgloeckner/networking

Dabei handelt es sich um ein Framework, welches in C++ geschrieben ist und SDL sowie SDL_net verwendet. Es bietet die Möglichkeit "Events" über das Netzwerk zu versenden und an der Gegenseite entsprechend zu behandeln. Die Events werden in threadsicheren Warteschlangen angesammelt und sequenziell gesendet, beziehungsweise gelesen und wieder in einer Warteschlange aneinandergereiht. Dort verbleiben sie bis zu ihrer Bearbeitung. Dabei handelt es sich bei den "Events" um Ableitungen einer Struktur Event. Sie enthält eine Event-ID, anhand der die Events auf der Gegenseite bezüglich ihres ursprünglichen Typs erkannt und gecastet werden können. Dabei verwenden beide (oder besser alle) Seiten die gleiche Menge an Events.

Einschränkungen

Meine Events dürfen nur Primitivdaten enthalten, keine Zeiger oder std-Container. Grund dafür ist die fehlende Serialisierung vor dem Versenden über das Netzwerk. Ohne Zeiger und Container können die Events direkt geschrieben werden, ohne übrige Daten vorher serialisieren zu müssen.

Lizenz und Aufbau

Das Projekt habe ich unter der Lizenz CC BY-NC 3.0 veröffentlicht. Im verlinkten GitHub-Repository findet ihr den Quellcode des Frameworks (src-Verzeichnis) sowie zwei Beispiel-Programme example1.cpp (TCP- bzw. UDP-basiertes Beispiel, welches direkt die NetworkingQueue verwendet) und example2.cpp (TCP-basiertes Beispiel einer kleinen Server-Client-Struktur mit Worker-Threads, die jeweils einen Client bedienen).

Einige weitere Gedanken

Das Projekt verwendet bereits Features von C++11. Eigentlich will ich es von C++11 loslösen, weil ja noch nicht alle Compiler C++11 in gleichem Umfang unterstützen. Daher habe ich beispielsweise alle Threading-bezogenen Dinge auf der Threading-Funktionalität von SDL aufgebaut.

Ich bin gespannt auf euer Feedback ☺

LG Glocke

mareb

Avatar von mareb

Anmeldungsdatum:
9. August 2008

Beiträge: 163

Hallo Christian, vielen Dank für Deine Networking-Module! Ich habe schon mal grob geschaut und mir gefällt, wie Du programmiert hast. Das Modul kann ich vielleicht gebrauchen, ich muß es noch genauer verstehen. Vor allem kannte ich bislang noch nicht die SDL und SDL_net (siehe http://de.wikibooks.org/wiki/SDL:_Sdlnet und http://www.libsdl.org/projects/SDL_net/ ) - was mich irgendwie ärgert.

Ich selbst schreibe C und C++ und verwende von C++ viele Features nicht. C++ Entwickler mögen das belächeln, aber ich fand immer bedenklich, dass ich fremden C++ Code nur selten vollständig verstand, fast nie Code-Schnipsel übertragen konnte - kurz, ich brauche immer einen tiefen Überblick über sämtliche Headerdateien, um den C++-Code zu verstehen. Das ist bei Deinem Source erfreulich einfacher.

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Hi,

ich versuche immer meinen Code so lesbar wie möglich aufzubauen. Dementsprechend freut es mich dein Feedback zu lesen ☺ Ich hoffe sogar, dass man SDL bzw. SDL_net gar nicht kennen muss, um den Code zu verstehen. Im Grunde baut das gesamte Modul sich darum auf.

Demnächst werde ich noch ein paar Speicherlecks beheben .. mal schauen wie lange das dauert 😀

LG Glocke

chaos.dennis

Anmeldungsdatum:
8. November 2011

Beiträge: 42

Hallo,

in der Boost lib wird man fündig, ist aber eine andere Liga: http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio.html

Ansonsten ist mir aufgefallen:

  • Ich würde structs die wie Klassen aussehen auch als Klassen deklarieren, mit deinen structs kommen evtl. nicht alle Compiler klar. Vorallem wenn du noch davon ableitest

  • Viel zu kompliziert:

1
2
3
4
5
6
7
8
    EventID id = event->event_id;
    link->send<EventID>(&id);
    // send event data
    switch (id) {
        case TEST:
            link->send<Test>(event);
            break;
    }

Wenn deine Events sowieso alle von eine Basis klasse haben, reicht auch das:

1
link->send(event);

Du brauchst keine Templates in dem Beispiel, du kannst es durch saubere Klassenhierarchie lösen. Das einzig sinnvolle template ist queue<Event>, alle anderen sind "missbraucht".

  • Wo ist überhaupt Event::fromTcp implementiert?

  • das geht garnicht:

1
2
3
    Test(std::string text): Event(TEST) {
        memcpy(this->text, text.c_str(), 255);
    }
  • Die Beispiele sind Prozedural und nicht event driven ☺.

Gruß,

Dennis

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

chaos.dennis schrieb:

  • Ich würde structs die wie Klassen aussehen auch als Klassen deklarieren, mit deinen structs kommen evtl. nicht alle Compiler klar. Vorallem wenn du noch davon ableitest

Imho ist das Ableiten von structs in C++ kein Problem. Ich habe hier eine Struktur verwendet, weil ich nicht extra noch "public:" dazuschreiben wollte. Allerdings ist es allein schon aus Sicht der Übersichtlichkeit "schöner" generell Klassen zu verwenden.

chaos.dennis schrieb:

  • Viel zu kompliziert:

1
2
3
4
5
6
7
8
    EventID id = event->event_id;
    link->send<EventID>(&id);
    // send event data
    switch (id) {
        case TEST:
            link->send<Test>(event);
            break;
    }

Wenn deine Events sowieso alle von eine Basis klasse haben, reicht auch das:

1
link->send(event);

Das sehe ich nicht so! Die Send-Funktion schreibt entsprechend der Größe des Typparameters die Anzahl der Bytes. Die Funktion (die du hier zitierst) bekommt ein Event*. Ein abgeleitetes Event ist i.d.R. größer als das Basis-Event. Daher würden Informationen abgeschnitten werden. Gleiches gilt für die Gegenseite: Sie muss wissen welches Event erwartet wird (und damit wie viele Bytes).

chaos.dennis schrieb:

Du brauchst keine Templates in dem Beispiel, du kannst es durch saubere Klassenhierarchie lösen. Das einzig sinnvolle template ist queue<Event>, alle anderen sind "missbraucht".

Spielst du damit auf die Server-Client-Worker-Geschichte an? Ich finde das derzeit auch noch "ekelhaft", habe aber keine andere Lösung gefunden, da ich die Server/Clients/Worker im Beispiel ja noch weiter spezialisere und intern dann mit den Spezialisierungen (anstatt der Basen) gearbeitet werden soll. Für Vorschläge bin ich offen ☺

chaos.dennis schrieb:

  • Wo ist überhaupt Event::fromTcp implementiert?

Die wird im Example implementiert (direkt da wo du das obige gefunden hast ^^)

chaos.dennis schrieb:

  • das geht garnicht:

1
2
3
    Test(std::string text): Event(TEST) {
        memcpy(this->text, text.c_str(), 255);
    }

Das habe ich lokal schon geändert ^^ (Minimum von 255 und text.size()). Aber ist richtig ^^

chaos.dennis schrieb:

  • Die Beispiele sind Prozedural und nicht event driven ☺.

Die Beispiele sind absichtlich einfach gehalten ☺

LG Glocke

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Glocke schrieb:

Spielst du damit auf die Server-Client-Worker-Geschichte an? Ich finde das derzeit auch noch "ekelhaft", habe aber keine andere Lösung gefunden, da ich die Server/Clients/Worker im Beispiel ja noch weiter spezialisere und intern dann mit den Spezialisierungen (anstatt der Basen) gearbeitet werden soll. Für Vorschläge bin ich offen ☺

Ich hab die Sache jetzt nochmal so umgebaut, dass ich dort ohne Templates auskomme ... 😉

chaos.dennis

Anmeldungsdatum:
8. November 2011

Beiträge: 42

Huhu,

ich hab mich jetzt nicht im detail beschäftigt, würde es aber etwas anders angehen ☺. Bau eine klasse die sich nur ums senden/empfangen kümmert, mit Template argument. Die event Objekte haben damit nichts zu tun und müßen auch nichts davon wissen, erben oder sonst was.

Mit dem Send hast du recht, auf die art wie es da Implementiert ist muß es so gemacht werden, wobei das sizeof(type) in TcpLink::send ist mir irgendwie suspeckt. Ich persönlich würde die Objekte in json oder xml serialisieren und dann als strings durch die gegend schicken, ist aber geschmackssache. Wenn du aber nachher nur einen Part des Client/Servers änderst, wird das böse folgen haben.

Klappt das eigentlich, wenn in der TransferQueue verschiedene typen von "events" liegen?

Wo ist überhaupt Event::fromTcp implementiert?

Die wird im Example implementiert (direkt da wo du das obige gefunden hast ^^)

Wenn du was in einer hpp definierst, ist es ja durchaus sinnig das dann in der entsprechenden cpp zu implementieren, es sei denn du willst den leser ärgern ☺.

Gruß,

Dennis

chaos.dennis

Anmeldungsdatum:
8. November 2011

Beiträge: 42

Moin,

denkansätze:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
enum EventIds { E_STR, E_INT }

class EventBase
{
private:
  int id:
public:
  int getId() {return id;}
};

class EventStr : EventBase
{
public:
  EventStr() { id = E_STR; }
  buffer[255] data;
};

class EventInt : EventBase
{
public:
  EventInt() { id = E_INT; }
  int data;
};

void Link::send(EventBase *event)
{
  swtich(event->getId()) {
    case E_STR: TCPLink<EventStr>::send(event);
    ...
}

Ich würde es aber aus oben angeführten gründen eher so machen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <sstream>
typedef std::stringstream ss;

enum EventIds { E_STR, E_INT }

class EventBase
{
private:
  int id:
public:
  int getId() {return id;}
  virtual std::string toString() = 0;
  virtual fromString(std::string &data) = 0;
};

class EventStr : EventBase
{
private:
  std::string data;
public:
  EventStr() { id = E_STR; }
  std::string toString() { return data; }
  void fromString(std::string &data) { this->data = data; }
};

class EventInt : EventBase
{
private:
  int data;
public:
  EventInt() { id = E_INT; }
  std::string toString() { ss s; ss<<data; return ss.str(); }
  void fromString(std::string &data) { ss s(data); this->data << ss; }
};

void Link::send(EventBase *event)
{
  TCPLink<EventID>::send(event->getId());
  TCPLink<string>::send(event->toString());
}

Wobei ich noch die eventid am anfang von toString paacken würde, dann hast du nur einen Send aufruf mit strings.

Gruß,

Dennis

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

chaos.dennis schrieb:

ich hab mich jetzt nicht im detail beschäftigt, würde es aber etwas anders angehen ☺. Bau eine klasse die sich nur ums senden/empfangen kümmert, mit Template argument. Die event Objekte haben damit nichts zu tun und müßen auch nichts davon wissen, erben oder sonst was.

Hab ich doch: Die Klasse TcpLink ☺

chaos.dennis schrieb:

Mit dem Send hast du recht, auf die art wie es da Implementiert ist muß es so gemacht werden, wobei das sizeof(type) in TcpLink::send ist mir irgendwie suspeckt. Ich persönlich würde die Objekte in json oder xml serialisieren und dann als strings durch die gegend schicken, ist aber geschmackssache. Wenn du aber nachher nur einen Part des Client/Servers änderst, wird das böse folgen haben.

Naja ich sende entsprechend der Bytelänge. JSON und XML nehmen mir da zu viel Platz ein neben der eigentlichen Information. Außerdem spare ich mir so das serialisieren und deserialisieren, wenn ich ausschließlich Primitivdatentypen verwende. Das war die Absicht der ganze Sache ☺

chaos.dennis schrieb:

Klappt das eigentlich, wenn in der TransferQueue verschiedene typen von "events" liegen?

Ja das klappt, weil die Events IDs haben:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void Event::toTcp(TcpLink* link, Event* event) {
    // send event id
    EventID id = event->event_id;
    link->send<EventID>(&id);
    // send event data
    switch (id) {
        case TEST:
            link->send<Test>(event);
            break;
    }
}

Erst wird die ID gesendet und dann (anhand der ID) das Event, wobei dabei der Typ und (wegen sizeof(Typ)) auch die Anzahl der Bytes (die dieser Datentyp belegt) verwendet wird. Im Endeffekt steht das ja alles hinter der Adresse des Events. Kennt man die Anzahl der Bytes kann man die Daten einfach "blind" schreiben und lesen. Da die Events auf beiden Seiten gleich aufgebaut sind, kommt jedes Byte wieder in den richtigen Kontext und wird zum Bestandteil der Information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Event* Event::fromTcp(TcpLink* link) {
    Event* event = NULL;
    // receive event id
    EventID* id = link->receive<EventID>();
    // receive event
    switch (*id) {
        case TEST:
            event = link->receive<Test>();
            break;
    }
    delete id;
    return event;
}

Beim Lesen geschieht das genauso: aha Event ID XY, also lese ich sizeof(...)-viele Bytes.

chaos.dennis schrieb:

Wenn du was in einer hpp definierst, ist es ja durchaus sinnig das dann in der entsprechenden cpp zu implementieren, es sei denn du willst den leser ärgern ☺.

Das Framework an sich kennt keine spezialisierten Events, die der Programmierung auf Basis des Frameworks verwenden will; die sind vom Einsatzzweck des Programms abhängig. Daher müssen beide Methoden beim Verwenden des Frameworks implementiert werden - genauso wie spezielle Events implementiert werden.

Weißt du was ich meine?

LG Glocke

chaos.dennis

Anmeldungsdatum:
8. November 2011

Beiträge: 42

Glocke schrieb:

chaos.dennis schrieb:

ich hab mich jetzt nicht im detail beschäftigt, würde es aber etwas anders angehen ☺. Bau eine klasse die sich nur ums senden/empfangen kümmert, mit Template argument. Die event Objekte haben damit nichts zu tun und müßen auch nichts davon wissen, erben oder sonst was.

Hab ich doch: Die Klasse TcpLink ☺

Ja aber das send musste von Event implementiert werden, ist ungünstig designed.

Naja ich sende entsprechend der Bytelänge. JSON und XML nehmen mir da zu viel Platz ein neben der eigentlichen Information. Außerdem spare ich mir so das serialisieren und deserialisieren, wenn ich ausschließlich Primitivdatentypen verwende. Das war die Absicht der ganze Sache ☺

Nachteile:

  • nur Primitivtypen umständlich und fehleranfällig (Memory Management)

  • Client oder Server crashen wenn nur einer mit änderung neu kompiliert wird.

  • Compiler und optimierungen sollten (müßten?) immer die selben sein

  • Debuggen und ist wesentlich schwerer als bei plain strings

chaos.dennis schrieb:

Klappt das eigentlich, wenn in der TransferQueue verschiedene typen von "events" liegen?

Ja das klappt, weil die Events IDs haben:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void Event::toTcp(TcpLink* link, Event* event) {
    // send event id
    EventID id = event->event_id;
    link->send<EventID>(&id);
    // send event data
    switch (id) {
        case TEST:
            link->send<Test>(event);
            break;
    }
}

Erst wird die ID gesendet und dann (anhand der ID) das Event, wobei dabei der Typ und (wegen sizeof(Typ)) auch die Anzahl der Bytes (die dieser Datentyp belegt) verwendet wird. Im Endeffekt steht das ja alles hinter der Adresse des Events. Kennt man die Anzahl der Bytes kann man die Daten einfach "blind" schreiben und lesen. Da die Events auf beiden Seiten gleich aufgebaut sind, kommt jedes Byte wieder in den richtigen Kontext und wird zum Bestandteil der Information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Event* Event::fromTcp(TcpLink* link) {
    Event* event = NULL;
    // receive event id
    EventID* id = link->receive<EventID>();
    // receive event
    switch (*id) {
        case TEST:
            event = link->receive<Test>();
            break;
    }
    delete id;
    return event;
}

Beim Lesen geschieht das genauso: aha Event ID XY, also lese ich sizeof(...)-viele Bytes.

Aber das verstehe ich nicht:

1
2
template <typename T>
void ThreadSafeQueue<T>::push(T* data)

kannst du da unterschiedliche typen reinpushe?

chaos.dennis schrieb:

Wenn du was in einer hpp definierst, ist es ja durchaus sinnig das dann in der entsprechenden cpp zu implementieren, es sei denn du willst den leser ärgern ☺.

Das Framework an sich kennt keine spezialisierten Events, die der Programmierung auf Basis des Frameworks verwenden will; die sind vom Einsatzzweck des Programms abhängig. Daher müssen beide Methoden beim Verwenden des Frameworks implementiert werden - genauso wie spezielle Events implementiert werden.

Weißt du was ich meine?

Im grunde schon funktioniertt auch so, ist aber in meinen Augen ungünstig. Du mußt dich fragen, was jemmand der das benutzten will alles implementieren muß und wissen muß. Und den part dann von möglichst viel Fachlogik abkoppeln. Auch diese Templates mußt du an X stellen dann einfügen switchen etc. .

In meinem ansatz muß der Anwender nur EventBase implementieren, wie er serialisiert ist ja egal, hauptsache String. Das einzige was er dann noch braucht ist eine Factory klasse die dann die empfangen Events je nach typ erstellt. Die vorteile überwiegen mM deutlich dem nachteil, ein paar bytes mehr zu senden. Es gibt ja auch gründe warum xml & co sich durchsetzten und nicht mehr structs in biniary files gedumpt werden, du machst es aber genau so ☺.

Gruß,

Dennis

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Hi,

chaos.dennis schrieb:

Nachteile:

  • nur Primitivtypen umständlich und fehleranfällig (Memory Management)

Inwiefern ist das umständlicher und fehleranfälliger?!

chaos.dennis schrieb:

  • Client oder Server crashen wenn nur einer mit änderung neu kompiliert wird.

  • Compiler und optimierungen sollten (müßten?) immer die selben sein

Naja nach Änderungen an Events sollten schon beide Seiten neu kompiliert werden. Zu der Sache mit der Optimierung kann ich nichts sagen; aber i.d.R. baut man doch (wenn man Client und Server schon in zwei Anwendungen trennt) beide mit den gleichen Hilfsmitteln, oder?

chaos.dennis schrieb:

1
2
template <typename T>
void ThreadSafeQueue<T>::push(T* data)

kannst du da unterschiedliche typen reinpushe?

Ich kann da alle Elemente reinpushen, die von T abgeleitet sind, wenn ich sie zur T* caste (die Informationen bleiben dabei ja erhalten - nur erstmal "nicht zugänglich" über den Typ T*)

chaos.dennis schrieb:

In meinem ansatz muß der Anwender nur EventBase implementieren, wie er serialisiert ist ja egal, hauptsache String. Das einzige was er dann noch braucht ist eine Factory klasse die dann die empfangen Events je nach typ erstellt. Die vorteile überwiegen mM deutlich dem nachteil, ein paar bytes mehr zu senden. Es gibt ja auch gründe warum xml & co sich durchsetzten und nicht mehr structs in biniary files gedumpt werden, du machst es aber genau so ☺.

Du willst die ID und dann den String senden? Das Problem ist, dass ich nicht so direkt einen String senden kann: die Gegenseite muss wissen, wie viele Zeichen sie dann lesen muss. Da würde ich eher die String-Länge und dann den String senden; auf der Gegenseite den String dann zum Event deserialisieren, vorausgesetzt im String steht (z.B. vorne) welches Event das mal war. Aber dann muss ich wieder switchen... Ich glaube egal wie wir das drehen: um das Switchen beim Lesen werde ich nicht herum kommen.

chaos.dennis schrieb:

Im grunde schon funktioniertt auch so, ist aber in meinen Augen ungünstig. Du mußt dich fragen, was jemmand der das benutzten will alles implementieren muß und wissen muß. Und den part dann von möglichst viel Fachlogik abkoppeln. Auch diese Templates mußt du an X stellen dann einfügen switchen etc. .

Er muss seine speziellen Events implementieren und die toTcp- / fromTcp-Sache für seine speziellen Events. Ich denke nicht, dass ich dem Programmierer abnehmen kann ohne zu wissen, welche Events er verwenden will - das weiß ja nur er ☺

LG Glocke

chaos.dennis

Anmeldungsdatum:
8. November 2011

Beiträge: 42

Moin,

Glocke schrieb:

chaos.dennis schrieb:

Ja aber das send musste von Event implementiert werden, ist ungünstig designed.

Alternativ hätte ich auch eine alleinstehende Funktion nehmen können..

Schöner? 😉

chaos.dennis schrieb:

Nachteile:

  • nur Primitivtypen umständlich und fehleranfällig (Memory Management)

Inwiefern?

  • Kein Vector/liste etc möglich

  • Keine dynamisch langen Strings

  • Feste feldgröße für die Daten

  • memcopy und co. provuzieren fehler und schlampereien (zummindest habe ich bei mir diese erfahrung gemacht ☺ )

chaos.dennis schrieb:

  • Client oder Server crashen wenn nur einer mit änderung neu kompiliert wird.

  • Compiler und optimierungen sollten (müßten?) immer die selben sein

Naja nach Änderungen an Events sollten schon beide Seiten neu kompiliert werden. Zu der Sache mit der Optimierung kann ich nichts sagen; aber i.d.R. baut man doch (wenn man Client und Server schon in zwei Anwendungen trennt) beide mit den gleichen Hilfsmitteln, oder?

Linux server, windows oder android client? 32 oder 64bit, little/big endian...

chaos.dennis schrieb:

  • Debuggen und ist wesentlich schwerer als bei plain strings

Kann ich nichts dazu sagen ^^

Um so wichtiger es einfach zu halten ☺. Wenn dir da binaries im TCPDump rumschwirren wirst du da nie schlau draus.

chaos.dennis schrieb:

Aber das verstehe ich nicht:

1
2
template <typename T>
void ThreadSafeQueue<T>::push(T* data)

kannst du da unterschiedliche typen reinpushe?

Ich kann da alle Elemente reinpushen, die von T abgeleitet sind, wenn ich sie zur T* caste (die Informationen bleiben dabei ja erhalten - nur erstmal "nicht zugänglich" über den Typ T*)

Von mir aus, ist mir persönlich aber zu heiß für client/server.

chaos.dennis schrieb:

Im grunde schon funktioniertt auch so, ist aber in meinen Augen ungünstig. Du mußt dich fragen, was jemmand der das benutzten will alles implementieren muß und wissen muß. Und den part dann von möglichst viel Fachlogik abkoppeln. Auch diese Templates mußt du an X stellen dann einfügen switchen etc. .

Eine andere Möglichkeit - als für die verschiedenen Typen zu switchen - ist mir bisher nicht eingefallen.

Beim senden kannst du es komplett über die Strings regeln, beim empfangen Factory pattern als pure virtual dem anwedner vorsetzten. A la:

1
2
3
4
class EventFactory()
{
  virtual EventBase &assemble(int id, string &data) = 0;
};

Dein Framework arbeitet mit dieser Klasse und kümmert sich um die übertragung.

Du willst die ID und dann den String senden? Das Problem ist, dass ich nicht so direkt einen String senden kann: die Gegenseite muss wissen, wie viele Zeichen sie dann lesen muss. Da würde ich eher die String-Länge und dann den String senden; auf der Gegenseite den String dann zum Event deserialisieren, vorausgesetzt im String steht (z.B. vorne) welches Event das mal war. Aber dann muss ich wieder switchen... Ich glaube egal wie wir das drehen: um das Switchen beim Lesen werde ich nicht herum kommen.

Ist hier doch schon fertig (gewesen):

1
void TcpLink::send(void* data, std::size_t len) 

Die Länge zu ermitteln ist trivial, EventID ist als getter da. Ich würde das dann so aufbauen: Server → TCP → | size_t len, int id, char *data | → TCP → Client

Da brauchst du von Anwender Seite nicht mehr zu implementieren als die 2. EventBase Vorlage. Den rest kriegst du dann auf framework seite zusammengebastelt.

LG Glocke

ebenso ☺.

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

chaos.dennis schrieb:

  • Kein Vector/liste etc möglich

  • Keine dynamisch langen Strings

  • Feste feldgröße für die Daten

Eingangs hatte ich glaube erwähnt, dass sowas unter "Grenzen des Frameworks" fällt ☺

chaos.dennis schrieb:

  • memcopy und co. provuzieren fehler und schlampereien (zummindest habe ich bei mir diese erfahrung gemacht ☺ )

Okay das ist mir neu ... ^^

chaos.dennis schrieb:

Ist hier doch schon fertig (gewesen):

1
void TcpLink::send(void* data, std::size_t len) 

Davon war ich abgegangen, weil ich dann den Speicher nur mit malloc allokieren kann und ihn somit mit free freigeben muss. Beabsichtigt war von mir aber, dass alle Events (egal ob sie vom Netzwerk gelesen oder vom System so generiert wurden) mit delete zu löschen sein sollen.

chaos.dennis schrieb:

Die Länge zu ermitteln ist trivial, EventID ist als getter da. Ich würde das dann so aufbauen: Server → TCP → | size_t len, int id, char *data | → TCP → Client

Ansonsten ist das schon schöner, richtig 😉 Btw habe ich 2min bevor ich das Post gesehen habe überlegt, ob ich wieder auf die malloc-free-Sache zurückgehe und einfach "damit lebe".

LG Glocke

chaos.dennis

Anmeldungsdatum:
8. November 2011

Beiträge: 42

Glocke schrieb:

chaos.dennis schrieb:

  • Kein Vector/liste etc möglich

  • Keine dynamisch langen Strings

  • Feste feldgröße für die Daten

Eingangs hatte ich glaube erwähnt, dass sowas unter "Grenzen des Frameworks" fällt ☺

Okay, also ist die Lösung weder einfach noch ausbaufähig? ☺.

chaos.dennis schrieb:

  • memcopy und co. provuzieren fehler und schlampereien (zummindest habe ich bei mir diese erfahrung gemacht ☺ )

Okay das ist mir neu ... ^^

Glaub mir, nimm stl container und Object referenzen wo es geht und dein code wird sauberer und fehlersicherer.

chaos.dennis schrieb:

Ist hier doch schon fertig (gewesen):

1
void TcpLink::send(void* data, std::size_t len) 

Davon war ich abgegangen, weil ich dann den Speicher nur mit malloc allokieren kann und ihn somit mit free freigeben muss. Beabsichtigt war von mir aber, dass alle Events (egal ob sie vom Netzwerk gelesen oder vom System so generiert wurden) mit delete zu löschen sein sollen.

Wieso dass? Der buffer existeirt ja nur für den "export". Ginge es nicht nach diesem Schema?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

void Link::send(EventBase *e)
{ 
  string data = e->toString()
  send<size_t>(data.size())
  send<int>(e->getId())
  send(data.c_str(), data.size()) 
}

EventBase *Link::recive()
{
  ... // das von oben empfangen
  EventBase *eb = EventFactory::assemble(id, tcpbuffer);
  delete[] tcpbuffer;
  return eb;
}

Gut möglich, dass bei der Größenberechnung noch mit sizeof char * multipliziert werden muß. Optimieren kann man es sicher auch so, dass der komplette Datenblock in einen send Aufruf gepackt wird...

Gruß,

Dennis

chaos.dennis

Anmeldungsdatum:
8. November 2011

Beiträge: 42

Moin,

chaos.dennis schrieb:

  • memcopy und co. provuzieren fehler und schlampereien (zummindest habe ich bei mir diese erfahrung gemacht ☺ )

Okay das ist mir neu ... ^^

Nur um es zu verdeutlichen...

1
2
3
    Test(std::string text): Event(TEST) {
        memcpy(this->text, text.c_str(), 255);
    }

std::strings sind beliebig lang. C-Strings auch, also std::string.c_str(). C-Strings sind "primitiv". Es ist ein char pointer auf ein array mit einem '\0' am ende. D.h. dass array wird so lange gelesen bis '\0' kommt, wenn es nicht kommt gibt es einen netten Speicherzugriffsfehler. Deswegen mußt du ja auch bei dem void * die Länge mit geben, weil es keine Konvention gibt, die sagt, dass es so lange geht bis '\0' kommt. Du kopierst jetzt munter diese dynamischen strings auf ein char array. Wenn dein quell string jetzt länger als 255 ist schneidest du einfach das '\0' ab. Es ist als kein C-String mehr, sondern ein char * array ohne definierte Größe. Behandelst du es weiterhin wie einen C-String wird es eben knallen.

Ist jetzt aus dem Kopf geschrieben, wenn du es genau wisssen willst gibts jede menge stoff ☺.

Stl container minimieren diese Gefahr beträchtlich.

Gruß,

Dennis

Antworten |