Hochschule RheinMain Fachbereich Design Informatik Medien Studiengang Medieninformatik Abschlussarbeit zur Erlangung des akademischen Grades Bachelor of Science (B.Sc.) Echtzeitapplikationen im Web-Kontext am Beispiel eines Multiplayer-Spiels Vorgelegt von am Referent Korreferent Wasili Adamow 04. Oktober 2013 Prof. Dr. Jörg Berdux Prof. Dr. Wolfgang Weitz 2 3 Erklärung gemäß ABPO Ich erkläre hiermit, • dass ich die vorliegende Abschlussarbeit selbstständig angefertigt, • keine anderen als die angegebenen Quellen benutzt, • die wörtlich oder dem Inhalt nach aus fremden Arbeiten entnommenen Stellen, bildlichen Darstellungen und dergleichen als solche genau kenntlich gemacht und • keine unerlaubte fremde Hilfe in Anspruch genommen habe. Wiesbaden, 04. Oktober 2013 Wasili Adamow Erklärung zur Verwendung der Bachelor Thesis Hiermit erkläre ich mein Einverständnis mit den im folgenden aufgeführten Verbreitungsformen dieser Abschlussarbeit: Verbreitungsform Einstellung der Arbeit in die Hochschulbibliothek mit Datenträger Einstellung der Arbeit in die Hochschulbibliothek ohne Datenträger Veröffentlichung des Titels der Arbeit im Internet Veröffentlichung der Arbeit im Internet Wiesbaden, 04. Oktober 2013 Wasili Adamow Ja Nein × × × × 4 Zusammenfassung Das Internet entwickelt sich immer weiter und übernimmt immer mehr Bereiche des alltäglichen Lebens. Dadurch wandern viele Applikationen, welche man bisher noch lokal installieren musste, in das Netz ab und werden somit für immer mehr Menschen zugänglich. Diese Arten von Diensten bringen viele Vorteile mit sich: Sie sind von überall auf jedem Endgerät erreichbar, speichern zuverlässig Daten ab und laufen so schnell, als wären sie lokal installiert. Jedoch steigt dadurch der Anspruch an die Hardware und an die Technologien immer weiter, wodurch diese sich immer weiterentwickeln müssen - was früher mit HTTP umgesetzt wurde, ist für diese Applikationen schon viel zu langsam. Diese Arbeit soll an einem Beispiel aufzeigen, wie man mit den heutigen Mitteln eine effiziente, verteilte WebApplikation entwickeln kann, welche zudem in Echtzeit auf nahezu jedem Gerät lauffähig ist. 2 Inhaltsverzeichnis 1 Einleitung 5 2 Spielkonzept 7 2.1 Grundlegende Spielmechanik . . . . . . . . . . . . . . . . . . . . . . . 7 2.2 Spielmechanik im Mehrspieler-Modus . . . . . . . . . . . . . . . . . . 9 3 4 5 Echtzeitkommunikation im Web 13 3.1 HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.2 AJAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.3 Websockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.4 WebRTC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.5 Vergleich Websockets, Socket.IO & WebRTC . . . . . . . . . . . . . . . 20 3.6 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Anforderungsanalyse 25 4.1 Benutzerinteraktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 4.2 Spielelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.3 Echtzeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.4 Kompatibilität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Konzept 35 5.1 Gesamtüberblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.2 Netzwerkkommunikation . . . . . . . . . . . . . . . . . . . . . . . . . 37 5.2.1 38 Infrastruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 4 Inhaltsverzeichnis 5.2.2 6 7 Nachrichtentypen . . . . . . . . . . . . . . . . . . . . . . . . . 41 5.3 Datenverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5.4 Struktur der Clients . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 5.5 Ablauf der Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . 52 Implementierung 65 6.1 Auswahl der Technologien . . . . . . . . . . . . . . . . . . . . . . . . . 65 6.2 Netzwerkschicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 6.3 Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 6.4 Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Schlusswort 77 Kapitel 1 Einleitung Das Internet und dessen Funktionalitäten wachsen immer weiter. Während zu Beginn der Entwicklungen vor allem der schnelle Transport von einfachen Textnachrichten im Vordergrund stand, haben sich heutzutage diese Nachrichten soweit entwickelt, dass man mit ihnen alle erdenklichen Daten verschicken kann. Durch die immer besser werdende Hardware und die damit schnellere Übertragung von Daten durch das Netz, etabliert sich seit einigen Jahren auch das Cloud Computing, welches als Grundlage für verschiedene Dienste über das Internet dient. Hierbei werden nach dem National Institute of Standards and Technology (NIST) drei Formen unterschieden: Platform as a Service (PaaS), Infrastructure as a Service (IaaS) und Software as a Service (SaaS) [12]. Neben dem Sichern von Daten im Netz (Cloud Storage), was zum Bereich IaaS zählt, ist vor allem das Auslagern von Software sehr verbreitet und wird von vielen Internetdiensten angeboten - ein Beispiel hierfür sind Textverarbeitungsprogramme. Diese Verschiebung der Dienste in das Netz hat sowohl für die Nutzer als auch für die Anbieter einige Vorteile. Die Nutzer können, ungeachtet der genutzten Geräte, von überall auf ihre Daten zugreifen, diese bearbeiten und wieder abspeichern. Dies ist wiederum auch ein Vorteil für die Anbieter, da diese immer mehr Dienste direkt im Browser anbieten können und somit nicht für jedes System unterschiedliche Software produzieren müssen. Jedoch steigen hierdurch auch die Ansprüche an die Technologien, auf denen diese Dienste basieren. Die Server müssen viel mehr Daten abspeichern, verwalten und anbieten können, ohne dabei an Geschwindigkeit zu verlieren. Auch das Netzwerk muss eine immer größer werdende Datenmenge übertragen können. Zuletzt müssen die verschiedenen Dienste auch auf allen Endgeräten die gleichen Funktionalitäten anbieten, weshalb diese bestimmte Technologien unterstützen müssen. Da all dies in der Praxis meist nicht umsetzbar ist, müssen Kompromisse eingegangen und Alternativen zur Verfügung gestellt werden, damit 5 6 Einleitung möglichst viele Nutzer die Dienste verwenden können. Diese Arbeit befasst sich mit der Thematik von Software im Netz und zeigt dabei am Beispiel eines Multiplayer-Spiels auf, mithilfe welcher Technologien momentan eine Applikation im Web-Kontext umgesetzt werden kann. Hierbei liegt der Fokus auf der Kommunikation in Echtzeit - es werden jedoch auch andere Bereiche der Server und Clients behandelt. Die Entwicklung eines Spiels hat dabei mehrere Vorteile: Zum einen ist es bei einem Spiel einfach, Probanden zu finden und durch diese wertvolle Verbesserungsvorschläge zu erhalten. Dies liegt vor allem daran, dass die Zielgruppe bei einem Spiel sehr breit gestreut ist und auch viele technikaffine Personen einschließt. Zum anderen gibt es nur sehr wenige andere Anwendungsfälle, in denen der Benutzer eine Verzögerung als so störend wahrnimmt, wie bei einem Spiel, in dem teilweise Millisekunden über den Ausgang einer Spielrunde entscheiden können. Dadurch ergibt sich eine optimale Umgebung, in der man eine verteilte Applikation mit dem Fokus auf die Echtzeit implementieren kann. Hierzu wird in dieser Arbeit zuerst die Spielmechanik beschrieben, welche in die Bereiche Einzelspieler- und Mehrspielermodus aufteilt ist. Danach zeigt die Entwicklung der Technologien für die Internetkommunikation auf, wie die beiden heutigen Ansätze für die Echtzeitkommunikation entstanden sind und welche älteren Technologien als Fallback genutzt werden können. Als nächstes werden die durch das Spiel gestellten Anforderungen gesammelt und analysiert, bevor sie im nächsten Schritt zum Konzept umgewandelt werden. Dieses Kapitel ist zuerst in die Bereiche Netzwerk, Server und Client aufgeteilt. Danach werden die dabei entstandenen Informationen verbunden, sodass ein Gesamtkonstrukt entsteht, an dem dann gezeigt wird, wie die einzelnen Komponenten miteinander kommunizieren können. Basierend auf diesem Konzept werden zuerst die verschiedenen Technologien ausgewählt, welche für die Implementierung genutzt werden. Danach folgen kurze Übersichten über die Umsetzungen der einzelnen Bereiche der Applikation. Wie sich die Technologien bewährt haben und was in Zukunft noch bevorsteht, wird im Schlussteil dargestellt. Kapitel 2 Spielkonzept In diesem Kapitel wird die Spielmechanik der Applikation beschrieben, welche in dieser Arbeit beispielhaft umgesetzt wird. Dazu wird zunächst erläutert, wie dieses Spiel aussieht und welche Möglichkeiten der Benutzer hat. Diese Erläuterung bezieht sich zunächst auf die grundlegende Spielmechanik, wie sie bereits in anderen Spielen im Einzelspieler-Modus auftaucht. Danach wird dieses Konzept so erweitert, dass sich daraus ein Mehrspieler-Modus ergibt, in dem mehrere Benutzer parallel zusammen und gegeneinander spielen können. 2.1 Grundlegende Spielmechanik Die Vorlage für das Spiel wurde im Juni 1994 vom japanischen Spieleentwickler Taito Corporation als „Puzzle Bobble“ veröffentlicht [1]. Als Grundlage für das Spiel dient ein rechteckiges Spielfeld, welches in Abbildung 2.1 dargestellt ist. Auf diesem werden vom oberen Spielfeldrand aus mehrere Reihen von verschiedenfarbigen Kugeln in das Spielfeld hineingeschoben. Am unteren Rand befindet sich mittig eine weitere Kugel, welche durch einen Klick auf das Spielfeld auf die oberen Kugeln geschossen werden kann. Der Spieler besitzt einen Kugelvorrat mit maximal fünf Kugeln, welche in einem Zusatzfeld unter dem Spielfeld dargestellt werden. Beim Abschuss einer Kugel fliegt diese immer in Richtung des Punktes los, auf den geklickt wurde. Gelangt die Kugel im Flug entweder an den linken oder rechten Spielfeldrand, so prallt diese ab und ändert die horizontale Richtung. Das geschieht solange, bis die Kugel auf eine andere Kugel oder auf den oberen Spielfeldrand trifft. In beiden Fällen wird sie auf den nächsten freien Platz im Raster gesetzt und ist nun Teil der Kugelreihen. Danach wird überprüft, ob die gesetzte Kugel an eine gleichfarbige Kugelgruppe an7 8 Spielkonzept Abbildung 2.1: Das Spielfeld mit allen relevanten Objekten grenzt, sodass sich nun eine Gruppe von mindestens drei gleichfarbigen Kugeln bildet. Wenn dies der Fall ist, werden alle Kugeln dieser Gruppe vom Spielfeld entfernt. Dabei kann es passieren, dass Kugeln ohne Verbindung zum oberen Spielfeldrand zurückbleiben und somit auf dem Spielfeld schweben. Diese Kugeln werden dann ebenfalls entfernt, wie in Abbildung 2.2 zu sehen ist. Abbildung 2.2: Zunächst wird die Gruppe der gelben Kugeln entfernt, wodurch die drei blauen Kugeln schweben. Diese werden daher als nächstes vom Spielfeld genommen Nachdem das Spielfeld, falls benötigt, aufgeräumt wurde, erhält der Benutzer die nächste Kugel am unteren Spielfeldrand und kann mit dieser das Spiel fortsetzen. Hat der Nutzer mit seiner zuletzt abgeschossenen Kugel keine Gruppe gebildet, so wird eine Kugel vom Vorrat entfernt. Wird die letzte Kugel vom Kugelvorrat verschossen, so wird vom oberen Spielfeldrand eine neue Reihe von Kugeln nachgeschoben. Dann werden mögliche schwebende Kugeln entfernt und der Kugelvorrat Spielkonzept 9 wird wieder mit fünf neuen Kugeln aufgefüllt. Während des Spiels ist es möglich, dass man eine Kugelfarbe komplett vom Spielfeld entfernt, wo-durch diese Farbe nicht mehr neu produziert wird. Dadurch nehmen nachfolgende Kugeln sowohl am unteren Spielfeldrand als auch in den Reihen diese Farbe nicht mehr an. Das sorgt dafür, dass das Spielfeld übersichtlicher wird und man sich dem Ziel des Spiels schrittweise nähern kann. Jedoch hat das auch Auswirkungen auf den Spielverlauf, da für jede entfernte Kugelfarbe eine weitere Reihe beim Verbrauchen des Vorrats hinzugefügt wird. Ziel des Spiels ist es, alle Kugelreihen vom Spielfeld zu entfernen. Dabei erhält man je nach benötigter Zeit oder verschossenen Kugeln unterschiedlich viele Punkte und kann sich so mit anderen Spielern messen. Erreicht man hingegen mit einer Kugel den unteren Rand des Spielfelds, so hat man das Spiel verloren und erhält keine Punkte. 2.2 Spielmechanik im Mehrspieler-Modus Das Spiel soll nun so erweitert werden, dass mehrere Benutzer zusammen und gegeneinander spielen können. Hierzu verbindet der Benutzer sich zunächst mit einem Server und befindet sich danach im Hauptmenü des Spiels, welches in Abbildung 2.3 dargestellt ist. Hier kann er seinen Benutzernamen sehen und ändern und bekommt eine Liste der aktuellen Spiellobbys dargestellt. Aus dieser Liste kann er sich einen Eintrag aussuchen und beitreten oder eine eigene Spiellobby erstellen. Abbildung 2.3: Im Hauptmenü sieht der Benutzer seinen Namen und die aktuellen Spiellobbys 10 Spielkonzept Sobald der Benutzer eine Spiellobby auswählt oder eine eigene Lobby erstellt, gelangt er in das Lobby-Menü, welches in Abbildung 2.4 zu erkennen ist. Hier sieht der Benutzer zunächst den Namen der Lobby und eine Liste der aktuellen Teilnehmer. Unter dieser Liste befindet sich eine Schaltfläche um wieder ins Hauptmenü zu gelangen. Ist der Benutzer der Ersteller der Lobby, so kann er andere Benutzer aus der Lobby entfernen oder auch das Spiel starten. Abbildung 2.4: In der Spiellobby hat der Ersteller die Möglichkeit, andere Spieler zu entfernen und das Spiel zu starten Im Spiel hat jeder Benutzer ein eigenes Spielfeld und sieht zusätzlich noch eine verkleinerte Darstellung der anderen Spielfelder. Dadurch ist es bereits möglich, das Spiel parallel zu nutzen und zu sehen, was auf den anderen Spielfeldern passiert – man kann also parallel spielen. Für die Interaktion zwischen den Spielern sorgen die Extras, welche bei besonders vielen entfernten Kugeln auf dem eigenen Spielfeld erscheinen. Ein Extra ist eine besondere Kugel ohne Farbe, welche beim Entfernen einer angrenzenden Gruppe eingesammelt wird und in das Inventar des aktuellen Benutzers übergeht. Der Benutzer kann dann die einzelnen Extras sequentiell in der Reihenfolge des Einsammelns nutzen und diese entweder auf sich selbst oder andere Teilnehmer des Spiels anwenden. Beim Anwenden verursacht jedes Extra eine Aktion, welche dem jeweiligen Ziel schaden oder helfen kann. Entfernt der Benutzer mit einem Schuss zehn gleichfarbige oder aber fünf schwebende Kugeln, so verwandelt sich eine zufällige andere Kugel auf dem Spielfeld in ein Extra, was durch einen Buchstaben auf der nun farblosen Kugel dargestellt wird. Die verschiedenen Typen der Extras sind in Tabelle 2.1 zu erkennen. Sammelt der Benutzer das Extra durch das Entfernen benachbarter Kugeln ein, so landet es in seinem Inventar, wobei neue Extras immer hinten angefügt werden. Der Benutzer Spielkonzept 11 kann nun das erste Extra durch Drücken einer Spielertaste (1-9) anwenden und so beispielsweise eine Reihe von seinem Spielfeld entfernen und sich helfen oder einem Mitspieler eine Reihe hinzufügen und diesen behindern. Dabei unterscheiden sich die einzelnen Extras in der Art, wie sie wirken. Manche Extras wirken direkt, während andere eine gewisse Wirkdauer haben, über welche der jeweilige Effekt anhält. Letztere sind mit der jeweiligen Dauer in der Tabelle vermerkt. Symbol A R C F S Bedeutung Reihe hinzufügen Reihe entfernen Steuerung verdrehen Schnellere Kugel Langsamere Kugel Effekt Neue Kugelreihe von oben Oberste Kugelreihe wird entfernt Steuerung 10s lang invertiert Kugel 10s lang doppelt so schnell Kugel 10s lang halb so schnell Tabelle 2.1: Die einzelnen Extras mit ihren jeweiligen Effekten Der Oberfläche müssen nun weitere Elemente hinzugefügt werden, um so die neuen Funktionalitäten darstellen zu können, wie in Abbildung 2.5 zu sehen ist. Neben den anderen Spielfeldern und dem Inventar des Spielers werden außerdem noch aktive Extras mit der verbleibenden Dauer angezeigt. Im abgebildeten Fall wurde die Steuerung des Benutzers verdreht („C“-Extra) und die Kugelgeschwindigkeit erhöht. Durch das zweite „C“-Extra wird die Steuerung wieder richtiggestellt, solange beide Extras aktiv sind. Nach fünf Sekunden würde der Benutzer also wieder eine verdrehte Steuerung für die verbleibenden drei Sekunden erhalten. Diese Regeln bilden die Grundlage für die Applikation und werden in den folgenden Kapiteln analysiert, erweitert und umgesetzt. 12 Spielkonzept Abbildung 2.5: Die Oberfläche wurde um die Spielfelder der anderen Benutzer, das Inventar und aktive Extras erweitert Kapitel 3 Echtzeitkommunikation im Web Benutzer wollen Anwendungen, welche ohne merkbare Verzögerung auf Eingaben reagieren und passende Ausgaben liefern, weshalb das Arbeiten in Echtzeit eine grundlegende Anforderung an viele Anwendungen ist. Sobald diese Anwendungen über mehrere Systeme und Benutzer verteilt werden, muss auch die Kommunikation zwischen den einzelnen Stellen so verzögerungsarm verlaufen, dass die einzelnen Benutzer in ihrem Arbeitsfluss nicht behindert werden. Doch vor allem im WebUmfeld war die ursprüngliche Entwicklung nicht darauf ausgelegt, dass Benutzer in Echtzeit miteinander kommunizieren können - ein Beispiel hierfür ist das in dieser Arbeit thematisierte Mehrspieler-Spiel. Deshalb gab es in den letzten Jahren vermehrt Technologien, welche diese Lücke schließen sollten. Diese Technologien bilden die Grundlage für die Entwicklung von Echtzeit-Anwendungen im WebKontext und sollen deshalb aufgezeigt werden. 3.1 HTTP Als Grundlage für die Kommunikation im Internet dient das Hypertext Transfer Protocol (HTTP) derzeit in der Version 1.1. Auf Basis dieses Standards kommunizieren auch heutzutage noch Browser, Server und Web-Applikationen miteinander – "HTTP is the common language of the modern global Internet."[8]. HTTP selbst ist ein Protokoll, welches eine zuverlässige Datenübertragung sicherstellt. Es baut auf dem bereits vorhandenen Protokoll TCP auf, welches wiederum über IP und die darunterliegende Netzzugriff-Schicht eine Verbindung herstellen. Durch die Nutzung dieser Elemente entsteht eine zuverlässige Verbindung, über welche die Applikationen kommunizieren können. 13 14 Echtzeitkommunikation im Web Abbildung 3.1: Die Kommunikation zwischen dem Client und dem Server mittels HTTP per Request und Response Für die Kommunikation schickt der Client eine Anfrage (Request) mit einer Methode, einem Ziel und der genutzten Protokoll-Version, wie in Abbildung 3.1 zu dargestellt ist. Diese Daten werden als Befehl (Command) in der ersten Zeile der Anfrage gesammelt. Danach folgt der Kopf (Header) der Anfrage, welcher weitere Informationen enthalten kann: was für ein Format wird erwartet, wer schickt die Anfrage und welche Sprache wird bevorzugt. Der Server erhält diesen Request, verarbeitet ihn passend und sendet eine Antwort (Response) zurück. Je nach Art des Servers kann dabei die Antwort aus einer statischen Datei gelesen oder auch dynamisch erstellt werden. Die Antwort enthält in der obersten Zeile zunächst den Status der Antwort ebenfalls mit der genutzten Protokoll-Version sowie mit einem Status-Code und der passenden Status-Nachricht. Danach folgt auch in der Antwort ein Header, in dem weitere Informationen zusammengefasst sind. Mögliche Inhalte des Headers sind das Datum, der Typ und die Länge der Antwort, sowie Informationen über den Server. Zusätzlich zu diesen beiden Bereichen gibt es noch den eigentlichen Inhalt der Antwort: den Body. Hier befindet sich beispielsweise bei der Abfrage einer Internetseite der HTML-Quelltext. 3.2 AJAX Wie man in Abbildung 3.1 erkennen kann, muss zunächst der Client eine Anfrage an den Server schicken, um eine Antwort zu erhalten. Das bedeutet auch, dass nur der Client für eine Aktualisierung des Inhaltes sorgen kann, selbst wenn sich Echtzeitkommunikation im Web 15 serverseitig etwas verändert hat. Dies führt natürlich zu einem Problem, weil viele Web-Applikation mit Live-Daten arbeiten, welche sich ständig aktualisieren können – so wird beispielsweise ein Live-Ticker für ein Sportevent ständig mit neuen Daten versorgt, welche dann direkt beim Benutzer angezeigt werden sollen. Eine Möglichkeit wäre das ständige neu Laden der gesamten Seite mit den aktuellen Daten. Dies hat jedoch enorme Nachteile bei der Geschwindigkeit, da die gesamte Datenmenge neu geladen werden muss – das schließt beispielsweise auch Bilder mit ein, die sich nicht verändert haben. Ziel ist es jedoch, nur die Daten anzufordern, die sich seit der letzten Aktualisierung verändert haben. Eine Möglichkeit hierfür wird am 18. Februar 2005 von Jesse James Garrett vorgestellt und trägt das Akronym AJAX („Asynchronous JavaScript and XML“). Diese Technik ermöglicht es, nur bestimmte Daten asynchron vom Server anzufordern und im Client zu aktualisieren. Hierdurch ergibt sich eine neue Möglichkeit, mit der Entwickler nur die Daten laden können, die wirklich aktualisiert werden müssen. Dabei werden die einzelnen Anfragen wiederum vom Client initiiert, aber ohne dass die Seite selbst neu geladen werden muss. Diese Anfrage wird dann ebenfalls über ein HTTP-Request an den Server gesendet, welcher dann eine Response zurückschickt. Die Daten aus dem Body können dann im Client ausgelesen und die Seite passend aktualisiert werden [7]. Durch dieses Vorgehen verringert sich die Menge der benötigten Daten in dem zuvor beschriebenen Szenario (Live-Ticker) massiv, da nur die Informationen übertragen werden, die sich geändert haben. Doch obwohl die Technik sehr innovativ war und die Art der Web-Applikationen massiv erweitert hat, nutzt sie weiterhin HTTP als Kommunikationsweg und ist deshalb auch an die Restriktionen dieses Protokolls gebunden. So muss weiterhin der Client einen Request abschicken, um neue Daten zu erhalten. Zwar funktioniert dies asynchron (der Client muss nicht aktiv auf den Response warten), jedoch kann der Server die Antwort nicht direkt zum Client durchleiten, weshalb der Client im Hintergrund den Status durch zyklisches Abfragen ermitteln muss („polling“). Somit ist AJAX keine optimale Lösung für Echtzeitanwendungen, auch wenn sie als etablierter Standard und Ausweich-Technologie („Fallback“) bis heute einen festen Platz in Web-Applikationen einnimmt. Eine erweiterte Form von polling ist das „long polling“. Auch hier sendet der Client eine Anfrage an den Server, die dieser verarbeitet und direkt beantworten kann. Liegen die angeforderten Informationen noch nicht vor, schickt der Server jedoch keine leere Nachricht zurück, wie es bei HTTP und polling üblich wäre, sondern hält die Verbindung solange offen, bis die passenden Informationen zur Verfügung stehen - erst danach wird eine Antwort zurückgeschickt. Das bedeutet zwar, dass der Server ohne Verzögerung neue Informationen an den Client leiten kann, dafür 16 Echtzeitkommunikation im Web aber sehr viele Ressourcen benötigt, da für jede offene Anfrage eine Verbindung gehalten werden muss. 3.3 Websockets Ein großes Problem bei HTTP und AJAX ist, dass für jeden Datenaustausch eine Verbindung aufgebaut werden muss, über welche die beiden Nachrichten geschickt werden. Diese Verbindung ist jedoch nur für ein einmaliges Austauschen der Nachrichten verfügbar und wird danach vom Server geschlossen. Zwar wurde das Problem bereits im Update von HTTP 1.0 auf 1.1 adressiert, indem die Verbindung aktiv gehalten werden kann, jedoch nur für die Daten einer einzigen Anfrage [6]. Will man mehrere unabhängige Daten anfordern, so müssen diese immer über eigene Requests/Repsonses angefragt und geliefert werden. Das bedeutet auch, dass jedes Mal alle Informationen im Header geschickt werden müssen, wodurch ein sehr großer Overhead entsteht, welcher viele redundante und teilweise uninteressante Informationen enthält. Vor allem bei kurzen Nachrichten (weniger als 32 Byte) verursacht der Header ein Vielfaches der Datenmenge, die im Body enthalten ist. Dadurch entsteht ein unnötiger Overhead, welcher besonders in modernen WebApplikationen eine starke Verlangsamung verursacht. Diese Probleme der unzähligen Verbindungen und der redundanten Header werden seit einigen Jahren von verschiedenen Entwicklerteams behandelt und resultierten 2011 in einem neuen Standard: dem Websocket Protokoll [5]. Wie in Abbildung 3.2 zu erkennen ist, benötigt dieses Protokoll zuerst einen Handshake, welcher vom Client initiiert wird. Dafür sendet dieser eine Nachricht, welche komplett zu HTTP kompatibel ist und somit auf dem gleichen Port laufen kann, an den Server und übergibt einige Parameter für die Verbindung. Zunächst sind das, wie bei HTTP üblich, die angefragte Ressource und der Host. Hinzu kommt dann noch eine Anfrage für die Verwendung des Websocket-Protokolls (Upgrade), das darauf aufbauende eigene Protokoll und die gewünschte Protokoll-Version. Der Server kann dann diese Daten wiederum verarbeiten und sendet, falls das Protokoll unterstützt wird, eine passende Nachricht mit dem Status-Code 101 und einigen Informationen zum Upgrade. Nach diesem Upgrade steht eine Verbindung mit einem Voll-Duplex zur Verfügung, auf welcher sowohl der Client Daten an den Server senden, als auch der Server ohne vorherige Anfragen vom Client direkt eine Nachricht an den Client zustellen kann. Diese Socket-Verbindung sorgt dafür, dass Web-Applikationen ohne Anfrage direkt Daten vom Server beziehen können. Außerdem wird die Verbindung solange offen gehalten, bis eine Seite sie gezielt schließt. Das bedeutet auch, dass nach dem Echtzeitkommunikation im Web 17 Abbildung 3.2: Der Handshake des Websocket Protokolls basiert auf HTTP Request und Response einmaligen Handshake und Upgrade keine weiteren Overheads mehr gesendet werden müssen: alle Nachrichten enthalten nur die Daten, welche die Applikation tatsächlich benötigt. Zwar können nach Anwendungsfall auch Informationen wie Nachrichtentyp oder Topic mit in die Nachricht aufgenommen werden, jedoch sind diese Angaben, im Gegensatz zum Header von HTTP, immer abhängig von der Nachricht selbst und können zwischen einzelnen Nachrichten variieren. Das Websocket Protokoll wird aufgrund dieser Vorteile heutzutage verstärkt in Web-Applikationen genutzt und deshalb auch von sehr vielen Browsern und ServerTechnologien unterstützt. So gibt es für alle gängigen Programmiersprachen eigene Bibliotheken, mit denen das Aufsetzen eines Servers erleichtert wird. Eine der verbreitetsten Umgebungen für Web-Applikationen ist dabei node.js auf Basis von JavaScript, welches über verschiedenste Erweiterungen nicht nur als HTTP-Server sondern auch als Websocket-Server bertrieben werden kann. Dabei reichen die einzelnen Erweiterungen von einer einfachen Serverumgebung, welche nur das Websocket Protokoll unterstützen und die einzelnen Nachrichten pur, also komplett ohne Overhead, verschicken, bis hin zu Lösungen für Server- und Clientseite. Letztere liefern dabei auch Unterstützung für ältere Browser, wie ein Fallback auf AJAX und Long-Polling. Eine bekannte Erweiterung ist socket.io1 , welche außer für node.js2 auch für andere Programmierumgebungen zur Verfügung steht und das Erstellen von Echtzeit-Applikation für das Web nochmals vereinfacht. Mittlerweile hat sich das Websocket-Protokoll jedoch so weit verbreitet, dass fast 1 http://socket.io/ 2 JavaScript Bibliothek für Entwicklung von Netzwerk-Applikationen; http://nodejs.org/ 18 Echtzeitkommunikation im Web alle aktuellen Browser diese Verbindungsart unterstützen. Lediglich der Android Standard Browser und Opera Mini verweigern in der aktuellen Version noch die Zusammenarbeit. Eine genauere Übersicht über die Unterstützung durch die einzelnen Browser ist in Tabelle 3.1 zu erkennen. IE 8.0 9.0 10.0 Firefox 21.0 22.0 23.0 Chrome 26.0 27.0 28.0 Safari 5.0 5.1 6.0 Opera Mini Android Blackberry Opera Mobile 5.0 6.0 7.0 4.0 4.1 4.2 7.0 10.0 12.0 12.1 14.0 Opera 12.0 12.1 15.0 Chrome Mobile iOS Safari 4.2-4.3 5.0-5.1 6.0-6.1 Firefox Android 28.0 23.0 Tabelle 3.1: Unterstützung des Websocket-Protokolls durch einzelne Browser (aktuelle Version zuletzt). Grün = unterstützt, gelb = teilweise unterstützt, rot = nicht unterstützt [4] 3.4 WebRTC Obwohl das Websocket Protokoll bereits viele Lösungen für die Echtzeitkommunikation im Web-Kontext bereitstellt, eine Vielzahl von Verbesserungen gegenüber anderen Technologien liefert und stetig ausgebaut wird, gibt es bereits neue Ansätze, mit denen man die Echtzeitkommunikation im Web-Kontext verbessern will. Eine Arbeitsgruppe um das World Wide Web Consortium (W3C) und die Internet Engineering Task Force (IETF) arbeitet derzeit an Web Real-Time Communication, kurz WebRTC. Nach dieser Technologie soll, im Gegensatz zu Websockets und HTTP, die Web-Applikation nicht über einen Server, sondern direkt zwischen zwei oder mehr Browsern kommunizieren. Die dadurch geschaffenen Peer-to-Peer-Verbindungen sollen dabei um ein Vielfaches schneller sein als der Umweg über einen Server [10]. Eine Möglichkeit für die Verbindung ist ein Dreieck, bestehend aus einem Server, welcher für die Vermittlung verantwortlich ist, und zwei Clients, welche dann einen oder mehrere Daten-Streams untereinander aufbauen und darüber kommunizieren können, wie in Abbildung 3.3 zu sehen ist. Ziel der Entwickler ist es außerdem, nicht nur einfache Daten verschicken zu können, sondern ebenfalls eine API zum Auslesen von Medienquellen (Kamera, Mikrofon) zur Verfügung zu stellen, mit deren Hilfe Entwickler Audio und Video auslesen und über eigene Streams versenden können. So verwundert es auch nicht, dass die erste Echtzeitkommunikation im Web 19 Abbildung 3.3: Durch WebRTC wird die Verbindung von dem Server nur vermittelt, die Kommunikation erfolgt direkt zwischen den beiden Clients mit Hilfe von DatenStreams [10] große Demonstration der Technologie einen Live-Video-Chat á la Skype3 zwischen zwei Personen zeigt, wobei die Besonderheit darin liegt, dass alles direkt über den Browser zur Verfügung gestellt wird. Ziel der Entwickler ist es außerdem, einen Standard zu schaffen, welcher über verschiedene Client- und Servertypen hinweg sogar einfache Telefonanlagen mit Computern verbinden können soll. Jedoch ist das größte Problem an der Technologie heutzutage, dass sie sich noch in der Entwicklung befindet und frühestens Ende 2013 in einer ersten Version offiziell erscheint. Bis dahin gibt es nur Empfehlungen für die Nutzung der verschiedenen Komponenten, welche beispielsweise zwischen den einzelnen Browsern sehr unterschiedlich sind und daher dafür sorgen, dass Mozilla Firefox und Google Chrome unterschiedliche Implementierungen benötigen, um miteinander kommunizieren zu können. Allgemein gibt es derzeit nur wenige unterstützende Browser: Mozilla Firefox ab der aktuellen Version 23.0 und Google Chrome ab Version 25.0, außerdem unterstützt die Beta des mobilen Browsers Google Chrome in der Version 29.0 bereits als erstes WebRTC auf mobilen Geräten. Dennoch ist es noch ein weiter Weg, bis eine Abdeckung erreicht wird, wie sie das Websocket-Protokoll bereits hat. Ein weiterer aktueller Nachteil ist, dass der Verbindungsaufbau zwischen zwei Clients noch sehr viele Anpassungen benötigt, damit es zu keinen Problemen kommt. Für etwas Abhilfe sorgt hier eine erste Bibliothek: PeerJS4 . Diese Bibliothek unter3 http://www.skype.com/de/ 4 http://www.peerjs.com/ 20 Echtzeitkommunikation im Web stützt die Vermittlung der Verbindung und fügt die APIs von Mozilla Firefox und Google Chrome so zusammen, dass man für beide Browser einheitliche Befehle nutzen kann, wodurch Entwickler stark entlastet werden. Dennoch bleibt festzuhalten, dass diese Technologie im aktuellen Zustand noch zu große Probleme aufweist und somit nur bedingt genutzt werden kann. Jedoch soll diese Technologie trotzdem für diese Arbeit berücksichtigt und getestet werden, da die zukünftigen Aussichten und die Möglichkeiten, welche diese Technologie liefern könnte, beeindruckend sind und einen weiteren, sehr wichtigen Schritt in der Entwicklung des Web darstellen können. 3.5 Vergleich Websockets, Socket.IO & WebRTC Nachdem geklärt ist, welche Technologien aktuell zur Umsetzung von Echtzeitkommunikationen im Web zur Verfügung stehen, soll nun noch überprüft werden, welche Vorteile und Nachteile die jeweiligen Alternativen bieten. Zum einen soll vor allem die Latenz der Verbindungsarten durch die Round-Trip-Time ermittelt werden, da dies der wichtigste Faktor für die verzögerungsfreie Kommunikation ist. Die Round-Trip-Time (RTT) misst dabei die Zeit, welche ein Paket vom Client zum Server und wieder zurück braucht. Jedoch spielen auch die Übertragungsgeschwindigkeit, die Zuverlässigkeit und die Implementierung eine wichtige Rolle. Websockets sind mittlerweile sehr weit verbreitet und haben dadurch, wie bereits erwähnt, sehr viele verschiedene Implementierungen zu bieten, welche verschiedene Szenarien abdecken. Eine einfache Bibliothek stellt dabei „WebSocket Node“ von Brian McKelvey bereit, welches serverseitig die aktuellen Websocket Protokolle umsetzt [11]. Eine mächtigere Alternative stellt Socket.IO dar, welches neben dem eigentlichen Websocket Protokoll auch andere Technologien wie AJAX als Fallback nutzt und diese sowohl auf Server- als auch auf Client-Seite passend implementiert zur Verfügung stellt[13]. Dieses Komplettpaket bietet viele Erweiterungen im Vergleich zum eigentlichen Websocket- Protokoll und sichert vor allem die Verwendung in älteren Browsern ab, welche noch keine Websockets unterstützen. Bei WebRTC hingegen gibt es nur die bereits erwähnte Bibliothek „PeerJS“, welche, bedingt durch den Entwicklungsstand von WebRTC an sich, nur wenige Funktionalitäten bereitstellt [3]. Die Implementierung bei Websockets ist im Allgemeinen durch die verschiedenen Bibliotheken schnell abgeschlossen. Bei beiden Implementierungen basieren serverseitig die Websocket-Verbindungen zunächst auf einem HTTP-Server, welcher standardmäßig von node.js unterstützt wird. Auf diesem Server wird dann ein Websocket-Server aufgesetzt, welcher auf dem gleichen Port lauscht und eingehende Echtzeitkommunikation im Web 21 Ereignisse an die jeweiligen Listener weiterleitet. Dafür warten beide Implementierungen auf eingehende Verbindungen und liefern im passenden Listener den Socket mit, welcher dann gespeichert und genutzt werden kann. Auf diesem Socket werden dann weitere Listener angemeldet, welche dann ein Verbindungsende abfangen und ankommende Nachrichten je nach Nachrichtentyp verwalten können. Das Verschicken von Nachrichten wird ebenfalls über die einzelnen Sockets ausgeführt, indem die Senden-Methode für jeden Socket aufgerufen wird. Clientseitig setzt Websocket Node auf die Standardimplementierung von Websockets, während SocketIO eine eigene Bibliothek mitliefert, die neben den Fallbacks auch einige Vereinfachungen für Websockets bereithält. Dennoch arbeiten beide Implementierungen nach demselben Prinzip: Es wird eine Verbindung zu einem bestimmten Server mit Adresse und Port geöffnet, welche dann ebenfalls über vorgegebene Listener Ereignisse abfangen und verarbeiten kann. Auch das Versenden von Nachrichten ist mit einem Methodenaufruf auf den Socket zu erledigen. Beide Seiten der Verbindung sorgen dafür, dass die gesamte Kommunikation mit Websockets sehr einfach zu implementieren ist. Bei WebRTC mit PeerJS benötigt man ebenfalls zunächst einen Server, welcher die Verbindung zwischen den beiden Clients austauscht. Dieser Server basiert ebenfalls auf node.js und ist durch einen zweizeiligen Code aufgesetzt. Die Clients benötigen dann die Adresse und den Port des Servers und verbinden sich mit diesen Daten auf den Server. Wie bereits erwähnt, muss dabei ein Client einen Peer-Server mit einer einmaligen ID erstellen, auf den sich der andere Client dann verbinden kann. Hierbei fehlt aber ein Austausch der einzelnen IDs, wodurch man bereits vor dem Verbindungsaufbau die IDs fest hinterlegen muss. Auch die Kommunikation über die aufgebaute Peer-to-Peer-Verbindung ist in der aktuellen Version dürftig, da es nur einen unverlässlichen Nachrichtentyp gibt. Insgesamt ist die Struktur noch sehr starr, was aber zweifellos an dem aktuellen Entwicklungsstand von WebRTC liegt. Neben der Implementierung und den dabei entstehenden Problemen ist vor allem die Übertragungsgeschwindigkeit von essentiellem Interesse, da diese maßgeblich dafür verantwortlich ist, ob die Applikation in Echtzeit abläuft. Zwar spielen auch Faktoren wie die Implementierung der eigentlichen Spiele-Logik eine große Rolle, jedoch ist dies vom eigentlichen Netzwerk-Bereich abgekapselt und stellt eine eigene Thematik dar. Für die Untersuchung der Geschwindigkeit soll also nur die Übertragungsgeschwindigkeit erfolgen, wobei hier die RTT entscheidend ist. Für diesen Test wird eine Nachricht vom Client an den Server gesendet und von diesem sofort wieder zurückgeschickt - ein solcher Server wird auch Echo-Server genannt. Bei WebRTC dient ein Client als Server und schickt ebenfalls einkommende Nachrichten direkt wieder zurück. Damit man dabei feststellen kann, wie lange die Nachricht unterwegs war, beginnt jede Nachricht mit einem Zeitstempel mit der 22 Echtzeitkommunikation im Web aktuellen UNIX-Systemzeit, also der Zeit seit dem 1. Januar 1970 00:00 Uhr UTC. Dieser Zeitstempel liefert eine auf die Millisekunde genaue Zeitangabe, welche dann vom Client wieder gelesen und mit dem aktuellen Zeitstempel verrechnet werden kann. Daraus ergibt sich dann die RTT für diese Nachricht. Um außerdem eine bessere Übersicht über die Übertragungsgeschwindigkeit abhängig von der Nachrichtengröße zu erhalten, variiert die Nachrichtengröße von 25 bis 214 Byte. Pro Nachrichtengröße werden dabei jeweils 1.000 Nachrichten verschickt und zum Schluss gemittelt, um so eine bessere Aussagekraft zu erzielen. Außerdem werden die Tests sowohl im Intranet über WLAN und Kabel durchgeführt, als auch über einen Server im Internet – auch hier wurden die Werte gemittelt. Insgesamt wurden dreimal drei Tests mit 10 Runden und 1.000 Schritten durchgeführt. Die gesammelten Ergebnisse sind im Abbildung 3.4 zu erkennen. Dabei fällt auf, dass bei kleineren Nachrichtengrößen sowohl die pure Implementierung der Websockets, wie auch SocketIO ähnliche Laufzeiten haben. Bei größeren Datenmengen hingegen benötigt SocketIO etwas mehr Zeit als Websockets selbst. Dies könnte sowohl durch die komplexeren Serverstrukturen als auch durch den Overhead entstehen, den SocketIO anhängt. Dennoch sind beide Technologien derzeit deutlich schneller als die Datenübertragung mittels PeerJS basierend auf WebRTC. Hinzu kommt, dass PeerJS Probleme mit Nachrichten ab 512 Byte hat und im durchgeführten Test abstürzt. Aus diesem Grund sind nur die Messwerte bis einschließlich 512 Byte gelistet. Abbildung 3.4: Untersuchungen der Round-Trip-Time (RTT) mit verschiedene Nachrichtengrößen für Websockets, SocketIO und WebRTC mit PeerJS Echtzeitkommunikation im Web 23 Der deutliche Geschwindigkeitsunterschied zwischen Websockets und WebRTC könnte dadurch erklärt werden, dass WebRTC in der aktuellen Version noch keine Kommunikation per TCP beherrscht und somit auf UDP zurückgreifen muss. Damit jedoch keine Nachrichten verloren gehen, wird die Zustellung nicht von der Transport- sondern von der Anwendungsschicht, in diesem Fall WebRTC, sichergestellt. Dies kann zu längeren Übertragungs- und Verarbeitungszeiten führen. 3.6 Zusammenfassung Zum Zeitpunkt der Untersuchungen war klar zu erkennen, dass WebRTC zwar ein sehr großes Ziel verfolgt, dieses jedoch noch lange nicht erreicht hat. Die Umsetzung der Audio- und Video-Streams ist bereits sehr gut umgesetzt und funktioniert sowohl bei Mozilla Firefox wie auch bei Google Chrome in einer sehr guten Qualität und Geschwindigkeit – Medien-Streams können also bereits auf die neue Technologie setzen. Die unfertigen Standards bei den anderen Datenverbindungen sorgen jedoch dafür, dass die Verbindungsgeschwindigkeit von WebRTC derzeit noch klar von Websockets geschlagen wird. Auch führen die offenen Richtlinien dazu, dass man Applikation häufig umschreiben und neu gestalten muss. Das Websocket-Protokoll hatte bereits viel Zeit, sich zu etablieren und hat das auch getan. Es gibt eine Vielzahl von verschiedenen Implementierungen, welche je nach Anwendungsfall mit immer mehr Funktionalitäten aufwarten. Auch das Management der Verbindungen und die eigentliche Kommunikation sind viel ausgereifter. Deshalb sind Websockets zum aktuellen Stand der klare Favorit, wenn es um Echtzeitkommunikation im Web geht. 24 Echtzeitkommunikation im Web Kapitel 4 Anforderungsanalyse Um von der Spielmechanik zur Umsetzung zu gelangen, müssen die einzelnen Ideen und Regeln zusammengefasst und analysiert werden. Dazu dient die Anforderungsanalyse, welche als eine erste Abstraktion der Spielidee die spätere Implementierung vorbereiten soll. Hierzu werden zunächst die möglichen Benutzerinteraktionen und deren Folgen ausgewertet und zusammengefasst. Danach werden daraus mögliche Spielelemente konstruiert, welche eine Grundlage für die späteren Objekte in der Implementierung bilden. Danach soll auch die benötige Infrastruktur für die Kommunikation und deren Schnittstellen behandelt werden. Zuletzt sollen auch noch die nicht-funktionalen Anforderungen bezüglich der Kommunikation in Echtzeit und der Kompatibilität behandelt werden. 4.1 Benutzerinteraktionen Beim Auswerten des Spielkonzeptes fällt auf, dass der Benutzer sich zunächst in drei verschiedenen Bereichen bewegen kann. Direkt nach dem Starten der Applikation gelangt er in das Hauptmenü und hat hier die Interaktionsmöglichkeiten aus Abbildung 4.1 zur Verfügung. Der Benutzer kann hier seinen Benutzernamen wählen, welcher dann im weiteren Spielverlauf für ihn genutzt wird. Diese Möglichkeit hat er jederzeit im Hauptmenü. Des Weiteren kann der Benutzer alle aktiven Spiellobbys sehen, welche vom System geladen werden müssen. Um immer aktuelle Daten zur Verfügung zu haben, wird die Lobby-Liste immer automatisch aktualisiert, wenn es eine Änderung bei den Spiellobbys im System gibt. Aus dieser Liste kann der Benutzer sich dann ein Element aussuchen und dieser Lobby beitreten. Er gelangt dann als einfacher Benutzer in die Spiellobby. Jedoch kann er auch eine eigene Spiellobby erstellen und gelangt direkt in die neu erstellte Lobby – dieses Mal jedoch als Lobby-Ersteller. 25 26 Anforderungsanalyse Abbildung 4.1: Im Hauptmenü können die Benutzer ihren Namen festlegen und in eine Spiellobby wechseln Die Spiellobby ist der zweite Bereich der Applikation, in dem alle Benutzer gesammelt werden, bevor das eigentliche Spiel beginnt. Wie bereits im Abschnitt 2.2 beschrieben und in Abbildung 4.2 zu erkennen ist, gibt es zwei Arten von Benutzern, wobei der Lobby-Ersteller einige Privilegien im Vergleich zum normalen Benutzer hat. Zunächst können beide Benutzertypen die Spiellobby wieder verlassen und gelangen dann ins Hauptmenü. Verlässt jedoch der Ersteller seine eigene Lobby, so wird diese geschlossen und alle Benutzer werden in das Hauptmenü geleitet. Um einen Überblick über die bereits verbundenen Teilnehmer zu haben, können alle Benutzer diese in einer Liste sehen. Dabei hat der Ersteller der Lobby hier die Möglichkeit, Benutzer zu entfernen, wodurch diese wieder im Hauptmenü landen. Ist der Ersteller mit der Liste der Teilnehmer zufrieden, kann er das Spiel starten und alle Benutzer dieser Lobby gelangen zum Spiel. Abbildung 4.2: Die Spiellobby stellt je nach Benutzertyp verschiedene Möglichkeiten bereit und kann entweder in Richtung Hauptmenü verlassen werden oder ins Spiel übergehen Anforderungsanalyse 27 Das Spiel selbst lässt sich wiederum in drei Handlungsstränge unterteilen, welche in Abbildung 4.3 zu sehen sind. Zunächst können die Benutzer Kugeln verschießen und somit bestimmte Aktionen auslösen und Extras sammeln. Dann haben sie die Möglichkeit die gesammelten Extras einzusetzen und somit das Spiel zu beeinflussen. Dies führt letztendlich dazu, dass Benutzer verlieren können und am Ende nur noch ein Gewinner übrig bleibt. Abbildung 4.3: Das Spiel besteht aus drei Bereichen: Abschießen der Kugel, Benutzen von Extras und Spielende Wie bereits in Kapitel 2.1 beschrieben, besteht der eigentliche Spielablauf aus dem Abschießen und Abbauen von Kugeln. Dies ist in Abbildung 4.4 zu erkennen. Der Benutzer kann dafür zunächst eine Kugel in das Spielfeld abschießen, welche dann in die Richtung des Mauszeigers losfliegt und solange an den Banden abprallt, bis sie an eine Kugelgruppe oder den oberen Spielfeldrand andockt. Wenn das geschieht, wird zunächst überprüft, ob das Spielfeld überfüllt ist, die Kugel also unterhalb der 15. Kugelreihe angedockt ist. Falls dies der Fall sein sollte, hat der Spieler das aktuelle Spiel verloren. Andernfalls wird die Kugelgruppe, an welche die neue Kugel angedockt ist, überprüft. Befinden sich hier mindestens noch zwei andere Kugeln mit der gleichen Farbe, wird diese gesamte Kugelgruppe entfernt. Ebenfalls werden alle an diese Kugelgruppe angrenzenden Extras entfernt und dem Inventar angefügt. Werden dabei mehr als fünf Kugeln entfernt, erscheint auf dem Spielfeld ein neues Extra. Falls jedoch keine Kugelgruppe abgebaut wird, muss eine Kugel aus dem Kugelvorrat entfernt werden. Ist dieser Vorrat aufgebraucht, wird außerdem eine neue Reihe von Kugeln angefügt. Hierbei kann es ebenfalls passieren, dass das Spielfeld überfüllt wird und der Spieler verliert. Bleibt das Spielfeld aber innerhalb der 15 vorhandenen Zeilen, so wird der Kugelvorrat wieder aufgefüllt. Falls das Spiel nicht vorbei ist, erhält der Spieler eine neue Kugel und kann mit dieser weiterspielen. 28 Anforderungsanalyse Abbildung 4.4: Das Abschießen einer Kugel kann viele verschiedene Aktionen auslösen Anforderungsanalyse 29 Hat der Benutzer durch das Abbauen von Kugelgruppen Extras in seinem Inventar gesammelt, kann er diese Extras benutzen. Dazu stehen ihm die Möglichkeiten aus Abbildung 4.5 zur Verfügung. Zunächst kann der Benutzer zu jeder Zeit sein Inventar mit den aktuellen Extras sehen und somit erkennen, welches Extra er als nächstes nutzen kann. Dieses nächste Extra kann er dann auf sich selbst oder einen anderen Mitspieler anwenden. Das hat zur Folge, dass das benutzte Extra aus dem Inventar entfernt wird und sich die Anzeige aktualisiert. Extras mit einer Wirkdauer werden dem jeweiligen Benutzer dann ebenfalls angezeigt, um den Benutzer darüber zu informieren. Damit die einzelnen Spieler eine Vorstellung davon haben, wie der Spielstand bei den anderen Mitspielern aussieht, können sie neben ihrem eigenen Spielfeld auch die Spielfelder der anderen Teilnehmer sehen. Abbildung 4.5: Der Spieler kann sein Inventar sehen und Extras daraus benutzen. Außerdem hat er auch eine Übersicht über seine aktuell aktiven Extras und alle Spielfelder Bei der Benutzung von Extras kann es auch dazu kommen, dass das Spielfeld eines Spielers überfüllt wird. In diesem Fall hat der Spieler, wie auch beim Überfüllen des Feldes durch eine eigene Aktion, das Spiel verloren. In beiden Fällen wird das Spielfeld geräumt und der Benutzer kann das Spiel nur noch verlassen. Sobald der vorletzte Mitspieler sein Spielfeld überfüllt, gewinnt der verbleibende Spieler das aktuelle Spiel. Außerdem steht es jedem Mitspieler zu jedem Zeitpunkt frei, das Spiel zu verlassen. Hier werden alle Spieler, also auch der frühere Lobby-Ersteller, gleichbehandelt: verlässt ein Spieler das Spiel, so gelangt er in das Hauptmenü und sein Spielfeld wird geräumt, genauso als hätte er verloren. Diese Use-Cases stellen die Interaktionen des Benutzers dar und schaffen einen Überblick darüber, welche Eingaben der Benutzer tätigen kann, zu welchen Reaktionen es dann jeweils kommt und welche Zustände unterschieden werden können. 30 4.2 Anforderungsanalyse Spielelemente Die möglichen Interaktionen des Benutzers ergeben neben den Use-Cases auch eine erste Übersicht über die Elemente und Objekte der Applikation. Diese Elemente repräsentieren teilweise reale Objekte aus der natürlichen Welt, wie die einzelnen Benutzer, aber auch Applikationselemente, wie beispielsweise Spiellobbys und Kugeln. Da diese Objekte die spätere Applikationswelt darstellen, müssen sie aus der Spielmechanik und den Use-Cases aus Kapitel 4.1 extrahiert und passend verbunden werden. Aus diesen Vorlagen entsteht das Domänenmodell in Abbildung 4.6, welches als Grundlage für die späteren Objekte und Daten in der Applikation dienen soll. Im Mittelpunkt der Applikation steht der Benutzer, welcher die anderen Elemente manipulieren kann. Deshalb wird der Benutzer im Domänenmodell ebenfalls im Zentrum positioniert und hat Verbindungen zu fast allen anderen Objekten. Er wird repräsentiert durch einen Benutzernamen. Im Hauptmenü sieht er zunächst die Spiellobby-Liste. Diese Liste besteht aus allen aktuell aktiven Spiellobbys im System und steht allen Benutzern im Hauptmenü zur Verfügung. Aus dieser Liste kann jeder Benutzer ein Element auswählen und diesem beitreten, wodurch die Spiellobby einen weiteren Benutzer aufnimmt. Insgesamt können sich bis zu neun Spieler in einer Lobby befinden, jeder Benutzer jedoch in maximal einer gleichzeitig. Außerdem kann jeder Benutzer maximal eine Spiellobby erstellen, wodurch dieser zu einem Ersteller wird. Dieser kann dann die Spiellobby in ein Spiel überführen, wodurch alle Benutzer und der Ersteller der Lobby zu Spielern werden. Im Spiel erhält jeder Spieler weitere Elemente, welche nur im Spielkontext relevant sind, weshalb der Spieler beim Verlassen des Spiels wieder zu einem Benutzer wird. Jeder Spieler hat ein Spielfeld, welches je nach festgelegter Größe aus beliebig vielen Kugeln bestehen kann. Diese Kugeln haben eine bestimmte Farbe, die für das Abbauen von Kugelgruppen von Bedeutung ist. Jede Kugel kann von einem Benutzer verschossen werden, jedoch können Kugeln auch direkt auf dem Spielfeld auftauchen. Kugeln, die vom Benutzer verschossen werden können, werden im Kugelvorrat gesammelt. Dabei besteht dieser aus maximal fünf Kugeln gleichzeitig, wobei mit jedem Schuss ohne entfernte Kugelgruppe jeweils eine Kugel daraus entfernt wird. Jeder Spieler hat genau einen Vorrat, welcher auch nur diesem Spieler gehört und beliebig oft aufgefüllt werden kann. Neben den normalen Kugeln besteht das Spielfeld auch aus beliebig vielen Extras, welche einen bestimmten Typ haben, der mit einer Aktion verbunden ist. Dabei kann jedes Extra entweder auf dem Spielfeld liegen, bei einem Benutzer aktiv sein Anforderungsanalyse 31 oder sich im Inventar eines Benutzers befinden. Jeder Benutzer kann beliebig viele aktive Extras haben, wobei jedoch jedes Extra auf nur einen Benutzer angewendet werden kann. Dabei kann das Extra auch einmalig auf das Spielfeld des jeweiligen Benutzers wirken und dieses manipulieren. Bevor das Extra angewandt werden kann, befindet es sich im Inventar eines Spielers, welches auch nur von diesem Spieler benutzt werden kann. Das Inventar selbst kann beliebig viele eingesammelte Extras sequentiell halten und stellt dem Benutzer immer das älteste davon zur Verfügung. Abbildung 4.6: Objekte des Spiels als Domänenmodell mit den Spielbereichen oben, darunter den Benutzertypen und den Spielelementen 32 Anforderungsanalyse 4.3 Echtzeit Die Anforderungen an die Applikation beschränken sich natürlich nicht nur funktional. Es existieren auch einige nicht-funktionale Anforderungen, welche ebenfalls beachtet werden müssen. Eine dieser Anforderungen steckt bereits im Namen der Arbeit: die Echtzeit. Der Benutzer soll Eingaben tätigen können, die dann von der Applikation verarbeitet werden und ein bestimmtes Feedback liefern. Diese drei Bestandteile sollen dabei in Echtzeit ablaufen. Zunächst muss dazu geklärt werden, was Echtzeit bedeutet. Hierzu gibt es verschiedene Ansichten und Definitionen, die sich teilweise überschneiden: Eine definierte Aktion im System muss für den Betrachter in einer garantierten Zeit simultan zu der gleichen Aktion in der realen Welt ablaufen. So muss beispielsweise ein Video genauso schnell abgespielt werden, wie es auch aufgenommen wurde, um das Kriterium der Echtzeit zu erfüllen. [9] Für den hier vorgestellten Fall muss diese Definition jedoch erweitert werden, da es keinen direkten Vergleich für die Kommunikation im Internet in der realen Welt gibt. Die Zeitspanne muss also anders festgelegt werden. Hierfür muss man die Erwartungen des Benutzers heranziehen. Dieser löst eine bestimmte Aktion aus und erwartet ein sofortiges Feedback vom System. Die Zeitspanne für die Verarbeitung und Ausgabe der Daten wird hierbei nach der Flimmerverschmelzungsfrequenz (FVF) bestimmt. Diese besagt, dass ab einer bestimmten Wiederholfrequenz zwei aufeinanderfolgende Reize nicht mehr separat, sondern simultan wahrgenommen werden – quasi verschmelzen [15]. Eine der bekanntesten Anwendungen ist dabei der Film: Hier werden einzelne Bilder so schnell ausgewechselt, dass Auge und Gehirn diese als Bewegung interpretieren. Dieses Medium soll auch als Grundlage für die Festlegung der Zeitspanne dienen. Basierend auf den aktuellen Standards bei Film und Fernsehen variiert die Anzahl der Bilder pro Sekunde zwischen 24 (Kino) und 30 (Fernsehen, NTSC1 ). Das bedeutet, dass zwischen einer Benutzerinteraktion und der Anzeige des daraus resultierenden Ergebnisses maximal 33 Millisekunden vergehen dürfen. Dies schließt im Netzwerkbetrieb auch die Dauer für die Übertragung einer Nachricht mit ein. 4.4 Kompatibilität Eine weitere nicht-funktionale Anforderung der Applikation ist die Kompatibilität mit verschiedenen Systemen, auf welchen sie lauffähig sein soll. Es soll eine Übertragbarkeit sichergestellt werden, die möglichst vielen Benutzern auf unter1 Fernsehsystem für Farbübertragung nach dem „National Television Systems Committee“, USA Anforderungsanalyse 33 schiedlichen Endsystemen die Möglichkeit liefert, die Applikation in vollem Umfang parallel benutzen zu können. Dies soll durch die Nutzung von Technologien und Frameworks sichergestellt werden, welche möglichst viele Systeme abdecken. Da die Applikation in zwei Bereiche, Server und Client, aufgeteilt ist, müssen diese beiden Systeme bezüglich der Kompatibilität separat betrachtet werden. Bei der Betrachtung dieser Komponenten fällt auf, dass auf einem Server immer beliebig viele Clients arbeiten können – es gibt also tendenziell mehr Clients als Server. Das bedeutet auch, dass ein Server ein starres Element ist, welches auf einem bestimmten System gestartet wird und auf diesem langfristig laufen kann, während die Clients viel kurzlebiger sind und sich ständig austauschen können. Daraus folgt, dass der Serverpart nicht auf jedem beliebigen System lauffähig sein muss - es soll ausreichen, wenn er auf einem passend vorbereiteten System ausgeführt werden kann. Die Clients hingegen müssen auf möglichst allen Systemen vollständig lauffähig sein. Um dies zu erreichen, werden diese in eine Website eingebettet und somit von einem Browser abgespielt. Durch die Nutzung von gängigen Webtechnologien wird sichergestellt, dass alles standardkonformen Browser die Applikation in vollem Umfang darstellen können. Jedoch gibt es tendenziell auch Bestandteile der Applikation, die noch nicht als verbreiteter Standard in allen Browsern identisch implementiert wurden. Um testen zu können, welche Technologien auf möglichst vielen Systemen lauffähig sind, muss zunächst analysiert werden, welche Browser aktuell wie stark auf dem Markt vertreten sind. Dazu soll eine Statistik zur Browsernutzung vom August 2013 herangezogen werden, welche in Abbildung 4.7 dargestellt wird. Die hier präsentierten Daten basieren auf Seitenaufrufen von verschiedenen Benutzern unterschiedlicher Webseiten ohne Beachtung der jeweiligen Platformen. Dabei findet auch keine Gewichtung statt. Insgesamt sind die Daten zwar nicht repräsentativ, sie geben jedoch eine ausreichende Vorstellung von der aktuellen Marktverteilung. Abbildung 4.7: Statistik zur Browsernutzung vom August 2013, Desktop & mobil [14] 34 Anforderungsanalyse Basierend auf diesen Daten müssen die Desktop-Browser Google Chrome ab Version 28, Microsoft Internet Explorer ab Version 8, Mozilla Firefox ab Version 22, Apple Safari ab Version 6 und Opera ab Version 12.1 von den genutzten Technologien unterstützt werden. Durch die immer stärker werdende Nutzung von Smartphones und Tablets ergibt sich im Moment ein Marktanteil von 18% bei Seitenaufrufen [14]. Daher müssen auch die Browser auf diesen mobilen Systemen berücksichtigt werden, dazu gehören Apple Safari ab iOS 6, der Android Browser ab Android 2.3 und Google Chrome ab Version 28. Kapitel 5 Konzept Die verschiedenen erhobenen Anforderungen zeigen auf, welche Elemente die Implementierung beinhalten soll, wie sie miteinander verbunden sind und vor allem, wie der Benutzer mit ihnen interagieren kann. Nun gilt es diese gesammelten Anforderungen in ein Konzept zu fassen und für die Implementierung vorzubereiten. Zunächst soll dazu eine vereinfachte Übersicht über die gesamte Applikation gegeben werden, bevor es anschließend detailliert in die einzelnen Bereiche geht. Da die Applikation über ein Netzwerk auf einen Server und mehrere Clients verteilt werden soll, wird als nächstes dargestellt, wie die einzelnen Daten über das Netzwerk vom Server zu den einzelnen Clients und wieder zurück transportiert werden, wie diese Schnittstelle erweiterbar bleibt und welche Optimierungen dabei berücksichtigt werden müssen. Diese abstrahierte Netzwerk-Schnittstelle ist dann die Zwischenschicht für die eigentliche Spiellogik, welche über die Bereiche Server und Client verteilt ist. Hierzu soll zunächst die serverseitige Spiellogik mit allen benötigten Objekten, Zuständen und Abhängigkeiten modelliert werden. Danach wird dargestellt, wie die einzelnen Clients an diese Elemente angebunden werden. Als letztes sollen dann noch die verschiedenen Aktionen der Benutzer von der Eingabe über die Verarbeitung bis zur Ausgabe dargestellt werden. 5.1 Gesamtüberblick Die hier vorgestellte Applikation verteilt sich über die Bereiche Server, Netzwerk und Clients. Die dabei benötigten Klassen und ihre bestehenden Abhängigkeiten sind in Abbildung 5.1 dargestellt. In dieser Darstellung werden diese drei Bereiche nochmals anders aufgeteilt: Die Clients bestehen hier aus einer Anzeigelogik, einem Zwischenspeicher und der GUI. 35 36 Konzept Abbildung 5.1: Vereinfachte Übersicht über die Objekte der Applikation und deren Abhängigkeiten und Kommunikationswege Konzept 37 Die GUI startet clientseitig zuerst und ist für die Anzeige von Informationen in den einzelnen Elementen zuständig. Diese Informationen befinden sich im Zwischenspeicher der Clients, welcher durch die Anzeigelogik verwaltet wird. Hierzu erhält diese Schicht neue Daten über einen Listener von der Netzwerkschicht und speichert diese Daten im Zwischenspeicher. Als nächstes benachrichtigt die Logik ebenfalls über einen Listener das GUI-Objekt über die Art der Veränderung, woraufhin sich das GUI-Objekt die jeweiligen Daten abholt und darstellt. Die Netzwerkschicht besteht in diesem vereinfachten Fall aus nur einem Objekt, welches genau einen Server mit beliebig vielen Clients verbindet. Dabei wird diese Schicht von außen direkt angesprochen und signalisiert Aktualisierung an den Server beziehungsweise an die Clients über zuvor registrierte Listener. Hierbei werden der Typ der Aktion und die benötigten Daten an die verarbeitenden Klassen übergeben, dort verarbeitet und dem passenden Callback zugewiesen. Der Server beinhaltet die eigentliche Logik der Applikation und startet unabhängig von den Clients. Auch hier kommen neue Informationen über die Netzwerkschicht von den Clients an, werden jedoch nicht nur abgespeichert, sondern manipulieren die verschiedenen Objekte der Applikation. Hierbei gibt es zunächst die BenutzerObjekte, welche die einzelnen Clients darstellen. Diese können Spiellobby-Objekte erstellen und diesen beitreten, was ebenfalls durch das Backend-Objekt gesteuert wird. Der Ersteller kann dann die Lobby in das Spiel überführen, woraufhin die einzelnen Benutzer-Objekte ein Spielfeld und ein Inventar erhalten und mit diesen dann am Spiel teilnehmen. All diese Informationen werden immer durch Eingaben der Benutzer in die Clients erzeugt, über die Netzwerkschicht zum Server geleitet und hier verarbeitet. Die aktualisierten Objekte und Daten werden dann wieder über die Netzwerkschicht entweder an alle oder nur an bestimmte, einzelne Clients geschickt und sorgen dort für eine Aktualisierung der Anzeige. 5.2 Netzwerkkommunikation Da diese Arbeit von der Entwicklung einer verteilten Applikation handelt, soll zunächst diese Nahtstelle zwischen den einzelnen Clients und dem Server beschrieben werden. Dabei sollen die verschiedenen Daten, welche auf dem Server vorliegen, von den einzelnen Clients manipuliert werden können. Dazu können die Benutzer verschiedene Aktionen ausführen, wodurch die Clients Nachrichten an den Server verschicken, welche dieser dann passend interpretieren und verarbeiten muss. Die daraus entstehenden neuen Informationen müssen dann ebenfalls über das Netzwerk zu den einzelnen Clients gelangen und auch dort passend verarbeitet und dann dargestellt werden. 38 Konzept Durch die verschiedenen Datentypen ergibt sich eine Vielzahl von Nachrichten, welche für den jeweiligen Zweck unterschiedliche Aktionen mit verschiedenen Informationen auslösen sollen. Damit alle diese Anwendungsfälle möglichst einfach nutzbar werden, soll eine kommunikationsorientierte Middleware entwickelt werden, welche als Bindeglied zwischen den Applikationen auf den einzelnen Clients und dem Server fungieren soll. Dadurch soll das Verschicken, Empfangen und Verarbeiten der einzelnen Nachrichten so vereinfacht werden, dass die Netzwerkkommunikation aus Entwicklersicht in den Hintergrund rückt. Für eine solche Netzwerkschicht soll zunächst die mögliche Infrastruktur entwickelt werden, welche sich um das Versenden, Empfangen und Verarbeiten der Informationen kümmert. Hierbei wird eine Schnittstelle entwickelt, welche es dem Entwickler vereinfachen soll, die Daten zwischen dem Server und den Clients auszutauschen. Dazu wird auch der Ablauf der Kommunikation in dieser Schicht beleuchtet. Danach soll diese Infrastruktur erweitert werden, um zunächst zwei Arten von Informationen separat behandeln zu können: Ereignisse und Daten. Hierzu wird aufgezeigt, worin die Unterschiede zwischen diesen Informationen bestehen und welche Verarbeitung sie erfordern. Dabei wird auch beschrieben, wie andere Informationsarten hinzugefügt werden können und wie die resultierende Netzwerkkomponente aussieht. 5.2.1 Infrastruktur Für die grundlegende Infrastruktur sollen die beiden Seiten der Applikation, Server und Client, zunächst stark vereinfacht dargestellt werden, wie in Abbildung 5.2 zu erkennen ist. Es soll ausreichen, wenn die beiden Seiten nur die Elemente beinhalten, welche für das Empfangen und Verarbeiten von Nachrichten benötigt werden. Da diese Schritte auf beiden Seiten gleich ablaufen, sind die zwei dargestellten Klassen zunächst fast identisch. Der Server erhält jedoch bei eingehenden Nachrichten zusätzlich noch die Verbindung, über welche die Nachricht eingegangen ist. Die Unterschiede befinden sich in den jeweiligen Callbacks, welche in einer Map gesammelt werden. Hierbei verlinkt eine bestimmte Aktion im Format String auf eine Methode. Sobald eine Nachricht über die Verbindung eingeht, wird die Methode handle() aufgerufen und erhält die gewünschte Aktion sowie Schlüssel-Wert-Paare mit allen vorhandenen Daten. Diese Methode sucht dann den passenden Callback aus der Map und übergibt der darin festgelegten Callback-Methode die Daten. Diese Callback-Methode ist dann für die eigentliche Verarbeitung der Daten zuständig. Für die Kommunikation über das Netzwerk soll eine andere abstrahierte Klasse sorgen: der Verbindungs-Manager. Dieser hält, wie in Abbildung 5.3 zu erkennen ist, die einzelnen Verbindungen und kann über diese Nachrichten versenden. Hierbei Konzept 39 Abbildung 5.2: Server und Client besitzen für die Verarbeitung von Nachrichten die gleichen Elemente wird sichergestellt, dass alle Nachrichten zuverlässig übertragen werden. In der späteren Umsetzung besteht dieser Bereich aus mehreren Elementen, welche auf Server und Client aufgeteilt werden. Für die Darstellung des Verbindungsablaufs soll diese Abstraktion jedoch ausreichen. Abbildung 5.3: Der Verbindungs-Manager ist für den Nachrichtenaustausch zuständig und umfasst hier sowohl die Client- als auch die Serverseite Theoretisch könnte ein Client nun eine Nachricht über den Verbindungs-Manager an den Server schicken und dieser könnte auf dem gleichen Weg antworten. Jedoch soll das Verpacken der Informationen in eine Nachricht weder Aufgabe der Verbindung, noch von Server und Client sein. Auch das Entpacken der ankommenden Nachricht gehört nicht in die Aufgabenbereiche der bisherigen Elemente. Für diese beiden Aufgaben ist ein Bindeglied verantwortlich: der Nachrichten-Manager. Dieser wird einmal zwischen dem Client und der Verbindung sowie zwischen dem Server und der Verbindung eingebaut und übernimmt die oben beschriebenen Funktionalitäten, wie in Abbildung 5.4 zu erkennen ist. Client und Server können über diesen Manager Informationen versenden, indem sie die Methode send() mit einer Aktion als String und den jeweiligen Daten als Schlüssel-Wert-Paare aufrufen. Der Server gibt dabei noch die jeweilige Verbindung an, über welche die Nachricht geschickt werden soll – der Client hält hingegen nur eine. Der Manager wandelt diese Informationen dann in eine Nachricht um und leitet diese an den Verbindungs-Manager. Empfängt dieser Manager eine neue Nachricht, wird diese an den Nachrichten-Manager geleitet, welcher die Nachricht dann entpackt. Diese entpackten Informationen werden dann über einen Listener zum Front- beziehungsweise Backend geschickt, welches dann die passende Callback-Methode startet. 40 Konzept Abbildung 5.4: Der Nachrichten-Manager ver- und entpackt Informationen und leitet diese weiter Wie diese einzelnen Komponenten zusammenspielen, soll an einer einfachen Benutzerinteraktion und dem daraus resultierenden Ergebnis dargestellt werden. Der Benutzer soll dazu seinen Benutzernamen abfragen und diesen dann angezeigt bekommen, wie im Sequenzdiagramm in Abbildung 5.5 dargestellt ist. Hierzu interagiert der Benutzer zunächst mit dem Client und fordert den Benutzernamen an. Der Client übergibt dann die passende Aktion an den clientseitigen Nachrichten-Manager, welcher diese Information dann verpackt. Die daraus resultierende Nachricht wird dann vom Verbindungs-Manager an den serverseitigen Nachrichten-Manager geleitet, welcher daraus wieder die Informationen ausliest und diese an den Server weiterleitet. Hier wird dann aus der Map die passende Methode zur Verarbeitung der Aktion gesucht und gestartet. Abbildung 5.5: Der Benutzername wird über das Netzwerk transportiert Auf diese Weise können Nachrichten zwischen genau einem Server und einem Client ausgetauscht werden. Es soll aber auch die Möglichkeit bestehen, eine Nachricht vom Server an alle Clients verschicken zu können - hierzu dienen Broadcasts. Diese werden ebenfalls von der Netzwerk-Schnittstelle als Funktionalität zur Verfügung gestellt und mit einer Aktion und den jeweiligen Daten aufgerufen, wodurch simultan einzelne Nachrichten an alle Clients verschickt werden. Die Nachrichten werden clientseitig genauso behandelt, wie auch die direkten Nachrichten, jedoch ist es auf diese Weise möglich, alle Clients gleichzeitig mit Informationen zu versorgen. Konzept 5.2.2 41 Nachrichtentypen Durch die Interaktion der einzelnen Benutzer mit der Applikation entstehen sehr unterschiedliche Arten von Informationen, welche vom Benutzer zum Server übertragen, dort verarbeitet und dann wieder verteilt werden müssen. Dabei kann sich nur ein einzelner Zustand ändern oder aber große Datenmengen anfallen, welche an alle Benutzer verteilt werden müssen. Hier sollte auch die Middleware so angepasst werden, dass diese die verschiedenen Arten von Informationen in spezielle Nachrichten umwandelt und diese passend zu der jeweiligen Informationsart verschickt. Dabei werden zunächst zwei Arten von Nachrichten unterschieden: Ereignisse und Daten. Die erste Nachrichtenart enthält Informationen zu Zustandsänderungen, welche mit nur kleinen Datenmengen und ohne Verarbeitung schnellstmöglich verschickt und verarbeitet werden sollen. Die resultierenden Nachrichten sind atomar und liefern immer neue Informationen, ohne dabei von alten Zuständen abhängig zu sein. Beispiele im aktuellen Anwendungsfall wären das Schließen einer Spiellobby oder das Anwenden eines Extras. Für diesen Nachrichtentyp muss der bereits festgelegte Ablauf nicht verändert werden – der Nachrichten-Manager entspricht also in seiner Funktionsweise bereits dem Ereignis-Manager. Die Informationen werden bereits vollständig in eine Nachricht verpackt, direkt verschickt und verarbeitet. Dem gegenüber stehen die Datennachrichten, welche beliebig große Datenmengen zwischen den einzelnen Clients und dem Server austauschen sollen. Im Gegensatz zu den Ereignissen können diese Datenmengen auf einem vorhergehenden Datenstand aufbauen und diesen aktualisieren. Eine Ausnahme stellen die initialen Daten dar, welche jedoch aus Konsistenzgründen ebenfalls als Daten-Nachrichten übertragen werden sollen. Falls eine Aktualisierung vorgenommen werden soll, werden nur die Teile der Nachricht neu übertragen, die sich auch verändert haben – diese Art der Aktualisierung wird als Delta-Update bezeichnet. Dazu müssen immer zwei Datenstände vorliegen: zum einen der Datenstand mit den neuen Daten und zum anderen der zuletzt verschickte Datenstand, welcher den Zustand der Daten beim Ziel repräsentiert. Hat man diese beiden Versionen, kann man den dazwischen bestehenden Unterschied an Informationen berechnen und diesen so herausziehen, dass man nur die neuen Informationen zu übertragen braucht. Durch diese Methode kann man vor allem bei kleinen Aktualisierungen in großen Datenmengen Bandbreite und Übertragungszeit einsparen. Das dafür benötigte Delta wird von der Klasse Zipper erstellt und wieder entpackt, wie in Abbildung 5.6 dargestellt ist. Grundlage für diese Sequenz ist das Aktualisieren des Spielfeldes bei einem Benutzer welche an den Server geschickt werden soll. In diesem Fall werden nur wenige Elemente des Spielfeldes verändert – eine 42 Konzept teilweise Aktualisierung der Daten ist also dem erneuten Versenden aller Kugeln vorzuziehen. Hierzu sendet der Server zunächst das Spielfeld mit der jeweiligen Aktion an den serverseitigen Nachrichten-Manager, welcher in diesem Fall der Daten-Manager ist. Dieser verpackt die Informationen wieder in eine Nachricht, wobei jedoch das Spielfeld noch nicht gesetzt wird, da hier zunächst noch das Delta gebildet werden muss. Hierzu übergibt der Daten-Manager das aktuelle Spielfeld sowie den letzten Stand des Spielfeldes, welcher ebenfalls im Server verwaltet wird, an die Zipper-Klasse, welche daraus das Delta erstellt. Da alle Nachrichten zuverlässig zugestellt werden, hat der Server immer einen Datenstand, welcher dem Datenstand bei den jeweiligen Clients entspricht. Das erstelle Delta wird nun an die vorbereitete Nachricht angehängt. Sobald dies geschehen ist, wird die Nachricht über den Verbindungs-Manager an den clientseitigen Daten-Manager geschickt und von diesem verarbeitet – die Zuteilung zu dem passenden Manager erfolgt dabei über ein Flag in der Nachricht. Alle bei diesem Manager eingehenden Nachrichten beinhalten ein Delta, das zusammen mit dem letzten Datenstand wieder an eine Zipper-Klasse übergeben wird - dieses Mal jedoch zum Zusammenbauen des aktuellen Datenstands. Dieser wird dann an den Daten-Manager zurückgegeben und von diesem zum Client-Objekt gereicht, dort gespeichert und dem Benutzer angezeigt. Der Abgleich und die teilweise Aktualisierung der Daten läuft dabei immer synchron ab, um die Reihenfolge der Aktualisierungen zu wahren. Abbildung 5.6: Sequenzdiagramm für ein Delta-Update des Spielfeldes Es ist aber auch vorstellbar, dass alternative Anwendungsfälle andere Nachrichtentypen verlangen, welche ebenfalls auf beiden Seiten der Netzwerkschicht gleich behandelt und somit von der Middleware selbst abstrahiert angeboten werden sollen. Für eine solche Erweiterung bietet der Verbindungs-Manager auf beiden Seiten jeweils eine Schnittstelle an, an welche weitere Manager angehängt werden können. Dadurch soll er ermöglicht werden, neue Funktionalitäten beidseitig anzubinden und diese dann wiederum per Schnittstelle aus der Middleware heraus anzubieten. Konzept 43 Aus diesen Vorgaben entsteht die Aufteilung der Netzwerkschicht, wie sie in Abbildung 5.7 dargestellt ist. Hier wird bereits die klare Trennung in Server und Client beachtet, welche für zwei Verbindungs-Manager mit unterschiedlichen Funktionen sorgt. So bietet der Server einen Dienst über einen Port an, welchen der Client dann benutzen kann, um sich mit dem Server zu verbinden. Über die dann gebildete Datenverbindung können nun beide Seiten miteinander kommunizieren, in dem die Informationen von den jeweiligen Nachrichten-Managern verpackt und an die Verbindungs-Manager weitergeleitet werden. Abbildung 5.7: Die einzelnen Komponenten auf Server- und Client-Seite für den Verbindungsaufbau und den Nachrichtenaustausch 5.3 Datenverwaltung Nachdem nun eine Netzwerkinfrastruktur geschaffen ist, welche beliebige Daten vom Server zum Client und wieder zurück transportieren kann und diese Dienste so per Schnittstelle anbietet, dass der eigentliche Transport in den Hintergrund rückt, sollen jetzt die eigentlichen Objekte und Daten der Applikation betrachtet werden. Dazu werden in diesem Abschnitt zunächst die Spielelemente behandelt, welche die Grundlage des Spiels bilden und auf dem Server verwaltet werden. Auch die Anbindung dieser Elemente untereinander und an die Netzwerkschicht wird hier erläutert, während die Anbindung der Clients erst Bestandteil des nächsten Abschnitts ist. Einen Überblick über die Objekte des Servers wird in Abbildung 5.8 vermittelt. Der Server besteht, wie bereits im Abschnitt 5.1 erläutert, aus der Netzwerkschnittstelle und der Spiellogik. Die Spiellogik ist auch hier in die bereits vorgestellten Objekte unterteilt, wobei jedoch das Backend-Objekt die einzelnen Benutzer- und LobbyObjekte nicht mehr direkt, sondern über eine Listen-Klasse hält und verwaltet. Die 44 Konzept detaillierteren Beschreibungen der einzelnen Klassen und ihre Zusammenhänge werden nachfolgend behandelt. Abbildung 5.8: Vereinfachte Darstellung aller serverseitigen Spielobjekte und die jeweiligen Beziehungen dazwischen Konzept 45 Die Nahtstelle zwischen dem Netzwerk und dem Backend ist in Abbildung 5.9 dargestellt. Hierbei ist das Netzwerk-Interface die eigentliche Netzwerkanbindung, welches die Funktionalitäten der serverseitigen Manager aus dem vorherigen Abschnitt abstrahiert zur Verfügung stellt. Dazu gehört das Verschicken der beiden verschiedenen Nachrichtenarten an entweder einen oder alle Benutzer, wobei auch das Ziel für einen Broadcast, etwa eine Lobby, angegeben werden kann. An dieses Interface ist die Klasse Backend gebunden, welche die Nachrichten aus dem Netzwerk über einen Listener signalisiert bekommt und diese an die passenden Callback-Methoden weiterleitet. Diese Methoden befinden sich entweder direkt in der Klasse Backend oder gehören zu einem anderen Spielobjekt und werden vom Backend kombiniert und aufgerufen. Bei Aktualisierungen wird dann die passende Nachricht verschickt. Abbildung 5.9: Die Schnittstelle zur Netzwerkschicht und das daran angebundene Backend Um die Aktionen aus dem Netzwerk passend weiterleiten zu können, muss das Backend die beiden wichtigsten Spielobjekte halten, welche wiederum alle anderen Elemente des Spiels beinhalten: die Liste aller Benutzer und die Liste aller Lobbys. Diese beiden Listen werden jeweils in einer eigenen Klasse verwaltet und bieten neben den üblichen Gettern und Settern auch spezielle Methoden zur Datenmanipulation an. Für die Verwaltung der Benutzer ist die Benutzer-Liste zuständig, welche zusammen mit der Benutzer-Klasse in Abbildung 5.10 zu erkennen ist. Bei diesen sowie bei allen anderen Klassen und Objekten auf dem Server muss die Abstraktion klar erkennbar sein, da es sich hier beispielsweise nicht um den Benutzer selbst, sondern nur seine Repräsentation handelt. Damit es zu keiner Verwechslung kommt, werden alle hier die verwendeten Klassen und Objekte explizit als solche bezeichnet. Die Benutzer-Liste bietet Methoden zum Anfügen, Entfernen und Auslesen von Benutzer-Objekten, sowie zur Überprüfung, ob ein bestimmtes Benutzer-Objekt in der Liste enthalten ist. Die Benutzer werden über einen Benutzernamen identifiziert, 46 Konzept welcher beim erstmaligen Einfügen zufällig gesetzt und zurückgegeben wird. Dieser muss in der gesamten Applikation einmalig sein. Für den Fall, dass ein Benutzer von einer Lobby in ein Spiel gelangt, steht eine Methode zum Erstellen der einzelnen Spielelemente bereit, welche später detailliert erläutert werden. Zusätzlich kann das Benutzer-Objekt dann auch eine neue Kugel zum jeweiligen Spielfeld hinzufügen, den aktuellen Stand seines Spielfeldes liefern, das erste Extra aus dem eigenen Inventar holen oder sich selbst ein neues aktives Extra hinzufügen. Abbildung 5.10: Die Benutzer werden als Objekt abstrahiert und in einer BenutzerListe verwaltet Die Benutzer-Liste wird jedoch nicht nur einmalig vom Backend gehalten und verwaltet, sondern findet auch Verwendung bei den Spiellobbys und hält in diesen ebenfalls alle Benutzer-Objekte, welche sich gerade in einer Lobby befinden. Diese Lobbys wiederum werden von einer Listen-Klasse verwaltet, wie in Abbildung 5.11 zu sehen ist. Die Klasse bietet dabei dem Backend Funktionalitäten an, um die Liste passend manipulieren zu können. So können neue Lobby-Objekte an die Liste angefügt oder über den Ersteller beziehungsweise den Lobbynamen wieder entfernt werden. Außerdem kann das Backend auch gezielt nach einem LobbyObjekt suchen. Für die Ausgabe der gesamten Lobbyliste wird außerdem eine Methode bereitgestellt, welche aus den Lobby-Objekten die Namen ausliest, damit diese als Liste von Strings an die Clients geschickt werden können. Die Lobbys selbst werden durch einen Namen identifiziert, wobei auch hier jeder Name einmalig in der Applikation ist. Außerdem hält jede Lobby neben der Liste von Benutzern auch den Ersteller der Lobby, da dieser mehr Privilegien im Lobbybetrieb genießt. Während der Ersteller für die Dauer der Lobby fest ist und nur einmalig beim Erstellen gesetzt werden kann, können sich die anderen Teilnehmer frei bewegen. Hierzu werden die Methoden der Listen-Klasse übernommen und erweitert, damit das Backend direkt auf den Daten arbeiten kann. Die Erweiterung dient dazu, dass beim Entfernen eines Benutzers geprüft wird, ob dieser der Ersteller der Lobby ist und die Lobby noch nicht ins Spiel übergeführt wurde, da in Konzept 47 Abbildung 5.11: Lobbys werden in einer Lobby-Liste verwaltet und können zu einem Spiel erweitert werden diesem Fall die Lobby entfernt werden muss. Startet der Ersteller das Spiel, wird dies im Lobby-Objekt durch das Setzen einer Flag gesichert. Die anderen Daten der Lobby werden nicht verändert, lediglich die Überprüfungen beim Manipulieren der Lobby werden somit dem aktuellen Zustand angepasst. Außerdem gibt es auch bei dieser Klasse die Möglichkeit, von allen aktuellen Teilnehmern die Benutzernamen auszulesen und in eine Liste von Strings zusammenzufügen, damit diese für die clientseitige Darstellung genutzt werden kann. Befindet sich der Benutzer in einem Spiel, so erhält das Benutzer-Objekt weitere Elemente, welche die spielrelevanten Objekte darstellen. Eine Übersicht dieser Spielobjekte ist in Abbildung 5.12 sichtbar. Dabei werden alle Klassen von dem Benutzer-Objekt gehalten und verwaltet. Zunächst hat jeder Benutzer ein eigenes Spielfeld, welches wiederum aus Kugeln besteht, wobei die Kugeln hier immer nur ein Teil des Spielfeldes sind. Dazu wurde auch der Kugelvorrat abstrahiert – dieser ist nur noch eine Zahl im Benutzer-Objekt, welche die Anzahl der verbleibenden Kugeln darstellt, wobei die Farbe der jeweils nächsten Kugel separat als Zahl abgespeichert wird. Die Kugel-Objekte selbst werden immer zufällig erzeugt, wenn der Spieler die nächste Kugel zum Abschuss benötigt oder eine neue Reihe eingefügt wird. Schießt der Spieler eine Kugel ab und bleibt diese an einer Position hängen, wird an eben dieser Position ein Kugel-Objekt im Spielfeld-Objekt eingefügt. Nach dem Einfügen der Kugel, wird die benachbarte Kugelmenge durchlaufen und entfernt, wenn mindestens drei gleichfarbige Kugeln zusammenkommen. Dabei werden auch schwebende Kugeln und Extra abgebaut, wobei letztere wieder zum Benutzer-Objekt gelangen und von diesem in sein Inventar gelegt werden. Baut der Benutzer außerdem mehr als fünf Kugeln oder mehr als fünf schwebende Kugeln ab, wird dem Spielfeld ein neues Extra ange- 48 Konzept Abbildung 5.12: Im Spiel wird das Benutzer-Objekt um ein Spielfeld und ein Inventar erweitert fügt. Außerdem bietet die Klasse auch Methoden zum Hinzufügen und Entfernen ganzer Kugelreihen an, sowie zum vollständigen Bereinigen des Spielfeldes, was beispielsweise beim Ende des Spiels geschieht. Andererseits hat der Spieler auch ein Inventar, in dem er Extras sammeln und diese benutzen kann. Dazu hat das Benutzer-Objekt eine Instanz der InventarKlasse, welche beliebig viele Extra-Objekte halten kann. Extras sind in diesem Fall Erweiterungen der Kugel-Klasse um einen Typ, welcher die Aktion festlegt, die beim Benutzen des Extras angewendet wird. Hierzu können neue Extra-Objekte ans Ende des Inventars angehängt und von vorne wieder ausgelesen werden. Sobald beim Server ein Extra ankommt, wird dieses vom Benutzer-Objekt angewendet. Bei Extras, welche das Spielfeld manipulieren, wird die Aktion auf das Spielfeld-Objekt des Benutzers angewandt. Bei anhaltenden Effekten mit einer Wirkdauer, wird das Extra an den jeweiligen Spieler geschickt und im Frontend angewendet. Konzept 5.4 49 Struktur der Clients Während der Server die gesamten Informationen der Applikation hält, manipuliert und über das Netzwerk zur Verfügung stellt, befinden sich auf der anderen Seite der Verbindung die einzelnen Clients, welche die Manipulation der Daten steuern. Diese basieren auf den in Abschnitt 5.1 beschriebenen Elementen Netzwerk, Anzeigelogik, Zwischenspeicher und GUI. Wie jedoch in Abbildung 5.13 zu erkennen ist, wurde der Zwischenspeicher in die einzelnen Spielelemente aufgeteilt, um diese so besser verwalten zu können. Auch die GUI wurde um eine Klasse erweitert, welche die Bewegungen der aktiven Kugel steuert. Abbildung 5.13: Die vereinfachten Klassen des Clients mit den jeweiligen Beziehungen und der Anbindung an die Netzwerkschicht 50 Konzept Die Clients werden über das Netzwerk mithilfe der bereits vorgestellten und im Backend genutzten Netzwerkschicht und deren Schnittstelle an den Server angebunden, wie in Abbildung 5.14 dargestellt ist. Der grundlegende Aufbau ähnelt dabei dem Server: Nachrichten können über die Netzwerk-Schnittstelle versendet und empfangen werden und gelangen dann in die Frontend-Klasse, welche die passende Callback-Funktion aufruft. Der essentielle Unterschied besteht jedoch darin, dass die Clients nicht nur auf Nachrichten reagieren, wie es der Server tut, sondern selbstständig neue Informationen erzeugen und diese verschicken können. Diese neuen Informationen werden durch Benutzereingaben erzeugt und vom FrontendObjekt verschickt. Solche Eingaben sind dabei die Aktionen, die der Spieler im Spiel tätigen kann – dazu gehören das Beitreten, Erstellen und Verlassen einer Lobby, das Entfernen eines Benutzers aus der eigenen Lobby sowie die Handlungen im Spiel: einen neuen Ball zum Spielfeld hinzufügen und das erste Extra aus dem Inventar auf einen Spieler anwenden. Abbildung 5.14: Das Frontend ist ebenfalls an die Netzwerk-Schnittstelle angebunden, kann aber nicht nur auf Nachrichten reagieren, sondern auch neue Informationen aus Benutzereingaben versenden Die Clients müssen jedoch auch die Objekte vom Backend passend speichern, aktualisieren und ausgeben können. Dabei werden hier die beiden Oberklassen Benutzer und Lobby benötigt, welche jedoch nur der Datenhaltung und der Datenaktualisierung vom Server aus dienen, jedoch nicht direkt im Client manipuliert werden können. Wie in Abbildung 5.15 zu erkennen ist, besitzt der Benutzer clientseitig viele Objekte vom Server als einfache Variablen innerhalb der Klasse. Neben dem Benutzernamen als String werden hier auch das Spielfeld, die verbleibende Kugelzahl im aktuellen Kugelvorrat, das Inventar und die aktiven Extras verwaltet. Bis auf das letzte Element besitzen alle anderen nur Methoden zum Setzen und Auslesen der Daten. Konzept 51 Abbildung 5.15: Die Klasse des Benutzers hält alle Informationen zum aktuellen Benutzer sowie die aktiven Extras Extras hingegen liegen als eigenes Objekt vor und werden in einer Liste gesammelt und verwaltet, da die aktiven Extras nur clientseitig angewendet werden. Hierzu werden neue Extras, welche über das Netzwerk im Client ankommen, an das Benutzer-Objekt weitergeleitet und hier in die Liste von Extras angefügt. Für die Aktualisierung der verbleibenden Zeiten sorgt ein Timer, welcher im Sekundentakt die dafür benötigte Methode im Benutzer-Objekt anstößt. Hier werden die abgelaufenen Extras ebenfalls entfernt. Neben den Informationen zum aktuellen Benutzer müssen auch alle relevanten Informationen für die aktuelle Lobby gehalten werden, in der sich der Benutzer befindet. Hierzu gehört zum einen die Angabe, ob der Benutzer der Ersteller der Lobby ist, da ihm hierdurch mehr Funktionen eingeblendet werden. Zum anderen zählen hierzu aber auch die Namen und Spielfelder der anderen Benutzer in der Lobby. Die Verwaltung dieser Daten übernimmt die Klasse Lobby aus Abbildung 5.16. Da dem aktuellen Benutzer jedoch nur der Benutzername und die Spielfelder, nicht aber die aktiven Extras oder das Inventar, von den anderen Spielern angezeigt werden sollen, speichert das Lobby-Objekt auch nur diese Informationen. Dabei reichen hier ebenfalls Getter und Setter aus. Abbildung 5.16: Die clientseitige Lobby-Klasse hält Informationen zu der aktuellen Lobby und deren Teilnehmern Für die Darstellung der einzelnen Objekte sowie für die Anbindung der Benutzerinteraktionen wird noch eine weitere Schicht oberhalb der Datenschicht im Client gesetzt. Da die eigentliche Darstellung im Browser durch HTML zustande kommt, 52 Konzept wird eine Klasse benötigt, welche die einzelnen Elemente passend aktualisiert. Hierzu dient die GUI-Manager-Klasse aus Abbildung 5.17, welche vor allem zwei Bereiche abdecken muss. Abbildung 5.17: Die Aktualisierung der Anzeige sowie das Abfangen von Benutzereingaben übernimmt der GUI-Manager Zum einen fängt diese Klasse Benutzerinteraktionen ab und leitet diese passend weiter. Dafür stehen die beiden Event-Listener für Maus- und Tastatureingaben zur Verfügung, welche entweder den Zahlenwert der gedrückten Taste oder aber die Position des Mausklicks auswerten. Diese Informationen werden dann, je nach Bedeutung der einzelnen Eingaben, an die passende Methode im Frontend-Objekt geleitet und von dort an den Server verschickt. Eine Ausnahme stellt dabei das Verschießen und Bewegen einer Kugel bis zum Zeitpunkt des Auftreffens auf andere Kugeln beziehungsweise den oberen Spielfeldrand dar. Diese Bewegung wird vollständig auf Clientseite berechnet und angezeigt, weil eine serverseitige Berechnung und die ständige Übertragung der aktualisierten Position über das Netzwerk zu aufwändig wären. Deshalb wird die Kugel clientseitig bewegt und erst die Position des Auftreffens wird an den Server übertragen. Neben dem Verarbeiten von Benutzereingaben sorgt diese Klasse aber auch für die Visualisierung der aktuellen Daten. Zum einen können dazu die einzelnen Bereiche der Applikation eingeblendet oder auch versteckt werden, welche dann die für diesen Bereich passenden Daten anzeigen. Sobald neue Daten über das Netzwerk eingehen, werden diese in den passenden Objekten abgelegt und der GUI-Manager wird über die Aktualisierung informiert. Je nach übergebenem Typ werden dann die passenden Information gespeichert und dem Benutzer in den jeweiligen Elementen angezeigt. 5.5 Ablauf der Kommunikation Bevor die entstandenen Klassen implementiert werden, muss noch geklärt werden, wie die einzelnen Objekte miteinander interagieren, um die verschiedenen Benutzerinteraktionen, wie sie in Abschnitt 4.1 aufgelistet wurden, umsetzen zu können. Hierzu sollen die wichtigsten Use-Cases durchlaufen und die dabei genommenen Konzept 53 Wege vom Benutzer zum Server und wieder zurück aufgezeigt werden. Hierbei wird die Netzwerkschicht ebenfalls abstrahiert dargestellt, da der Ablauf innerhalb dieser immer dem bereits demonstrierten Schema aus Abschnitt 5.2 entspricht. Zunächst soll der Benutzer die Applikation starten und zum Hauptmenü gelangen, in dem er dann seinen Benutzernamen und eine Liste der aktuellen Spiellobbys sehen kann. Hierzu wird die Applikation beim Client gestartet, wie in Abbildung 5.18 dargestellt ist. Dabei werden von dem GUI-Manager aus alle weiteren Klassen erzeugt und das Hauptmenü angezeigt. Sobald die Netzwerk-Schnittstelle erzeugt wurde, verbindet diese sich automatisch mit dem Server. Abbildung 5.18: Der Client instanziiert vom GUI-Manager aus alle weiteren Klassen und verbindet sich zum Server Serverseitig geht daraufhin ein Ereignis für einen neuen Benutzer ein, wie in Abbildung 5.19 zu sehen ist. Zunächst fügt das Backend-Objekt ein neues Benutzer-Objekt in der Benutzer-Liste ein. Dabei wird ein zufälliger, einmaliger Name erzeugt und über die Netzwerk-Schnittstelle als Ereignis mit dem Benutzernamen als Datensatz an das Frontend gesendet. Danach wird noch eine weitere Aktion gestartet, welche dem Benutzer alle aktuellen Lobbys liefert. Hierzu holt sich das BackendObjekt von der Lobby-Liste alle aktuelle Lobbynamen und sendet diese über die Netzwerk-Schnittstelle als Daten-Nachricht an den Client. Beim Client müssen nun beide Informationen vom Server verarbeitet werden. Zunächst wird dazu der Benutzername gesetzt, wie es in Abbildung 5.20 sichtbar ist. Dazu übergibt die Netzwerk-Schnittstelle die Aktion mit dem Benutzernamen als Datensatz an das Frontend-Objekt. Dieses speichert den Namen ab und informiert den GUI-Manager über die Aktualisierung. Dieser holt sich nun über das Frontend-Objekt den gespeicherten Benutzernamen aus dem Benutzer-Objekt und zeigt diesen an. Dieser Ablauf wird immer beim Erhalt eines neuen Benutzernamen durchlaufen, also auch, wenn der Benutzer selbst einen Namen festlegt. Hierbei sendet der Client diesen Benutzernamen an den Server, welcher dann überprüft, ob 54 Konzept Abbildung 5.19: Der Server fügt den neuen Benutzer in die Liste und schickt ihm seinen Benutzernamen und die Lobby-Liste der Name zulässig ist. Falls das der Fall ist, wird der neue Name zurückgeschickt, andernfalls der alte. Auch bei anderen Aktionen, in denen sich ein Element im Benutzer-Objekt verändert, wird dieses Muster durchlaufen – hierzu gehört das Aktualisieren der verbleibenden Kugeln und des Inventars. Abbildung 5.20: Sobald der Client einen neuen Benutzernamen erhält, wird dieser gespeichert und angezeigt Auch die Liste der aktuellen Lobbys wird auf einem ähnlichen Weg gespeichert. Die Liste selbst wird wieder über die Netzwerkschnittstelle an das Frontend-Objekt gereicht, welches sie dann abspeichert. Danach wird dem GUI-Manager auch hier eine Aktualisierung signalisiert, woraufhin dieser sich die Liste abholt und anzeigt. Die Aktualisierung geschieht jedoch nicht nur beim ersten Verbinden, sondern auch danach bei jeder Änderung der Lobby-Liste – etwa beim Erstellen oder Schließen von Lobbys. Dabei werden die Informationen unabhängig vom aktuellen Zustand der Applikation aktualisiert, wodurch die Clients immer aktuelle Daten zur Verfügung haben, auch wenn diese dem Benutzer nicht angezeigt werden müssen. Dies stellt sicher, dass bei einem Zustandswechsel alle relevanten Informationen direkt Konzept 55 zur Verfügung stehen und der Server die einzelnen Clients bei Broadcasts nicht unterscheiden muss. Aus dieser Liste kann der Benutzer nun einen Eintrag auswählen und diesem Beitreten. Hierbei wird passend zu dieser Aktion eine Nachricht an den Server geschickt, wie in Abbildung 5.21 dargestellt ist. Dazu liest der GUI-Manager den Namen der ausgewählten Lobby direkt aus dem Element aus und sendet diesen an das Frontend-Objekt. Dieser Name wird zusammen mit der passenden Aktion über die Netzwerk-Schnittstelle als Ereignis an den Server geschickt. Ebenfalls nach diesem Prinzip verläuft auch das Erstellen, Verlassen und Starten einer Lobby, sowie das Entfernen eines Benutzers als Ersteller einer Lobby ab. In allen Fällen wird ein Ereignis an den Server geschickt, welches dieser je nach Aktion und Daten passend verarbeiten muss. Abbildung 5.21: Bei einfachen Benutzerinteraktionen werden die Daten direkt an den Server geschickt Das Betreten einer Lobby wird in Abbildung 5.22 behandelt. Hierbei wird das Backend-Objekt über die Netzwerkschnittstelle mit der Verbindung des Benutzers, welcher die Nachricht geschickt hat, der Aktion und dem Lobbynamen als Datensatz aufgerufen. Danach holt sich diese Klasse aus der Liste der Benutzer das Objekt mit der gesetzten Verbindung. Mittels dieses Benutzer-Objekts kann nun auch das passende Lobby-Objekt geladen werden, in dem sich der Benutzer aktuell befindet. Danach wird die Instanz des Benutzers hinzugefügt und eine Nachricht über den erfolgreichen Beitritt in die Lobby über die Netzwerk-Schnittstelle an den Client zurückgeschickt. Außerdem wird auch die aktualisierte Liste der Benutzernamen in dieser Lobby an alle Teilnehmer verschickt. Abbildung 5.23 zeigt auf, was beim Beitreten einer Lobby clientseitig geschieht. Das Ereignis kommt als Nachricht über die Netzwerk-Schnittstelle mit dem Namen der beigetreten Lobby an. Das Frontend-Objekt erstellt daraufhin eine neue Lobby mit diesem Namen und gibt dabei gleich mit an, ob der aktuelle Benutzer der Ersteller der Lobby ist. Da der Benutzer in diesem Fall einer Lobby beigetreten ist, kann er nicht der Ersteller sein. Sobald das Lobby-Objekt erstellt ist, wird eine Aktualisierung an den GUI-Manager signalisiert, woraufhin dieser zur Spiel-Ansicht 56 Konzept Abbildung 5.22: Beim Eintreten in eine Lobby wird die Teilnehmer-Liste aktualisiert und verschickt wechselt. Sollte der Benutzer eine Lobby erstellt haben, so erhält der Client eine ähnliche Nachricht nur mit einer anderen Aktion, woraufhin bei der Erstellung des Lobby-Objekts der aktuelle Benutzer als Ersteller markiert wird. Abbildung 5.23: Nach dem Erstellen des Lobby-Objekts gelangt der Spieler in die Spiel-Ansicht Sobald der Benutzer einer Lobby beitritt, erhält er auch die aktuelle Liste aller Benutzer in dieser Lobby, welche dann im Client gespeichert wird. Dabei kommt die Benutzer-Liste, wie in Abbildung 5.24 zu sehen ist, über die Netzwerk-Schnittstelle zum Frontend-Objekt, welches die Liste im Lobby-Objekt abspeichert und danach dem GUI-Manager eine Aktualisierung mitteilt. Dieser holt sich die Benutzer-Liste Konzept 57 und zeigt sie an. Diese Sequenz wird bei allen Veränderungen der Teilnehmer innerhalb der Lobby durchlaufen. Abbildung 5.24: Die Benutzerliste wird im Lobby-Objekt gespeichert und vom GUI-Manager angezeigt Wenn der Ersteller der Lobby mit den Teilnehmern zufrieden ist, kann er die Lobby zum Spiel überführen. Hierzu kann er über einen Klick ein Ereignis an den Server schicken, welches nach dem gleichen Muster wie auch das Beitreten einer Lobby (siehe Abbildung 5.21) abläuft. Die serverseitige Verarbeitung ist in Abbildung 5.25 dargestellt. In diesem Fall liefert die Netzwerk-Schnittstelle wieder das Ereignis an das BackendObjekt, welches sich das Benutzer-Objekt aus der Benutzer-Liste holt, welches das Ereignis abgeschickt hat. Der Benutzername wird dann genutzt um das LobbyObjekt zu holen, in dem sich der aktuelle Benutzer befindet. Falls der Benutzer der Ersteller der Lobby ist, wird dieses Lobby-Objekt gestartet. Hierzu werden alle Benutzer-Objekt in der Benutzer-Liste des Lobby-Objekts durchlaufen, wobei bei jedem Eintrag das Spiel gestartet wird. Hierbei erhält die Instanz des Benutzers ein neues Spielfeld, welches initial mit acht Kugelreihen mit jeweils 17 zufälligen Kugeln gefüllt wird. Außerdem wird dem Benutzer noch eine neue Inventar-Instanz angefügt, sowie die Kugelfarbe für die erste bewegliche Kugel zufällig festgelegt. Nach diesem Setup wird ein Broadcast an alle Spieler in der Lobby über den bevorstehenden Spielstart verschickt. Danach holt sich das Backend-Objekt eine vereinfachte Darstellung aller Spielfelder, durchläuft diese Liste und verschickt jedes Spielfeld mit dem Namen des Benutzers an alle Benutzer im Spiel. Danach wird jedem Spieler noch die Farbe seiner ersten beweglichen Kugel mitgeteilt. 58 Konzept Abbildung 5.25: Beim Starten des Spiels erhält jeder Spieler ein neues Spielfeld, Inventar und die Farbe der ersten beweglichen Kugel. Die Spielfelder und die Kugelfarbe werden danach verschickt Konzept 59 Die einzelnen Spielfelder werden clientseitig nach dem Muster aus Abbildung 5.26 behandelt. Nachdem die Netzwerk-Schnittstelle die Informationen an das BackendObjekt gegeben hat, speichert dieses das Spielfeld mit dem Benutzernamen im Lobby-Objekt. Danach benachrichtigt das Backend-Objekt den GUI-Manager über die Aktualisierung und übergibt dabei den Benutzernamen des jeweiligen Nutzers, bei dem sich das Spielfeld verändert hat. Der GUI-Manager holt sich die Informationen aus dem Lobby-Objekt und aktualisiert das jeweilige Spielfeld. Diese Aktualisierung läuft auch in allen anderen Fällen, in denen sich ein Spielfeld verändert, so ab – also auch beim Hinzufügen einer Kugel und beim Benutzen eines Extras. Abbildung 5.26: Das Spielfeld wird im Lobby-Objekt gespeichert und die Anzeige über die Spielerposition aktualisiert Die erste bewegliche Kugel wird ebenfalls über die Netzwerk-Schnittstelle und das Frontend-Objekt an den GUI-Manager geleitet, welcher eine neue Kugel mit der erhaltenen Farbnummer erzeugt. Diese Kugel bleibt solange an ihrem Startpunkt, bis der Benutzer auf das Spielfeld klickt und den Abschuss auslöst, welcher in Abbildung 5.27 dargestellt ist. Zuerst wird die Entfernung vom Ursprung der Kugeln zu der geklickten Stelle berechnet und auf eine Geschwindigkeit normiert, sodass sich alle Kugeln mit der gleichen Geschwindigkeit bewegen. Danach wird die Instanz der beweglichen Kugel in diese Richtung gestartet und erhält außerdem noch das Frontend-Objekt als Referenz, um über dieses das eigene Spielfeld auslesen zu können. Bei jedem Aufruf der Bewegungs-Methode wird zunächst die Kugelposition mit der Bewegungsrichtung aktualisiert. Danach holt sich das Kugel-Objekt über das Frontend-Objekt den aktuellen Spielernamen aus dem Benutzer-Objekt, um dann damit das eigene vereinfachte Spielfeld laden zu können. Auf diesem Spielfeld wird nun überprüft, ob die bewegliche Kugel auf eine andere Kugel auf dem Spielfeld getroffen ist. Da sich das Spielfeld auch während des Fluges beispielsweise durch die Benutzung 60 Konzept Abbildung 5.27: Die bewegliche Kugel wird nur clientseitig bewegt – der Server erhält erst die finale Position von Extras verändern kann, muss bei jedem Durchlauf das aktuelle Spielfeld zum Abgleich genutzt werden. Danach wird die neue Position im GUI-Manager aktualisiert, sodass der Spieler sie auch sehen kann. Ist die Kugel auf eine Kugelgruppe oder den oberen Spielfeldrand getroffen, so wird die finale Position der Kugel vom Frontend-Objekt als Ereignis an die Netzwerk-Schnittstelle geschickt. Das Setzen der neuen Kugel läuft nach dem in Abbildung 5.28 dargestellten Muster ab. Zunächst erhält das Backend-Objekt das Ereignis und holt sich das passende Benutzer-Objekt zu der Verbindung. Danach wird die finale Position der Kugel an das Objekt übergeben, welches wiederum diese Daten zusammen mit der Nummer der Kugelfarbe an die Instanz des Spielfeldes weiterleitet. Hier wird zunächst eine neue Kugel erzeugt und an dieser Position in das Spielfeld eingefügt. Konzept 61 Abbildung 5.28: Das serverseitige Hinzufügen einer Kugel kann verschiedene Konsequenzen haben 62 Konzept In diesem Ablauf kann es passieren, dass die anzufügende Kugel außerhalb des Spielfelds liegt und der Benutzer somit verloren hätte. In diesem Fall würde der aktuelle Ablauf zunächst im Feld- und dann im Benutzer-Objekt sofort beendet werden, woraufhin das Backend-Objekt eine negative Rückmeldung bekommt und somit erkennt, dass der Benutzer das Spiel verloren hat. Dieses Szenario soll jedoch separat beschrieben werden – in dem aktuellen Fall liefert das Anfügen der Kugel eine positive Antwort. Nachdem die Kugel angefügt wurde, wird als nächstes die Größe der Kugelgruppe an der neuen Kugel ermittelt, bei der alle Kugeln die gleiche Farbe haben wie die neue. Falls diese Gruppe aus weniger als drei Elementen besteht, wird der Kugelvorrat um eins reduziert. Dies kann dazu führen, dass dieser danach null ist. In diesem Fall wird eine neue Reihe angefügt, wobei auch hier wieder überprüft wird, ob eine Kugel aus dem Spielfeld herausläuft – sollte dies der Fall sein, verliert der Spieler. Andernfalls wird der Kugelvorrat wieder aufgefüllt. Sollte hingegen die Kugelgruppe mehr als drei gleichfarbige Kugeln enthalten, werden diese entfernt. Hierbei wird die Anzahl der entfernten Kugeln gespeichert, da bei mehr als fünf entfernten Kugeln eine zufällige Kugel auf dem Spielfeld zu einem zufälligen Extra aufgewertet wird. Dies ist auch der Fall, wenn beim Entfernen von schwebenden Kugeln mehr als fünf Kugeln abgebaut werden. Beim Abbauen können außerdem auch Extras entfernt werden, welche in einer Liste gesammelt zum Benutzer-Objekt zurückgegeben werden – dieses fügt die Extras nach dem Entfernen zu seinem Inventar-Objekt hinzu. Da der Benutzer noch nicht verloren hat, erhält das Backend-Objekt eine positive Antwort und versendet als nächstes die aktualisierten Daten. Zunächst holt es sich den Namen der Lobby in dem sich der Spieler befindet und versendet dann an alle Teilnehmer das Spielfeld des Benutzers zusammen mit seinem Namen. Im Client erfolgt die Aktualisierung dabei wie bereits in Abbildung 5.26 dargestellt. Da sich außerdem auch das Inventar des Spielers verändert haben kann, erhält dieser hierfür eine Aktualisierung, welche im Client nach dem Muster aus Abbildung 5.20 verarbeitet wird. Das Benutzen eines Extras aus dem Inventar wird clientseitig ebenfalls direkt als Ereignis an den Server geschickt, wie es auch bei dem Beitreten einer Spiellobby der Fall ist. Der Server verarbeitet die Nachricht passend zum Typ des Extras, welches der Benutzer zuerst in seinem Inventar hat. Verändert das Extra das Spielfeld eines Benutzers, wird dieses Spielfeld passend manipuliert und alle Benutzer erhalten die neue Version des Spielfeldes. Handelt es sich hingegen um ein aktives Extra, wird dieses an den jeweiligen Client geschickt, welchen der Benutzer beim Versenden des Extras ausgewählt hat. In beiden Fällen erhält der initiierende Benutzter eine aktualisierte Version seines Inventars. Wurde ein aktives Extra an einen Benutzer verschickt, so verläuft die clientseitige Verarbeitung wie in Abbildung 5.29 dargestellt. Dem Benutzer-Objekt wird zunächst Konzept 63 ein neues Extra mit einer Wirkdauer hinzugefügt. Falls noch kein Timer-Objekt läuft, wird eins gestartet, welches die verbleibenden Zeiten der einzelnen Extras im Sekundentakt wieder verringert. Dieses Verringern und das daraus resultierende Aktualisieren der Anzeige finden solange statt, wie es noch aktive Extras gibt. Abbildung 5.29: Erhält der Benutzer aktive Extras, werden diese im Client gehalten und aktualisiert Durch das Abschießen von Kugeln und das Benutzen von Extras kommt es dazu, dass die Spielfelder der einzelnen Spieler überfüllt werden. In diesem Fall werden die bei Abbildung 5.28 beschriebenen alternativen Routen abgelaufen. Hierbei wird das Spielfeld im Benutzer-Objekt geräumt und alle Mitspieler erhalten ein Ereignis, welches sie über das Ausscheiden eines Mitspielers informiert. Clientseitig wird dazu das Spielfeld bei den anderen Teilnehmern ebenfalls geräumt und Extras, welche zu diesem Spieler geschickt werden, kommen ungenutzt zurück. Der ausgeschiedene Benutzer kann dann zwar nicht mehr am Spiel teilnehmen, sieht aber noch die Spielfelder der anderen Mitspieler, bis er das Spiel verlässt. Sobald es nur noch einen verbleibenden Spieler gibt, erhält dieser die Mitteilung über seinen Sieg ebenfalls durch ein Ereignis. Nachdem das Spiel vorbei ist, können alle Spieler dieses verlassen, ins Hauptmenü zurückkehren und dort wieder eine neue Lobby erstellen. 64 Konzept Kapitel 6 Implementierung Die bisher gesammelten Anforderungen und das daraus resultierende Konzept bildet nun die Grundlage für die Implementierung. Dazu wird zunächst eine passende Technologie ausgewählt, mit der sich das hier präsentierte Design am besten umsetzen lässt. Danach soll auf Basis dieser Technologie damit begonnen werden die einzelnen Bereiche der Applikation umzusetzen, wobei dazu zunächst die Netzwerkschicht, dann der Server- und zuletzt der Clientpart beschrieben werden. 6.1 Auswahl der Technologien Da die Applikation clientseitig in einem Browser laufen soll, bietet sich die Nutzung von HTML an, da diese Sprache von allen Webbrowsern interpretiert werden kann. Um die Elemente der Anzeige steuerbar zu machen, wird außerdem auf JavaScript und jQuery1 gesetzt, wobei letzteres einige Vereinfachungen bietet und Fallbacks für ältere Browser bereitstellt. Dadurch ist es möglich die gesamte Applikation in einer Seite ablaufen zu lassen und passend auf die einzelnen Aktionen des Benutzers oder Aktualisierungen aus dem Netzwerk reagieren zu können. Für die Kommunikation mit dem Server soll primär auf das Websocket-Protokoll gesetzt werden, da dieses zum aktuellen Stand die am meisten verbreitete und bei Datenübertragungen auch schnellste Technologie im Web-Kontext ist, welche außerdem auch die Kommunikation in Echtzeit unterstützt. Für die serverseitige Umsetzung soll hierbei node.js eingesetzt werden. Dieses Framework basiert auf JavaScript und ist eine ideale Grundlage zur Entwicklung von Serverstrukturen. Hierzu reagiert es auf Ereignisse und kann passend dazu die jeweiligen Aktionen ausführen. Außerdem ist das Framework vollständig skalierbar und passt sich 1 http://jquery.com/ 65 66 Implementierung automatisch an die aktuelle Netzwerklast an. Zwar unterstützt node.js selbst nicht das Websocket-Protokoll, jedoch können Erweiterungen diese Funktionalität hinzufügen. Eine einfache Implementierung des Websocket-Protokolls bietet die bereits in Abschnitt 3.5 erwähnte Implementierung „WebSocket Node“ von Brian McKelvey. Mit dieser wäre bereits ein großer Teil der modernen Browser, auf Desktop-Computern sowie auch im mobilen Bereich, in der Lage mit dem Server kommunizieren zu können. Damit aber auch alle anderen Browser die Applikation nutzen können, wird die Erweiterung Socket.IO genutzt. Dieses Framework implementiert verschiedene Fallbacks für nahezu alle Browser und bietet diese über eine einzige Schnittstelle an, welche unabhängig von der Technologie angesprochen werden kann. Außerdem bietet es auch eine erweiterte Verwaltung der einzelnen Verbindung an: man kann Benutzer direkt benachrichtigen, einen globalen Broadcast an alle Benutzer oder einen Broadcast an eine zuvor festgelegte Gruppe von Benutzern verschicken. Als Technologien auf Seite der Clients werden somit HTML und CSS zur Visualisierung und JavaScript zusammen mit jQuery und dem clientseitigen Skript von Socket.IO zur Datenhaltung und -manipulation genutzt. Der Server basiert auf node.js mit dem Framework Socket.IO. 6.2 Netzwerkschicht Die Netzwerkschicht sorgt für die Übertragung der Daten zwischen den beiden Applikationshälften und ist deshalb das Element, welches zuerst beschrieben wird. Zunächst geht es dabei um den Aufbau einer Verbindung und welche Schritte dabei zum Versenden einer Nachricht nötig sind. Danach werden spezielle Formen des Nachrichtenversands erläutert, beispielsweise das Verschick einer Nachricht an alle Benutzer einer zuvor festgelegten Benutzergruppe. Nachdem die verschiedenen Versandtechniken geklärt sind, werden auch die verschiedenen Nachrichtenarten beleuchtet. Hierbei spielt vor allem die teilweise Aktualisierung eines Objektes, das Delta-Update, eine wichtige Rolle. Am Ende soll dann noch beschrieben werden, wie diese verschiedenen Funktionalitäten nach außen propagiert werden, also wie die Schnittstelle der Netzwerkschicht aussieht und aufgebaut ist. Für das Erstellen einer Verbindung wird zunächst ein Server benötigt, welcher die verschiedenen Dienste über das Netzwerk anbietet. Der Code für eine einfache Version eines solchen Servers ist im Listing 6.1 zu sehen. Dabei werden zunächst alle benötigten Pakete importiert, welche für das Aufsetzen der Serverfunktionalität wichtig sind. Diese Pakete sind in node.js primär JavaScript-Dateien, welche wiederum andere Dateien importieren können. Hierbei kann eine Datei für eine oder Implementierung 67 auch mehrere Klassen stehen. Für die spätere Benutzung dieses Pakets wird es als Variable abgelegt und steht dann unter diesem Variablennamen zur Verfügung. 1 2 3 4 5 6 7 var http = require(’http’); var PORT = 80; var server = http.createServer(function(request, response) { response.writeHead(200); response.write(’Hello World’); response.end(); }).listen(PORT); Listing 6.1: Ein einfacher HTTP-Echo-Server In diesem Fall wird nur das Paket zum Erstellen eines HTTP-Servers importiert, auf dessen Grundlage dann ein einfacher Echo-Server aufgesetzt wurde. Dieser liefert zu jeder eingehenden Anfrage eine statische Nachricht. Hierzu wird die CallbackFunktion, welche innerhalb von createServer() festgelegt wurde, aufgerufen und erhält die Objekte für die Anfrage und die Antwort. Dieser Server kann nun so erweitert werden, dass er die URL der Anfrage auswertet, die angeforderte Zieldatei sucht und diese als Antwort zurücksendet. Hierdurch entsteht ein einfacher HTTPServer, welcher auf Benutzeranfragen reagiert und Daten zurückliefern kann. Auf diesen HTTP-Server soll nun das Websocket-Protokoll aufgesetzt werden. Da dieses auf dem gleichen Handshake basiert, wird es auf den bereits bestehenden HTTP-Server aufgesetzt. Wie in Listing 6.2 aufgeführt ist, muss auch hier als erstes das jeweils benötigte Paket importiert werden, welches in diesem Fall Socket.IO ist. Danach wird das Websocket-Protokoll auf den HTTP-Server gelegt, wodurch bereits Websocket-Verbindungen aufgebaut werden können. Um die dabei entstehenden Nachrichten abfangen und verarbeiten zu können, werden zuletzt noch die passenden Callbacks registriert. In diesem Fall reagiert der Server zunächst auf eingehende Verbindungen und bei diesen dann auf Nachrichten und den Verbindungsabbruch. 1 2 3 4 5 6 7 var io = require(’socket.io’); var socketio = io.listen(server); var mainsocket = socketio.sockets.on(’connection’, function (socket) { console.log(’connected’); socket.on(’message’, function(message) {console.log(message);}); socket.on(’disconnect’, function() {console.log(’disconnect’);}); }); Listing 6.2: Der Websocket-Server baut auf dem bestehenden HTTP-Server auf Um diese Websocket-Verbindung nutzen zu können, muss der Client erweitert werden. Dafür soll eine leere HTML-Seite dienen, welche die clientseitige Implementierung von Socket.IO importiert. Außerdem bindet sie auch den Code aus Listing 6.3 ein und startet diesen nach dem Ladevorgang. Dadurch wird eine 68 Implementierung Websocket-Verbindung zum lokalen Server aufgebaut. Danach werden auf diesen Socket verschiedene Callbacks registriert, welche je nach Ereignis aufgerufen werden. In diesem Fall wartet der Client auf den Aufbau der Verbindung und sendet dann über diese eine Nachricht an den Server. Dieser kann die Nachricht dann verarbeiten und sie in der Konsole ausgeben. 1 2 3 4 5 6 7 8 socket = io.connect(’http://localhost/’); socket.on(’connect’, function() { socket.emit(’message’, ’hello socket world’); }); socket.on(’message’, function() (message) { console.log(message); }); Listing 6.3: Der Aufbau der Socket.IO-Verbindung auf Clientseite Über diese Verbindung können nun verschiedene Informationen übertragen werden. Dabei beschränken sich die Daten nicht nur auf einfache Zeichenketten – es können auch JSON-Objekte verschickt werden, wie Listing 6.4 aufzeigt. Beim Ziel kommen die Daten ebenfalls in diesem Format an und können dann übern den jeweiligen Namen ausgelesen werden. 1 2 3 4 5 6 7 8 9 socket.emit(’message’, {msg: ’hello socket world’, browser: navigator.appName, mood: {name: ’happy’, category: ’positive’}}); socket.on(’message’, function(message) { console.log(’new message from ’ + message.browser); console.log(’saying: ’ + message.msg); console.log(’being: ’ + message.mood.name); }); Listing 6.4: Versenden, Empfangen und Auslesen von JSON-Objekten mittels Socket.IO Hat man mehrere Benutzer, so ist es auch sinnvoll, Nachrichten gleichzeitig an alle diese Nutzer zu verschicken. Hierzu dient der Broadcast, welcher ebenfalls von Socket.IO zur Verfügung gestellt wird und im Listing 6.5 dargestellt ist. Dieser Code befindet sich serverseitig innerhalb des connection-Callbacks und sendet zunächst einen Broadcast vom neuen Socket aus an alle anderen Sockets im System. Dies bedeutet, dass die neue Verbindung diese Nachricht nicht erhält. Die zweite Nachricht hingegen wird an alle verbundenen Sockets geschickt, also auch an den neuen Benutzer. Implementierung 1 2 69 socket.broadcast.emit(’message’, ’new user connected’); mainsocket.emit(’message’, ’we have ’ + curUserCount + ’ users online’); Listing 6.5: Globales Verteilen von Informationen mittels Broadcasts in Socket.IO Manchmal will man jedoch nur eine bestimmte Gruppe von Benutzern über etwas informieren, beispielsweise sollen nur die Benutzer einer Lobby eine Liste der darin befindlichen Teilnehmer erhalten. Hierzu bieten sich Räume an, welche Socket.IO ebenfalls bereitstellt und welche im Listing 6.6 gezeigt werden. Die Verbindungen abwechselnd in zwei unterschiedliche Räume hinzugefügt. Diese Räume können dann, unter Angabe des Namens, ebenfalls mit Broadcasts benachrichtigt werden. 1 2 3 4 5 6 7 if(currentUserCount % 2 == 0) { socket.join(’even’); mainsocket.to(’even’).emit(’message’, ’secret message for even room’); } else { socket.join(’odd’); mainsocket.to(’odd’).emit(’message’, ’secret message for odd room’); } Listing 6.6: Benutzer können in Räume gefasst werden, welche dann lokale Broadcasts unterstützen Um verschiedene Nachrichtentypen unterscheiden zu können, ist es außerdem möglich, Nachrichten nicht nur als message zu verschicken, sondern eigene Typen anzugeben und diese beim Empfangen gesondert zu verarbeiten. Wie Listing 6.7 zeigt, können weitere Nachrichtentypen direkt beim Versenden angegeben und für das Empfangen registriert werden, wodurch besser auf verschiedene Informationsarten reagiert werden kann. Dies wird in der aktuellen Applikation genutzt, um Ereignis- und Daten-Nachrichten zu unterscheiden. 1 2 3 4 5 socket.emit(’usercount’, currentUserCount); socket.on(’usercount’, function(message) { console.log(’### NEW USERCOUNT: ’ + message + ’ ###’); }); Listing 6.7: Der Typ der Nachricht kann variiert werden, um verschiedene Informationsarten zu unterscheiden Nachdem nun Daten-Nachrichten separat behandelt werden können, müssen die hierfür benötigten Delta-Updates realisiert werden. Für diese Applikation spielen bei der Übertragung nur Arrays eine Rolle, da die verschiedenen Informationen, welche als Daten-Nachricht zwischen Server und Client ausgetauscht werden müssen, immer als Array vorliegen. Hierfür muss immer der letzte übertragene Stand gehalten werden, damit dieser mit den neuen Informationen abgeglichen werden 70 Implementierung kann. Dieser Abgleich und das Erstellen des Deltas wird im Listing 6.8 gezeigt. Hierzu werden die beiden Datenstände an die Funktion übergeben, welche sie dann abgleicht und die Aktualisierungen zurückgibt. Als erstes wird dazu ein leeres Array erstellt, in dem die einzelnen Aktualisierungen gesammelt werden. Dann werden alle Elemente der beiden Arrays durchlaufen. Um dies zu erreichen, wird die Länge des größeren Arrays zum iterieren verwendet. In der Schleife werden dann die jeweiligen Elemente beider Arrays an einer Stelle verglichen. Falls dabei das neue Array diese Stelle nicht hat, muss sie auch aus dem alten gelöscht werden. Sollte jedoch das neue Array eine Stelle mehr haben als das alte, wird das Element aus dem neuen Array übernommen. Dies geschieht ebenfalls, wenn beide Arrays zwar Elemente an dieser Stelle haben, diese jedoch unterschiedlich sind. Sind die beiden Elemente hingegen Arrays, wird der Vergleich rekursiv durchgeführt und bei Veränderungen an das Delta angehängt. Der Aufbau des Deltas ähnelt dabei einer beim IETF2 gelisteten Vorlage für Delta Updates bei JSON-Dokumenten [2]. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function getDelta(o, n) { var delta = []; var i = (o.length > n.length) ? o.length : n.length; while(i--) { if(n[i] == null) { delta.push({"op": "del", "pos": i}); } else if(o[i] == null) { delta.push({"op": "mod", "pos": i, "val": n[i]}); } else if(Array.isArray(o[i]) && Array.isArray(n[i])) { var cDelta = getDelta(o[i], n[i]); if(cDelta.length > 0) { delta.push({"op": "array", "pos": i, "val": cDelta}); } } else if(o[i] !== n[i]) { delta.push({"op": "mod", "pos": i, "val": n[i]}); } } return delta; } Listing 6.8: Die Unterschiede zwischen den Arrays werden mit der jeweiligen Position, dem Typ und dem Wert in einem Delta gesammelt Diese Deltas sparen vor allem bei kleinen Aktualisierungen in großen Datenmengen sehr viel Bandbreite, was beispielsweise bei dem Setzen einer neuen Kugel auf dem Spielfeld von Vorteil ist. Liegt jedoch kein alter Datenstand vor, müssen alle Daten neu übertragen werden, was bei dieser Art von Delta jedoch auch sehr viel Overhead bedeutet. Deshalb sollen Daten nur dann auf diese Weise übertragen werden, wenn 2 "http://www.ietf.org/" Implementierung 71 es bereits einen alten Datenstand gibt. Andernfalls wird die neue Datenmenge als Ganzes ohne Delta übertragen. Kommt ein Delta-Update an, muss dieses den alten Datenstand aktualisieren, wie es der Code aus Listing 6.9 tut. Dabei werden für jeden Eintrag aus dem Delta die einzelnen Informationen für die Aktualisierung ausgelesen. Je nach Typ wird dann entweder der Wert überschrieben, das Element gelöscht oder rekursiv mit einem Array gefüllt. Am Ende entsteht dadurch das aktuelle Array. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function patch(o, delta) { for(var i = 0; i < delta.length; i++) { var operation = delta[i].op; var position = delta[i].pos; var value = delta[i].val; if(operation == "mod") { o[position] = value; } else if(operation == "del") { o.splice(position,1); } else if(operation == "array") { o[position] = patch(o[position], value); } } return o; } Listing 6.9: Das alte Array wird mit jedem Eintrag im Delta aktualisiert Diese verschiedenen Funktionalitäten müssen nun noch an die Manager von Client und Server angebunden werden. Hierzu erhält die Netzwerkschicht beim Instanziieren eine Referenz auf die handle()-Methoden der jeweiligen Manager-Klasse, welche für die Verwaltung von Nachrichten zuständig ist. Dadurch werden eingehende Nachrichten passend zu ihrem Typ direkt zu dem jeweiligen Callback weitergeleitet und von diesem verarbeitet. Für das Senden von Informationen bietet die Schicht die bereits in den Abschnitten 5.3 und 5.4 aufgezeigten Methoden an. 6.3 Server Der Server wurde nach dem Konzept aus Abschnitt 5.3 umgesetzt, weshalb die meisten Details hierzu bereits vorliegen. Aus diesem Grund wird in diesem Abschnitt nur die Anbindung der Callbacks an die Netzwerkschicht und die generelle 72 Implementierung Nutzung von Objekten in JavaScript beschrieben. Die detaillierte Umsetzung der einzelnen Klassen wird nicht behandelt. Für die Anbindung an die Netzwerkschicht werden die beiden Handler-Methoden des Backend-Objekts beim Instanziieren an diese übergeben. Über diese Handler können nun eingehende Nachrichten zum passenden Callback weitergeleitet werden. Um das Hinzufügen von eigenen Callbacks zu vereinfachen, werden diese nicht explizit in einer Map mit ihrer Aktion verlinkt, sondern in einen JavaScript Namespace geschrieben. Dies ist ein Objekt, welches verschiedene Variablen und Methoden sammelt. Diese können zum einen klassisch über den Methodennamen aufgerufen werden: 1 myNamespace.method(data); Jedoch ist es auch möglich, auf die einzelnen Bestandteile per Zeichenkette zuzugreifen: 1 myNamespace[method](data); Dabei kann die Zeichenkette frei gewählt werden, wodurch es möglich wird, eine Nachricht über einen String aufzurufen. Dies ist vor allem im vorliegenden Fall besonders gut geeignet, da auf diese Weise neue Callbacks schnell angebunden werden können. Hierzu wird der Namespace um eine Methode mit dem Namen der Aktion erweitert und kann dann bereits über eine Nachricht angesprochen werden. Diese Verarbeitung von Nachrichten ist server- und clientseitig identisch, mit dem Unterschied, dass der Server noch die Verbindung, also den Socket der Nachrichtenquelle an das Callback übergibt. Eine vollständige Implementierung eines solchen Handlers und des dazugehörigen Namespaces wird im Listing 6.10 dargestellt. Hierbei werden die Methoden im Namespace über den Handler mit den jeweiligen Daten aufgerufen. 1 2 3 4 5 6 7 8 9 10 11 function handle(action, data) { myFunctions[action](data); } var myFunctions = { log: function(data) {console.log(data);}, wraplog: function(data) {this.log(’>~~~> ’ + data + ’ <~~~<’);} } handle(’log’, ’message 1’); handle(’wraplog’, ’beautiful message 2’); Listing 6.10: Mit Namespaces können Funktionen über einen String aufgerufen werden Implementierung 73 Wie bereits an den Namespaces zu sehen ist, gibt es in JavaScript keine klare Unterscheidung zwischen verschiedenen Objektarten. Auch die Typen der Extras basieren auf Variable aus Namespaces, da JavaScript keine eigene Implementierung für Enumerations liefert. Auch gibt es keine Klassen, wie man sie beispielsweise aus Java kennt. Hier setzt JavaScript auf Funktionen, welche durch Variablen und Methoden erweitert werden können und dann über new() zu instanziieren sind. Ein Beispiel für eine solche Klasse zeigt Listing 6.11. Hierbei wird zunächst die Klasse User mit dem Attribut username erstellt, welches beim Instanziieren gesetzt wird. Weitere Attribute können dabei entweder direkt in dieser Funktion gesetzt werden, oder aber auch später in den anderen Methoden angefügt werden. Diese anderen Methoden werden über den Prototypen des neuen Objektes gesetzt, da ansonsten jede neue Instanz auch alle Methoden neu instanziieren würde, wodurch es zu unnötigen Speicherverbrauch kommt. Dieses Objekt kann entweder direkt in dem Code eingebaut werden, in dem es auch genutzt wird, oder aber auch ausgelagert werden. Dabei sollten die einzelnen Objekte als Modul exportiert werden. 1 2 3 4 5 6 7 8 9 10 11 12 13 function User(username) { this.username = username; } User.prototype.setUsername = function(username) { this.username = username; } User.prototype.setUsername = function() { return this.username; } module.exports.User = User; Listing 6.11: Klassen basieren in JavaScript auf Funktionen, welche dazu erweitert werden können Nach dem Einbinden ist es dann möglich, ein neues Objekt zu erzeugen, welches sich dann wie jede andere Variable verhält. Auf Basis dieser Technik wurden auch alle anderen Klassen des Servers umgesetzt. Aus diesem Grund werden diese nicht explizit beschrieben. 74 6.4 Implementierung Client Auch die Klassen im Client basieren auf den Informationen aus Abschnitt 5.4, welche nach dem Muster aus Listing 6.11 umgesetzt wurden. Auch die Anbindung der einzelnen Callbacks an den Server verläuft so, wie bereits im Listing 6.10 gezeigt wurde. Die Implementierung dieser Elemente soll deshalb nicht nochmals beschrieben werden. Vielmehr soll hier die Ausgabe der Informationen an den Benutzer behandelt werden. Dabei wird zuerst gezeigt, wie Benutzereingaben abgefangen und verarbeitet werden. Danach werden die Aktualisierungen der einzelnen Anzeigeelemente behandelt. Dabei wird zunächst gezeigt, wie einzelne Elemente einmalig ausgetauscht werden und dann, wie man dies so erweitern kann, dass auch Animationen durchgeführt werden können. Zum Abfangen von Benutzereingaben soll neben JavaScript auch die Bibliothek jQuery genutzt werden, welche viele nützliche Vereinfachungen und Erweiterungen für JavaScript bietet. Nachdem diese Bibliothek importiert ist, kann man über den Namespace $ auf die einzelnen Objekte der Website zugreifen und diese manipulieren, wie im Listing 6.12 gezeigt wird. In diesem Beispiel wird jeweils ein Klick-Handler mit einem Callback an alle input-Objekte sowie an das Element mit der ID clickfield angebunden. Außerdem wird auch ein Tastatur-Handler für die gesamte Seite registriert. In den einzelnen Callbacks können dann die Eingaben ausgewertet werden. So kann beispielsweise die Position des geklickten Elements innerhalb von anderen Elementen geliefert werden, während beim Spielfeld die Koordinaten der geklickten Stelle ausgelesen werden können. Bei den Tastatureingaben kann ebenfalls abgefangen werden, welche Tasten der Benutzer gedrückt hat. 1 2 3 4 5 $(function () { $("input").click(buttonClick); $("#clickfield").click(fieldClick); $(document).keypress(handleKeypress); }); Listing 6.12: jQuery vereinfacht das Anbinden von Handlern zur Laufzeit Sobald die Aktionen verarbeitet wurden, gibt es in den meisten Fällen eine Ausgabe, welche wieder an den Benutzer gelangen soll. Hierzu müssen die Objekte der Anzeige mit den neuen Informationen aktualisiert werden. Hierzu bietet jQuery ebenfalls einige Erweiterungen an, welche im Listing 6.13 aufgeführt sind. Dabei kann ein Element neue Daten angefügt bekommen oder gleich den gesamten Inhalt neu setzen. Außerdem können auch die style-Attribute ausgelesen und angepasst werden, um so Elemente neu zu positionieren oder ein- und auszublenden. Implementierung 1 2 3 4 75 $("#output1").append("a"); $("#output2").html("Neuer Inhalt"); $("#rect").css("display", "block"); $("#rect").css({top: 100, left: 50}); Listing 6.13: Die Manipulation von Elementen ist mittels jQuery ebenfalls schnell umzusetzen Verbindet man diese Funktionalitäten mit einem Interval, können damit Animation umgesetzt werden, wie Listing 6.14 zeigt. Hierbei wird ein neues Interval mit einem Callback und einer Zeitangabe in Millisekunden registriert, welches dann das Callback solange aufruft, bis dieses Interval wieder gelöscht wird. Speichert man sich die Koordinaten und die Bewegungsrichtungen ab, kann man dadurch ein Element in einem anderen hin- und herfliegen lassen. Dies ist auch die Grundlage für die Animation der Kugeln auf dem Spielfeld. 1 2 3 4 5 6 7 8 9 10 11 12 13 var moveInterval = setInterval(move,10); function move() { x += xDir; y += yDir; $("#rect").css({top: y, left: x}); if(x < 0) xDir = Math.abs(xDir); if(x > 600) xDir = -1*Math.abs(xDir); if(y < 0) yDir = Math.abs(yDir); if(y > 280) yDir = -1*Math.abs(yDir); } clearInterval(moveInterval); Listing 6.14: Ein Interval ruft die angegebene Funktion alle 10 Millisekunden auf, wodurch eine Animation entsteht Basierend auf diesen Funktionalitäten können die verschiedenen Anwendungsfälle im GUI-Manager umgesetzt werden. Sobald neue Informationen über das Netzwerk angekommen und abgespeichert sind, wird der GUI-Manager über die Aktualisierung benachrichtigt. Dieser holt sich dann die jeweils benötigten Daten von den Daten-Objekten über das Frontend-Objekt und aktualisiert die Anzeige. 76 Implementierung Kapitel 7 Schlusswort Die in dieser Arbeit beschriebenen und bei der Implementierung genutzten Technologien sind für die hier behandelte Applikation sehr passend, wodurch eine Umsetzung aller funktionalen und nicht-funktionalen Anforderungen gelang. Deshalb können die Technologien der einzelnen Anwendungsbereiche klar empfohlen werden, auch wenn es Alternativen gibt. Node.js bietet eine stabile und vor allem skalierbare Serverarchitektur, welche zudem durch verschiedene Erweiterungen an den jeweiligen Anwendungsfall angepasst werden kann. So gestaltet sich auch die Anbindung von Websockets durch Socket.IO als völlig problemlos, was dadurch zu einer sehr mächtigen Serverinfrastruktur führt. Auch die Anbindung der Clients an den Server ist dank Socket.IO schnell erledigt und durch eine abstrahierte Netzwerkschicht lassen sich viele verschiedene Ereignisse und Daten direkt zwischen den Applikationselementen austauschen. Die Darstellung ist dank html5 und jQuery sowohl einfach als auch mächtig – für jede Aufgabe kann eine passende Funktionalität geschaffen werden. Alle hier vorgestellten Technologien werden außerdem auch weiterhin durch eine große Community und viele engagierte Entwickler gepflegt, wodurch man für die nähere Zukunft mit vielen Neuerungen rechnen kann. Es wurde aber auch gezeigt, dass bereits die nächsten Technologien in den Startlöchern stehen. Allen voran ist WebRTC zu erwähnen, welches die Kommunikation im Web nochmals stark verändern kann – durch die Nutzung von Peer-to-PeerVerbindungen werden die Server stark entlastet, was bei den stetig wachsenden Datenmengen für ein entkoppeltes Wachstum der Dienste sorgen kann. Insgesamt befindet sich die Entwicklung der verteilten Systeme und des Cloud Computing noch in den Anfängen, hat jedoch bereits zu diesem Zeitpunkt eine enorme Nachfrage. Die Echtzeitkommunikation im Web-Kontext bleibt daher auf jeden Fall ein Hauptentwicklungsgebiet im Internet. 77 78 Schlusswort Literaturverzeichnis [1] ArcadeHistory. puzzle bobble [coin-op] arcade video game, taito corp. (1994). [letzter Zugriff: 10. August 2013]. [2] Ed. P. Bryan and Inc. ForgeRock US. A json media type for describing partial modifications to json documents. [letzter Zugriff: 21. September 2013]. [3] Michelle Bu and Eric Zhang. Peerjs - peer-to-peer data in the web browser. [letzter Zugriff: 21. September 2013]. [4] caniuse. Can i use web sockets. [letzter Zugriff: 20. August 2013]. [5] I. Fette, Google Inc., A. Melnikov, and Isode Ltd. Rfc 6455 - the websocket protocol. [letzter Zugriff: 17. August 2013]. [6] R. Fielding, UC Irvine, J. Gettys, Compaq/W3C, J. Moguland Compaq, H. Frystyk, W3C/MIT, L. Masinter, Xerox andP. Leach, Microsoft, and T. Berners-Lee. Rfc 2616 - hypertext transfer protocol – http/1.1. [letzter Zugriff: 20. August 2013]. [7] Jesse James Garrett. Ajax: A new approach to web applications. [letzter Zugriff: 16. August 2013]. [8] D. Gourley, B. Totty, M. Sayer, A. Aggarwal, and S. Reddy. HTTP: The Definitive Guide. O’Reilly Media, 2009. [9] ITWissen. Echtzeit :: realtime :: Rt :: Itwissen.info. [letzter Zugriff: 11. September 2013]. [10] A.B. Johnston and D.C. Burnett. Webrtc: APIs and Rtcweb Protocols of the Html5 Real-Time Web. CreateSpace, 2012. [11] Brian McKelvey. Worlize/websocket-node. [letzter Zugriff: 21. September 2013]. [12] Peter Mell and Timothy Grance. The nist definition of cloud computing. [letzter Zugriff: 22. September 2013]. 79 80 Literaturverzeichnis [13] Guillermo Rauch. Socket.io: the cross-browser websocket for realtime apps. [letzter Zugriff: 21. September 2013]. [14] StatCounter. Statcounter global stats - browser, os, search engine including mobile market share. [letzter Zugriff: 21. September 2013]. [15] UniversalLexikon. Flimmerverschmelzungsfrequenz. [letzter Zugriff: 21. September 2013].