C++ Server Paket Entwurfsmuster

12/07/2013 15:46 EasyTale#1
Hi,

ich bin gerade am überlegen wie ein Entwurfsmuster meiner Paket Klasse die für einen Game Server fungieren soll aussehen könnte.

PacketBuffer (Buffer für die empfangenen Pakete bzw. zum senden)
PacketFilter (Unbekannte Pakete etc. filtern)
PacketHandler (Pakete parsen und Callback Funktionen aufrufen z.B. wenn es sich um das Login Packet handelt)
PacketLog (Zum protokollieren der Pakete in eine .txt Datei)
PacketEncoding (Dient dazu um die Pakete zu verschlüsseln / entschlüsseln)

Mein Gedanke währe jetzt folgender:

Receive Packet:
Session -> PacketBuffer -> PacketEncoding -> PacketLog -> PacketFilter -> PacketHandler -> Klasse die das Packet handled

Send Packet:
Session -> PacketBuffer -> PacketEncoding -> PacketLog

Passt das so?
Außerdem muss ich noch irgendwo die Packete für den PacketHandler registrieren und den dazugehörigen Callback binden.

Bei dem Server habe ich das mehr oder weniger von den boost examples übernommen.

Klasse: Server
Startet die Servercore und beginnt neue Verbindungen anzunehmen.
Bei einer neuen Verbindung wird ein shared Objekt der Klasse 'Session' erstellt und in den Parametern das Socket übergeben sowie eine Referenz der Klasse SessionList.

Klasse: Session
Leitet von der Klasse Client sowie der Klasse std::enable_shared_from_this<T> ab.
Im Konstruktor wird der Socket und eine Referenz der SessionList erwartet, beide werden als Member gespeichert.
Außerdem wird die Session in die SessionList hinzugefügt und bei Verbindungsabbrüchen etc. wieder entfernt.

Klasse: Client
Enthält pure virtuelle Schnittstellen z.B. send.

Klasse: SessionList
Enthält virtuelle Funktionen für das sammeln von shared Client Objekten in einem Container und weitere virtuelle Funktionen.

Btw. In die Session Klasse würde ich alle Header wie Character, Account etc. packen und für alles eine Membervariable erstellen. Das Problem an der Sache ist aber, das die Session ja eig. nur einen indirekten Bezug zu dem Zeug wie z.B. Character hat und es somit bad practice ist, oder nicht?

Ansonsten schönes Wochenende euch noch. :)
12/07/2013 16:05 Omdi#2
Sieht soweit in Ordnung aus, nur verstehe ich den Unterschied bzw. den Sinn von PacketFilter und PacketHandler nicht.

Quote:
PacketFilter (Unbekannte Pakete etc. filtern)
PacketHandler (Pakete parsen und Callback Funktionen aufrufen z.B. wenn es sich um das Login Packet handelt)
Das kann man doch auch vereint in den PacketHandler packen :O
12/07/2013 17:19 EasyTale#3
Quote:
Originally Posted by Omdihar View Post
Sieht soweit in Ordnung aus, nur verstehe ich den Unterschied bzw. den Sin von PacketFilter und PacketHandler nicht.



Das kann man doch auch vereint in den PacketHandler packen :O

Oh, stimmt, ich weiß jetzt auch nicht wie ich darauf gekommen bin.

Quote:
Btw. In die Session Klasse würde ich alle Header wie Character, Account etc. packen und für alles eine Membervariable erstellen. Das Problem an der Sache ist aber, das die Session ja eig. nur einen indirekten Bezug zu dem Zeug wie z.B. Character hat und es somit bad practice ist, oder nicht?
Weißt du vielleicht auch hier was zu?
12/07/2013 17:24 MrSm!th#4
Quote:
Leitet von der Klasse Client sowie der Klasse std::enable_shared_from_this<T> ab.
Sicher, dass du Shared-Pointer oder überhaupt Pointer brauchst?

Quote:
Enthält virtuelle Funktionen für das sammeln von shared Client Objekten in einem Container und weitere virtuelle Funktionen.
Warum kein std::vector innerhalb der Server-Klasse?

Quote:
In die Session Klasse würde ich alle Header wie Character, Account etc. packen und für alles eine Membervariable erstellen. Das Problem an der Sache ist aber, das die Session ja eig. nur einen indirekten Bezug zu dem Zeug wie z.B. Character hat und es somit bad practice ist, oder nicht?
Kannst du das näher erläutern? Was genau willst du alles in der Session haben und wieso?
12/07/2013 18:29 EasyTale#5
Quote:
Originally Posted by MrSm!th View Post
Sicher, dass du Shared-Pointer oder überhaupt Pointer brauchst?
boost::asio::ip::tcp::socket ist noncopyable und es gäbe noch andere Probleme.
Deswegen nutze ich shared Pointer.

Quote:
Originally Posted by MrSm!th View Post
Warum kein std::vector innerhalb der Server-Klasse?
Ich könnte einen std::vector(ich glaube std::set eignet sich besser?) mit shared Pointern anlegen, allerdings gibt es im Spiel auch unterschiedliche Maps mit Spielern, dort würde ich dann einfach von der SessionList ableiten.
Die SessionList bietet ja dann die Funktion an allen eingetragenen Sessions in der Liste ein Paket zu senden.
Das mit der Map ist allerdings nicht das einzigste.
Bei einem std::vector müsste ich dann nochmal überall die nötigen Funktionen extra implentieren.

Quote:
Originally Posted by MrSm!th View Post
Kannst du das näher erläutern? Was genau willst du alles in der Session haben und wieso?
Im Game Server werden die Accountdaten und eine Sessionid empfangen, diese müssten nochmal geprüft werden (Sessionid erstellt der Login Server).
D.h. ich brauch schonmal eine Klasse 'Accounts' oder so ähnlich.
Es besitzt auch jeder Account einen oder mehrere Charactere diese verfügen ein Inventar, Equipment, Statuswerte etc.
In der Account Klasse würde ich jetzt alle anderen Header wie die Character Klasse includen und anschließend in der Session.h die Account.h includen.
Mein vorhaben war dann, in der Session Klasse die Account Klasse als Member zu deklarieren.
Der PacketHandler called dann später die Callback Funktion wo das zerpflückte Packet übergeben wird und einen Pointer von der Session.
Dort wird dann alles gehandled und die getters/setters aufgerufen indem dann über dem Session Pointer auf die Eigenschaften wie z.B. CharacterInventory zugegriffen wird.
Nur bin ich jetzt etwas verunsichert, weil ich das Gefühl habe es ist ein unschöner Weg?
12/07/2013 20:52 MrSm!th#6
Quote:
Originally Posted by EasyTale View Post
boost::asio::ip::tcp::socket ist noncopyable und es gäbe noch andere Probleme.
Deswegen nutze ich shared Pointer.
Warum keine Referenzen? Die sind grundsätzlich zu bevorzugen. Damit meine ich deine gesamte Session. Die kannst du ebenfalls Single-Instance-mäßig gestalten, dann muss auch nichts kopiert werden.

Quote:
(ich glaube std::set eignet sich besser?)
Wieso?
Die Frage meine ich ernst, so fit bin ich auch nicht, was die Standard-Container angeht :o
Quote:
mit shared Pointern anlegen, allerdings gibt es im Spiel auch unterschiedliche Maps mit Spielern, dort würde ich dann einfach von der SessionList ableiten.
Um diesen Gedankengang nachvollziehen zu können, müsste man mehr über dein Gesamtdesign wissen. Alleine aus dem Netzwerk-Design erschließt sich das mir jetzt nicht.

Quote:
Im Game Server werden die Accountdaten und eine Sessionid empfangen, diese müssten nochmal geprüft werden (Sessionid erstellt der Login Server).
D.h. ich brauch schonmal eine Klasse 'Accounts' oder so ähnlich.
Es besitzt auch jeder Account einen oder mehrere Charactere diese verfügen ein Inventar, Equipment, Statuswerte etc.
In der Account Klasse würde ich jetzt alle anderen Header wie die Character Klasse includen und anschließend in der Session.h die Account.h includen.
Mein vorhaben war dann, in der Session Klasse die Account Klasse als Member zu deklarieren.
Ok, das klingt erstmal sinnig. Je nach Design könnte natürlich auch eine Art Player-Klasse einen Verweis auf die aktive Session haben, die die nötigen Handler in der Session registriert.
Quote:
Der PacketHandler called dann später die Callback Funktion wo das zerpflückte Packet übergeben wird und einen Pointer von der Session.
Auch hier gilt: Wenn du keinen Grund hast, der explizit für einen Pointer spricht, würde ich eine Referenz nehmen.
Quote:
Dort wird dann alles gehandled und die getters/setters aufgerufen indem dann über dem Session Pointer auf die Eigenschaften wie z.B. CharacterInventory zugegriffen wird.
Du könntest auch Funktionsobjekte als Callbacks (ich gehe mal fast davon aus, dass du stinknormale Function-Pointer verwendest; siehe std::function) verwenden, dann kannst du einfach eins registrieren, dass selbst den entsprechenden Account/Character/etc. kennt, dann braucht die Session den nicht zu kennen.
Quote:
Nur bin ich jetzt etwas verunsichert, weil ich das Gefühl habe es ist ein unschöner Weg?
Nunja, da wo Abhängigkeit bestehen muss, da ist sie letztendlich auch unumgänglich und kann nicht Bad Practice sein. Irgendwie muss eine Session nunmal zum Player/Account gehören. In welche Richtung das geht, hängt letztendlich auch vom restlichen Design ab, man kann nicht grundsätzlich einen Weg als falsch darstellen.
12/07/2013 23:00 Omdi#7
Für das mit der Wahl der Standart Container habe ich hier ein schönes Bild :)

12/08/2013 13:03 EasyTale#8
Quote:
Warum keine Referenzen? Die sind grundsätzlich zu bevorzugen. Damit meine ich deine gesamte Session. Die kannst du ebenfalls Single-Instance-mäßig gestalten, dann muss auch nichts kopiert werden.
Schon, aber dann müsste ich die auch irgendwie ordnungsgemäß aus den Containern löschen. Und was passiert wenn die Referenz out of scope läuft?
Außerdem muss ich ja noch die Referenzen in einem Container bekommen, was ja bei noncopyable Objekten problematisch werden kann?

Quote:
Wieso?
Die Frage meine ich ernst, so fit bin ich auch nicht, was die Standard-Container angeht :o
std::set (sortierter Container) eignet sich besser um Inhalte mitten im Container hinzuzufügen oder zu entfernen.
std::vector (unsortierter Container) eignet sich besser zum iterieren der Objekte und oder Objekte am Ende hinzuzufügen oder zu entfernen.
Da dauernd neue Verbindungen hinzukommen oder mitten im Container entfernt werden, gehe ich davon aus das std::set schneller ist. (Ein unsortierter std::vector ist zum suchen sowieso eher ungeeignet)
Was genau dort passiert will ich vorsichtshalber nicht erwähnen, da ich selber nur eine grobe Vorstellung habe.^^
Wann man welchen Container (davon gibt es echt 'nen dutzend :D) verwenden sollte, habe ich aus einer Liste entnommen.

Quote:
Um diesen Gedankengang nachvollziehen zu können, müsste man mehr über dein Gesamtdesign wissen. Alleine aus dem Netzwerk-Design erschließt sich das mir jetzt nicht.
Das Problem ist hier, dass ich selber noch keine genaue Vorstellungen über das Gesamtdesign gemacht habe. Ich dachte anfangs, dass es sich schon ergeben wird nachdem mein Netzwerk Design steht.

Quote:
Ok, das klingt erstmal sinnig. Je nach Design könnte natürlich auch eine Art Player-Klasse einen Verweis auf die aktive Session haben, die die nötigen Handler in der Session registriert.
Auch hier gilt: Wenn du keinen Grund hast, der explizit für einen Pointer spricht, würde ich eine Referenz nehmen.
Könntest du darauf noch etwas eingehen?

Quote:
Du könntest auch Funktionsobjekte als Callbacks (ich gehe mal fast davon aus, dass du stinknormale Function-Pointer verwendest; siehe std::function) verwenden, dann kannst du einfach eins registrieren, dass selbst den entsprechenden Account/Character/etc. kennt, dann braucht die Session den nicht zu kennen.
Nene, ich verwende schon std::function, über normale Funkionspointer währe mir das zu umständlich.
Die Idee hört sich ziemlich gut an, danke.

Quote:
Nunja, da wo Abhängigkeit bestehen muss, da ist sie letztendlich auch unumgänglich und kann nicht Bad Practice sein. Irgendwie muss eine Session nunmal zum Player/Account gehören. In welche Richtung das geht, hängt letztendlich auch vom restlichen Design ab, man kann nicht grundsätzlich einen Weg als falsch darstellen.
Da wüsste ich jetzt auch nicht, was ich hinzuzufügen hatte.


Quote:
Originally Posted by Omdihar View Post
Für das mit der Wahl der Standart Container habe ich hier ein schönes Bild :)



Funktioniert leider bei mir nicht. :p
12/08/2013 14:24 Tasiro#9
Quote:
Originally Posted by EasyTale View Post
std::set (sortierter Container) eignet sich besser um Inhalte mitten im Container hinzuzufügen oder zu entfernen.
std::vector (unsortierter Container) eignet sich besser zum iterieren der Objekte und oder Objekte am Ende hinzuzufügen oder zu entfernen.
Da dauernd neue Verbindungen hinzukommen oder mitten im Container entfernt werden, gehe ich davon aus das std::set schneller ist. (Ein unsortierter std::vector ist zum suchen sowieso eher ungeeignet)
Was genau dort passiert will ich vorsichtshalber nicht erwähnen, da ich selber nur eine grobe Vorstellung habe.^^
Wann man welchen Container (davon gibt es echt 'nen dutzend :D) verwenden sollte, habe ich aus einer Liste entnommen.
Müssen die Verbindungen sortiert sein? Wenn nicht, könntest du dir std::unordered_set ansehen, das ist im durchschnittlichen Fall besser.
12/08/2013 15:16 EasyTale#10
Quote:
Originally Posted by Tasiro View Post
Müssen die Verbindungen sortiert sein? Wenn nicht, könntest du dir std::unordered_set ansehen, das ist im durchschnittlichen Fall besser.
Quatsch, die müssen nicht sortiert sein, zumindest wüsste ich jetzt selber nicht wofür.
Ich werde es mir aufjedenfall anschauen, danke.

Ich mache mir derzeit noch zuviele Gedanken über das ganze Design.
Mr.Smith hat mir ja zu Referenzen geraten, welche ich auch bevorzugen würde.
Da boost::asio::ip::tcp::socket aber noncopyable ist, sehe ich einige Probleme.

Quote:
Schon, aber dann müsste ich die auch irgendwie ordnungsgemäß aus den Containern löschen. Und was passiert wenn die Referenz out of scope läuft?
Außerdem muss ich ja noch die Referenzen in einem Container bekommen, was ja bei noncopyable Objekten problematisch werden kann?
Die stl Container sind darauf ausgelegt, dass der Kontent copyable ist.
Es gibt ja std::reference_wrapper aber das kann nicht die Lösung sein.