Networking-Framework für SDL-Projekt



  • Hallo, ich hätte gern zu Folgendem etwas Feedback von Euch 🙂

    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


  • Mod

    -was genau ist die zielgruppe? (der grund weshalb es sowas generisches nicht gibt es dass sowas sehr verbunden ist mit den restlichen systemen, bei netzwerk code sind subtile dinge oft die entscheidenen. und, RTS, FPS, Sims etc. haben alle andere anforderungen).
    -ist kompressions schon eingebaut?
    -welche architektur supportest du? (client<->client,client<->server,...?)
    -ist die reihenfolge garantiert bei allen?
    -wie schaut die lib aus in vergleich zu anderen wie z.b. RakNet? (gibt es was, was besonders gut ist, etwas, was fehlt aber noch reinkommt?)
    -support von low priority dingen ala file transfer/streaming?
    -support auf router konfiguration?



  • Du verwendest Exceptions, aber kein RAII. Deswegen hat deine Software sehr viele Ressourcenlecks.
    Du verwendest malloc und gibts den rohen Zeiger dann einfach an den Benutzer zurück ( void* UdpLink::receive() ), welcher dann irgendwie herausfinden muss, dass er den Speicher mit free wieder freigeben muss und nicht etwa mit delete[] . Dabei prüfst du nicht den Rückgabewert von malloc auf Null bevor du da reinschreibst.
    Du nimmst an, dass int überall das gleiche Layout hat. Das fliegt dir plötzlich um die Ohren, wenn Computer mit verschiedener Endianness kommunizieren sollen.
    Du verwendest verschiedene Typen für Längen in Bytes: Mal int , mal unsigned long , mal implizit size_t (das ist der Typ des Arguments von malloc ).
    Du schreibst Fehlermeldungen manchmal in die Standardausgabe, anstatt sie einer Exception mitzugeben.



  • Der Eventklasse eine virtuelle string serialize() und bool deserialize(string) geben würde den Events Pointer und Container erlauben.



  • Hi, erstmal danke für eure Beiträge 🙂

    nwp3 schrieb:

    Der Eventklasse eine virtuelle string serialize() und bool deserialize(string) geben würde den Events Pointer und Container erlauben.

    Der Einfachheit halber habe ich darauf verzichtet. Aber der Ansatz ist auf jeden Fall interessant!

    TyRoXx schrieb:

    Du verwendest Exceptions, aber kein RAII. Deswegen hat deine Software sehr viele Ressourcenlecks.

    Oh, da hast du verdammt recht! Hast du da einen Vorschlag? Prinzipiell sollte es ja reichen jeden (durch die aktuelle Funktion reservierten Speicher) freizugeben, bevor ich schließlich die Exception werfe, oder?

    TyRoXx schrieb:

    Du verwendest malloc und gibts den rohen Zeiger dann einfach an den Benutzer zurück ( void* UdpLink::receive() ), welcher dann irgendwie herausfinden muss, dass er den Speicher mit free wieder freigeben muss und nicht etwa mit delete[] . Dabei prüfst du nicht den Rückgabewert von malloc auf Null bevor du da reinschreibst.

    Zugegebene: das ist eine Schwäche meinerseits. Diesem Problem war ich mir bisher nicht bewusst. Wie würdest du an der Stelle arbeiten?
    Was mir einfällt, wäre mit ::operator new(len) zu arbeiten (und natürlich auf NULL zu prüfen vor dem Schreiben 😃 )

    /EDIT: Ich glaube ich ziehe das Senden der Länge und der Daten mal auseinander, so dass die Senden- und Empfangen-Methode der von Link abgeleiteten Klassen mit Typparameter arbeiten und ich somit mit new Speicher anfordern kann.

    TyRoXx schrieb:

    Du nimmst an, dass int überall das gleiche Layout hat. Das fliegt dir plötzlich um die Ohren, wenn Computer mit verschiedener Endianness kommunizieren sollen.
    Du verwendest verschiedene Typen für Längen in Bytes: Mal int , mal unsigned long , mal implizit size_t (das ist der Typ des Arguments von malloc ).

    Hab ich das echt noch gemacht? Verdammte Axt -.- Eigentlich wollte ich das vermeiden 😃 Danke für den Hinweis!

    TyRoXx schrieb:

    Du schreibst Fehlermeldungen manchmal in die Standardausgabe, anstatt sie einer Exception mitzugeben.

    Uups xD Thx 🙂

    rapso schrieb:

    -was genau ist die zielgruppe? (der grund weshalb es sowas generisches nicht gibt es dass sowas sehr verbunden ist mit den restlichen systemen, bei netzwerk code sind subtile dinge oft die entscheidenen. und, RTS, FPS, Sims etc. haben alle andere anforderungen).

    Ich arbeite an einem Multiplayer-RPG mit isometrischer Darstellung. Der Haupteinsatzzweck soll der Einsatz dort sein. Zusätzlich hatte ich mit Clint Bellanger (falls den jemand kennt^^) geschrieben (er arbeitet an der Singleplayer-RPG Engine "FLARE") und ihm angeboten, dass er ggf. den Code irgendwann bei sich integrieren kann, wenn er mal auf Multiplayer-Support umsteigen will.

    rapso schrieb:

    -ist kompressions schon eingebaut?

    Nein, noch nicht.

    rapso schrieb:

    -welche architektur supportest du? (client<->client,client<->server,...?)

    Das zweite Beispielprogramm implementiert einen Server mit n Clients. Aber Prinzipiell wäre auch Host-to-Host möglich, wenn man auf der NetworkingQueue Klasse aufbauend eine ähnliche Architektur implementiert. Die Idee finde ich gar nicht so schlecht 😛

    rapso schrieb:

    -ist die reihenfolge garantiert bei allen?

    Was meinst du damit genau?

    rapso schrieb:

    -wie schaut die lib aus in vergleich zu anderen wie z.b. RakNet? (gibt es was, was besonders gut ist, etwas, was fehlt aber noch reinkommt?)

    Ich muss ganz ehrlich sagen, dass ich mir andere Libs nicht ins Detail angeschaut habe. Von daher kann ich nur darüber sprechen, was ich zukünftig noch geplant habe (siehe "Scheduled Changes" in der README).

    rapso schrieb:

    -support von low priority dingen ala file transfer/streaming?

    Das habe ich bisher nicht vorgesehen

    rapso schrieb:

    -support auf router konfiguration?

    Auch hier muss ich fragen: Was genau meinst du? 😃

    Ich werde je nach Zeit mal die genannten Probleme angehen.

    LG Glocke



  • Hallo zusammen,

    ich habe noch einige Dinge verbessert und umgebaut. Wäre cool wenn mir jemand noch Hinweise wegen Memory Leaks geben könnte. Ich arbeite zwar mit Valgrind, aber manchmal werde ich aus dem Meldungen auch nicht ganz schlau. Blöderweise habe ich jetzt gerade keine explizite Meldung da zum fragen ... vielleicht hat jemand ein Auge für sowas 🙂

    LG Glocke

    /EDIT: Doch, einen konkreten Fall im Bezug auf die example1.cpp habe ich hier:

    ==13560== Thread 4:
    ==13560== Syscall param socketcall.send(msg) points to uninitialised byte(s)
    ==13560==    at 0x042e9808: send (socket.S:100)
    ==13560==    by 0x040ee166: SDLNet_TCP_Send (in /usr/lib/i386-linux-gnu/libSDL_net-1.2.so.0.0.7)
    ==13560==    by 0x0804b25c: TcpLink::send_ptr(void*, unsigned int) (connection.hpp:131)
    ==13560==    by 0x0804acf0: trigger_sender(void*) (eventsystem.cpp:24)
    ==13560==    Address 0x4c723f9 is 257 bytes inside a block of size 258 alloc'd
    ==13560==    at 0x0402b9b4: operator
    ==13560==    by 0x0804d3a8: tcp_demo() (example1.cpp:71)
    ==13560==    by 0x0804d69c: main (example1.cpp:181)
    ==13560==    Uninitialised value was created by a heap allocation
    ==13560==    at 0x0402b9b4: operator
    ==13560==    by 0x0804d3a8: tcp_demo() (example1.cpp:71)
    ==13560==    by 0x0804d69c: main (example1.cpp:181)
    ==13560==
    

    Liegt der Fehler an mir? Wenn ja wo .. 😃



  • Hallo,

    in den letzten Wochen habe ich das Framework ein ganzes Stück umkonzipiert - auch basierend auf einigen der hier angesprochenen Probleme:

    Kommunikation auf Basis von JSON

    • Daten werden in JSON-Objekten gesammelt und können serialisiert / deserialisiert werden. Die gängigen Container können in JSON-Objekte abgebildet werden.
    • Einige Speicherlecks beim Werfen von Exceptions wurden behoben. (aber sichtlich noch nicht alle!)
    • Beim Lesen der JSON-Objekte wird nicht mehr mit malloc und void-Pointern gearbeitet. Statt dessen wird auf das Mittel der Deserialisierung zurückgegriffen und ein neues JSON-Objekt erstellt, das mittels delete gelöscht werden kann. Dadurch wurden einige Speicherlecks behoben.
    • Probleme aufgrund verschiedener Größen von Datentypen wurden durch (die von C++11 gegebenen) Datentypen mit fester Länge (z.B. std::uint32_t - siehe http://en.cppreference.com/w/cpp/types/integer) eingedämmt. Soweit ich es überblicke, habe ich bereits das ganze Framework auf diese Typen umgebaut - oder fast das ganze ^^
    • Weitere plattformspezifische Probleme wurden durch die Serialisierung / Deserialisierung verringert. Mir ist derzeit erstmal kein weiteres Problem bewusst - ich bin aber offen für neue Probleme 😃
    • Komprimierung ist bisher noch nicht eingebaut. Da aktuell aber serialisierte JSON-Objekte als Zeichenkette versandt werden, erscheint ein nachträgliches Implementieren einer Komprimierung bzw. Dekomprimierung recht einfach.
    • Bislang wird ausschließlich die Client-Server-Architektur unterstützt. Wenn jemand Interesse an einer Peer-to-Peer-Implementierung hat, kann ich da ggf. etwas bauen. Hat jemand eine Peer-to-Peer-Implementierung auf Basis des Frameworks, bin ich gerne bereit mir die anzuschauen und ggf. hinzuzufügen.
    • Es gibt aktuell genau eine Beispiel-Anwendung: Ein Konsolen-Chatroom. Siehe https://github.com/cgloeckner/networking

    Es würde mich freuen, wenn der ein oder andere das Framework mal etwas auf den Zahl fühlen möchte. Für Fehler/Probleme/Anregungen/Kritik und Lob stehe ich gern zur Verfügung 🙂

    Falls ich Eure Fragen noch nicht (vollständig) beantwortet habe, dann fragt bitte ruhig nochmal 🙂

    LG Glocke

    PS: Kann jemand das "event-based" aus dem Titel nehmen? 😃 Das kann ich ja sogar selber 🙂



  • *push in der Hoffnung auf weiteres Feedback*



  • ohn es mir angeschaut zu haben: hast du dir mal boost::mpi angeschaut? MPI löst ähnliche Probleme, aber da kannst du dir zum Beispiel anschaueen, wie du deine srialisierung ordentlich hinkriegst.

    Ich verstehe nicht, warum du JSON verwendest? tut das not?



  • otze schrieb:

    Ich verstehe nicht, warum du JSON verwendest? tut das not?

    Ich verstehe nicht, warum nicht JSON. Es war die erste Wahl: es ist übersichtlich, verbreitet und hat imho geringeren Overhead als XML.


  • Mod

    wenn du eh nur primitive verschickst, wieso schickst du die daten nicht einfach als raw packet?



  • rapso schrieb:

    wenn du eh nur primitive verschickst, wieso schickst du die daten nicht einfach als raw packet?

    (1) "Zukunftssicherheit" - wenn ich irgendwann mehr als Primitive geschicken will, habe ich "vorgesorgt".
    (2) Little Endian vs. Big Endian vs. weitere exotische Bytereihenfolgen.



  • Dazu muesstest du das Jetzt erstmal in den Griff bekommen: Robustheit vs Fehler. Weiterhin muss auf beiden Seiten (Client/Server) serialisiert und deserialisiert werden, das kostet Performance und ist ein mehr an Aufwand im Vergleich zum einfachen Verwenden des Integeres in binaer. Auch mag Genauigkeit bei Fliesskommazahlen verloren gehen, ... Alles hat Vor- und Nachteile. Und wenn du auf Zukunftsicherheit setzen moechtest, dann suche dir eine andere Bibliothek oder mache alles gleich mit der POSIX-Api.



  • knivil schrieb:

    Weiterhin muss auf beiden Seiten (Client/Server) serialisiert und deserialisiert werden, das kostet Performance und ist ein mehr an Aufwand im Vergleich zum einfachen Verwenden des Integeres in binaer.

    Das ist richtig. Mit diesem Mehraufwand kann man entweder leben oder nicht. Jedes Framework hat seine Einsatzgebiete und seine Grenzen. Die Grenzen meines Ansatzes liegen klar bei der Performance: für ein MMORPG wird die Implementierung nicht taugen - für ein "kleines" Spiel (mit z.B. 5-6 Spielern) sollte es hingegen schon funktionieren.

    knivil schrieb:

    Und wenn du auf Zukunftsicherheit setzen moechtest, dann suche dir eine andere Bibliothek oder mache alles gleich mit der POSIX-Api.

    Ich hatte "Zukunftssicherheit" absichtlich in "-Zeichen gesetzt; was ist in der Informatik schon zukunftssicher 😃 Ich meinte damit viel mehr, dass es auf spätere Änderungen möglichst flexibel reagiert.

    Die genauen Gründe für die Verwendung eines Serialisierungsansatzes habe ich in der Readme auf GitHub ausformuliert: https://github.com/cgloeckner/networking#why-serialization

    LG Glocke


Anmelden zum Antworten