Entwicklung einer Massive Multiplayer Network Engine in Java
Transcrição
Entwicklung einer Massive Multiplayer Network Engine in Java
Entwicklung einer Massive Multiplayer Network Engine in Java Diplomarbeit Vorgelegt von Christian Stein Institut für Computervisualistik Arbeitsgruppe Computergraphik Betreuer und Prüfer: Prof. Dr.-Ing. Stefan Müller Zweitprüfer: Prof. Dr. Christoph Steigner Oktober 2004 Hiermit erkläre ich, dass ich diese Diplomarbeit selbständig und nur mit den angegebenen Hilfsmitteln angefertigt habe. Koblenz, den 28. Oktober 2004 Christian Stein Blatt mit der Aufgabenstellung statt dieser Seite einfügen! ⇐ TODO Zusammenfassung In dieser Arbeit wird eine allgemeine Network Engine für Massive Multiplayer Onlinespiele in Java entwickelt und für die Verwaltung großer isometrische Landschaften optimiert. Anhand des Spiels Run To The Hills werden die Hauptfunktionen der Engine demonstriert. Die Basis der Engine bildet das klassische Client-Server Modell. Diese zentralisierte Architektur bietet neben der einfacheren Umsetzungsmöglichkeit auch hinsichtlich der Synchronisierung vieler Clients eindeutige Vorteile gegenüber dezentralen oder replizierenden Modellen. Das ermöglicht einen unkomplizierten Beitritt eines neuen Clients in ein laufendes System. Serverseitig kompensieren drei Schichten den durch das gewählte Modell unabdingbaren Mehraufwand an Kommunikations- und Prozessorbelastung. Die Aufteilung dieser Last passiert für den Spieleentwickler völlig transparent und bedarf keiner aktiven Programmierung seinerseits. Administratoren können jedoch zur Laufzeit dem System weitere Resourcen hinzufügen (oder welche entfernen) um das Servernetzwerk an erhöhte (oder geringere) Anforderungen anzupassen. Eine webbasierte und skriptbare Schnittstelle ermöglicht ausserdem ein Eingreifen in das Spielgeschehen. Der Einsatz nicht blockierender Sockets sichert die hohe Leistungsfähigkeit der Datenübertragung bei vielen Netzwerkverbindungen. Die Verwendung der Programmiersprache Java bietet erstens einen Zusammenschluss heterogener Hardware- und Betriebssysteme auf Seiten der Serverkomponenten und wahrt zweitens die Plattformunabhängikeit des Endnutzers, dem Spieler. Damit braucht der Spieleentwickler auch clientseitig keine Portierung des Spiels vorzunehmen und erreicht automatisch eine größere Zielgruppe. Diese Arbeit entstand in Zusammenarbeit mit Thomas Schuster, der im Rahmen seiner Diplomarbeit Entwicklung einer isometrischen Grafik-Engine in Java die graphischen Grundlagen von Run To The Hills implementierte. iii Inhaltsverzeichnis 1 Einleitung 1.1 Verspielte Einleitung . . . . . . . . . . . 1.1.1 Spiele . . . . . . . . . . . . . . . 1.1.2 Computerspiele . . . . . . . . . . 1.1.3 Multiplayer Computerspiele . . . 1.1.4 Multiplayer Onlinespiele . . . . . 1.1.5 Massive Multiplayer Onlinespiele 1.1.6 Zusammenfassung . . . . . . . . . 1.2 Übersicht der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 2 3 4 4 5 5 2 Grundlagen, Systeme und Verfahren 2.1 Systeme . . . . . . . . . . . . . . . . 2.1.1 Echtzeitsysteme . . . . . . . . 2.1.2 Verteilte Systeme . . . . . . . 2.2 Technische Grundlagen . . . . . . . . 2.2.1 Simulationen . . . . . . . . . 2.2.2 Resourcemanagement . . . . . 2.3 Bestehende Lösungen . . . . . . . . . 2.3.1 Java Shared Data Toolkit . . 2.3.2 OpenSkies . . . . . . . . . . . 2.3.3 Terazona . . . . . . . . . . . . 2.3.4 Sun Game Server Technology 2.3.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 6 7 9 9 10 11 12 12 12 14 16 18 3 Anforderungsanalyse 3.1 Anforderungen an das System . . . . . . . . . . . . . . . . . . 3.2 Anforderung an den Client . . . . . . . . . . . . . . . . . . . . 3.3 Anforderungen an den Server . . . . . . . . . . . . . . . . . . 19 19 20 21 iv . . . . . . . . . . . . . . . . . . . . . . . . 4 Design 4.1 Mamuneen . . . . . . . . . . . 4.2 Drop’n Pick! . . . . . . . . . . 4.2.1 DropItemCommand . . 4.2.2 PickItemQuery . . . . 4.2.3 Zusammenfassung . . . 4.3 Late-Join . . . . . . . . . . . 4.3.1 Late-Join eines Clients 4.4 Komponenten . . . . . . . . . 4.4.1 Command . . . . . . . 4.4.2 Application . . . . . . . . . . . . . . . . 23 23 24 25 27 29 30 30 31 31 32 5 Implementierung 5.1 Externe Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Logging . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.2 Webadmin . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Umsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.1 CAPI: Die Command API . . . . . . . . . . . . . . . . 5.2.2 Binden von Kommandoklassen und execute-Methoden 5.2.3 Java NIO Selector . . . . . . . . . . . . . . . . . . . . . 35 35 35 36 38 38 40 43 6 Ein Demospiel 6.1 Design . . . . . . . . . . . . 6.1.1 Allgemein . . . . . . 6.1.2 Anforderungsanalyse 6.2 Implementierung . . . . . . 6.3 Screenshots . . . . . . . . . 45 45 45 46 49 50 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Ergebnisse 54 7.1 Serveradministration via Webbrowser . . . . . . . . . . . . . . 56 7.2 NIO: Fehler in Sun JDK 1.4 für Windows . . . . . . . . . . . . 59 7.3 Fazit und Ausblick . . . . . . . . . . . . . . . . . . . . . . . . 61 Abkürzungsverzeichnis FAQ GPRS HTTP LAN LRMP MOG MMOG MVC NAT NIO OSI P2P RTCP RTP SoC SSL TCP UDP Frequently Asked Questions (. . . and answers) General Packet Radio Service Hypertext Transfer Protocol Local Area Network Light-weight Reliable Multicast Protocol Multiplayer Online Game Massive Multiplayer Online Game Model–View–Controller Designpattern Network Address Translation (auch: masquerading) New Input/Output Paket von Java Open Systems Interconnection Peer–to–Peer Netzwerk Real–Time Transfer Control Protocol Real–Time Transfer Protocol System on Chip Secure Socket Layer Transmission Control Protocol User Datagram Protocol vi Kapitel 1 Einleitung Den Anfang macht das Wort Spiel, gefolgt von Computerspiel und Multiplayer Computerspiel. Danach wird das Wort Computer durch Online ersetzt und abschließend noch den Massen an Spielern durch das Wort Massive genüge getan. Aus dieser naiven Betrachtungsweise lassen sich jedoch bereits grundlegende und gemeinsamen Eigenschaften von Spielen im allgemeinen ableiten, die sich mehr oder minder stark in allen Spielformen wiederfinden. 1.1 1.1.1 Verspielte Einleitung Spiele Reale Spiele machen Spaß! Dieser Spaß ist eine Motivation für die Entwicklung von immer neuen und Veränderung bekannter Spielideen. Es sind viele Möglichkeiten zur Klassifizierung von Spielen denkbar. Intuitiv kann zum Beispiel zwischen Gesellschaftsspielen, Denkspielen und Sportspielen unterschieden werden. Weiterhin ist die Spieldauer (festgelegt oder variabel) als Kriterium denkbar. Auch die Gruppen- oder Mannschaftsbildung (Jeder gegen jeden oder klare Teams) und Rollenverteilung (Torwart und Feldspieler oder gleichberechtige Spieler im Tennisdoppel) können als Unterscheidungsmerkmale herangezogen werden. Im Rahmen dieser Arbeit interessieren hauptsächlich • die Anzahl der Spieler • und die Art und Weise wie ein Spieler mit dem Spiel (und den Mitspieler) interagiert oder interagieren darf. Die Anzahl der Spieler lässt sich meist aus dem Regelwerk des Spiels entnehmen und ist in vielen Fällen explizit vorgeschrieben. Zum Beispiel werden 1 KAPITEL 1. EINLEITUNG 2 für eine ordentliche Skatrunde genau drei Brüder benötigt, für eine Partie Schach genau zwei. Bei realen Spielen ist die Mindestanzahl immer eins oder höher. Computerspiele können auch ohne Spieler auskommen. Spiele können eine maximale Anzahl an Spielern festlegen oder durch den Raum des Spielfeldes begrenzt sein. Die meisten Gesellschaftsspiele, darunter fallen zum Beispiel Brett-, Karten- und Würfelspiele, laufen zugweise ab. Zu einem fixen Zeitpunkt ist genau ein Spieler am Zug – der Rest schaut zu und wartet unter Umständen stundenlang bis der eine Spieler zieht. Eine dem Autor bekannte Ausnahme ist das Kartenspiel Ligretto. Hier können alle Spieler am Tisch gleichzeitig Aktionen ausführen und ihren Kartenstapel abarbeiten ohne auf die Beendigung des Zuges eines Vorgängers zu warten. Auch Sportspiele, vor allem Mannschaftspiele, funktionieren meist hochgradig konkurrent: Jeder agiert und reagiert zu jedem Zeitpunkt der Spielzeit und versucht das Spielziel zu erreichen oder die Spielsituation zu seinen Gunsten zu verbessern. Dieser Aspekt der gleichzeitigen und konkurierenden Interaktionsmöglichkeit ist der Kerngedanke von Massive Multiplayer Online Games. Er stellt ebenfalls die Hauptmotivation der teilnehmenden Spieler dar. 1.1.2 Computerspiele Die Faszination von Computerspielen ist unumstritten. Ihre Geschichte begann 1962 als zwei der Mitarbeiter des MIT, Steve Russel und Dan Ewards für die eben angelieferte PDP-1, den ersten Computer mit Tastatur und Kathodenstrahlbildschirm das erste Programm entwickelten - ein Computerspiel. Sie nannten es Spacewar und es war ein Shoot-Up Game, das es erlaubte, am Bildschirm Raumschiffe abzuschießen. In den darauffolgenden Jahren entstanden zumeist in BASIC programmierte Spiele (wie Lunar Lander, ein textbasiertes Simulationsspiel oder Kingdom, eine Simulation von ökonomischen Prozessen und Vorgänger von Sim City). 1972 gründeten Nolan Bushnell und Al Alcorn mit einem Startkapital von 500 Dollar das Unternehmen Atari. Sie entwickelten den ersten Computerspiel-Automaten Pong, der zum Schlager für Arcade Games wurde und 1974 als Spiel für Heimcomputer auf den Markt gebracht wurde. Es besaß nur zwei Bildschirmausgaben: Deposit Quarter und Avoid missing Ball for High Score. Ein großer Verkaufserfolg für Atari wurde 1978 VCS 2600 KAPITEL 1. EINLEITUNG 3 als hybride Station aus Fernseher, Videospiel und Computer, ein Jahr später gründeten vier ehemalige Atari Mitarbeiter die erste Spielesoftwarefirma Activision. 1980 veröffentlichte Nintendo Pac Man, das sowohl in den Game Arcades als auch am Heimcomputermarkt ein Langzeiterfolg wurde. In den nächsten Jahren deutete sich ein großer finanzieller Einbruch des Spielesektors ab, der erst Ende der achtziger Jahre mit dem Commodore Amiga und dem Gameboy von Nintendo wieder in Schwung kam. Die bekanntesten Spiele waren damals Sim City für Amiga und Tetris für den Gameboy. Nintendos Super Mario Brothers, das 1990 herauskam, spielte weltweit 500 Millionen Dollar ein. Das populärste First Person Shooter Spiel wurde 1993 Doom. Ebenso großen Erfolg hatte Quake, das Nachfolgespiel, das drei Jahre später auf den Markt kam. 1998 stellte Epic Megagames Unreal 1 vor. Dieses Spiel ermöglichte es dem Benutzer erstmals mit Hilfe eines Level Editors selbst Spielwelten zu erzeugen. Mit der Entwicklung des japanischen Spieles Final Fantasy VIII im Jahr 1999, bei der 400 Künstler beschäftigt waren, wurde erstmalig für die Entwicklung eines Computerspiels ein Budget eingerichtet, das diejenigen großer Hollywood Filmproduktionen überstieg. Zwar können auch Denk- und Einzelspielerspiele wie Solitär, Tetris und Co stundenlangen Spaß garantieren, doch ist das unmittelbare Kräftemessen mit andere Menschen noch reizvoller. Zusätzlich zum klassischen Gegner Zeit, kommt beim Gegeneinanderspielen der Anreiz des direkten Vergleichs hinzu. 1.1.3 Multiplayer Computerspiele Eine der ersten Varianten der multiplayer computer games (MCG) ist die, bei der mehrere Spieler gleichzeitig an einem Computer und den daran angeschlossenen Eingabegeräten wie Tastatur, Maus, Joysticks oder -pads zusammen oder gegeneinander spielen. Der Vorteil dieser Art der Unterstützung mehrerer Spieler liegt im einfachsten Fall in der Zuordnung von einem bestimmten Eingabegerät zu einem bestimmten Spieler. Je nach Spielart kann die gemeinsame Sicht auf das Spiel jedoch zu Problemen führen. Vor allem, wenn sich die Spieler im virtuellen Raum örtlich voneinander entfernen können. Entweder muss die Bewegungsfreiheit eingeschränkt werden oder es wird die zur Verfügung stehende Bildschirmfläche gemäß der Spieleranzahl aufgeteilt. Jeder Bildschirmausschnitt kann somit eine individuelle Ansicht präsentieren. Ein weiteres Problem dieser Variante ist, dass es gegenüber dem Mitspieler keine Geheimnisse geben kann. Man spielt immer mit offenen Karten – was wiederum eine Menge von Spielformen ausschließt. KAPITEL 1. EINLEITUNG 4 Durch den Einsatz eines Modems wird nicht nur die Einschränkung auf offene Spielformen festgelegt zu sein überwunden. Zusätzlich kann sich der Spielpartner auch an einem beliebigen Ort mit einem Telefonanschluss befinden. Die lokale Version der Modemvariante bei der die Rechner nur einige Meter von einander entfernt stehen, bietet sich durch die Verwendung eines Nullmodemkabels. Mit diesem Kabel wird mithilfe von Software ein Modem simuliert und verbindet somit zwei Rechner ohne Onlinekosten. Die laufenden Kosten sind ein stark hemmender Faktor für die Ausbreitung von MCGs. Der Vorteil gegenüber der eingangs beschriebenen Variante ist die getrennte Sicht der beiden Spieler auf das Spiel. Allerdings ergibt sich durch diese Trennung ein Synchronisationsaufwand zwischen den beiden Spielen. Doom erlaubte 1993 als eins der ersten Spiele, dass sich mehrere Spieler, die jeweils an einem eigenen Rechner agieren, gleichzeitig in einer simulierten Welte befinden. Die Vernetzung der Rechner geschah damals über ein Intranet, auf dem meist das IPX-Protokoll von Novell gefahren wurde. 1.1.4 Multiplayer Onlinespiele Unter online sein versteht man heutzutage das verbunden sein mit dem Internet. Multiplayer online games sind demnach Spiele, die erst funktionieren, wenn man seinen Computer dem weltweiten Verbund aus Rechnern hinzufügt und dort nach Mitspielern sucht. Die einzige Weiterentwicklung bezüglich der MCGs liegt im Austausch des Datentransferprotokolls. Die IPv4-Suite löste IPX weitgehend ab und behauptet sich seit Jahren gegen die Ablösung durch den designierten Nachfolger IPv6. Mit den sinkenden Onlinekosten und stärkeren Ausbreitung der breitbandigen Zugänge zum Internet wuchs auch der Absatzmarkt für MOGs. Auch derzeit ist dieses Wachstum noch nicht abgeschlossen. Die Zeichen für eine Entwicklung hin zu einer größeren Anzahl von Spieler, die an einem Spiel teilnehmen, stehen sogar sehr gut. Dieses soll im nächsten Abschnitt erläutert werden. 1.1.5 Massive Multiplayer Onlinespiele Wie beim Übergang vom MCG zum MOG ist auch der nächste Schritt vom MOG zum massive(ly) multiplayer online game lediglich eine konsequente Weiterentwicklung von Mehrbenutzer-Computersimulationen. Die einzige Neuerung, und damit Abgrenzungskriterium zu MOGs, ist die Verwaltung von bis zu drei Größenordnungen höhere Anzahl an Spielern. Statt zwei bis KAPITEL 1. EINLEITUNG 5 vier Spieler geht es bei MMOGs um 200 bis 400, in einigen Systemen interagieren bereits mehr als 1000 Spieler gleichzeitig. Ein Mitarbeiter der Firma Blizzard[Bliz], Chris Sigaty, beziffert die Anzahl der auf einem Server gleichzeitig aktiven Spieler auf 2500 bis 3000 [Gamo]. Wie schon beim einfachen Zweispieler-Spiel gibt es natürlich auch hier immer noch die Unterscheidung zwischen zugbasierten und in gefühlter Echtzeit ablaufenden Spielen. Erste sind durch ihre einfachere Umsetzbarkeit schon länger auf dem Markt und verwenden Webseiten, Email und andere Internetdienste als Spielplattform und nutzen sie als Interaktions- beziehungweise Informationsträger. 1.1.6 Zusammenfassung Man erkennt spielend leicht, dass MMOGs die Tradition der Computerspiele fortführen und dabei hohe Anforderung an die logistische und technische Umsetzung stellen. Die in Echtzeit ablaufenden Spiele sind das Thema der vorliegenden Diplomarbeit. 1.2 Übersicht der Arbeit Nach der Einleitung und Klärung des Arbeitsauftrags folgt im nächsten Kapitel 2 eine Vorstellung einiger bereits bestehender Multiplayer-Systeme. Aus dem Diskurs dieser Systeme im Kapitel 3, den Grundlagen von Echtzeitsystemen und der Motivation der echten Spiele, wird im Kapitel 4 ein Modell einer Massive Multiplayer Engine beschrieben und auf den Namen Mamuneen getauft. Die prototypische Umsetzung befindet sich auszugsweise im Kapitel 5 – gefolgt vom Bericht des Feldversuchs im Kapitel 6: Run To The Hills, dass als proof-of-concept einen Teil dieser Arbeit ausmacht. Abschließend werden die Ergebnisse im Kapitel 7 zusammengetragen. Kapitel 2 Grundlagen, Systeme und Verfahren In diesem Kapitel wird das technische Umfeld der Arbeit vorgestellt. Die Erläuterung der Begriffe Echtzeitsysteme und Netzwerke stellt die Grundlage für den Abschnitt Verteilte Systeme. Der Fokus liegt auf dem Client-Server Modell, da andere Topologien (Strukturen) verteilter Anwendungen im Zusammenhang mit hochgradig interaktiven Onlinespielen in Bezug auf Schummeln nicht viel Sinn machen. Da der Flaschenhals im Client–Server Modell die Leistungsfähigkeit des Servers ist, wird eine ereignisgesteuerte Architektur gewählt und genauer erklärt. Abschließend werden bestehende Verfahren vorgestellt. 2.1 Systeme Vorweg werden noch einige Schlagworte rund um Systeme genannt und für die weitere Verwendung in diesem Dokument beschrieben. Es handelt sich dabei nicht um eine vollständige Liste von allgemeinen Systemeigenschaften, sondern um eine an den Inhalt der Arbeit angepasste Auswahl: • Verfügbarkeit bedeutet, dass ein System für die bestimmungsgemäßen Aufgaben zur Verfügung steht. • Der Begriff der Verlässlichkeit erweitert die Verfügbarkeit um die Dimension der jeweils aktuell aufgabenbezogenen Nutzbarkeit von Ergebnissen aus bzw. in einem System. Ein verlässliches System muss sicherstellen, dass es immer korrekte Ergebnisse erzeugt oder darüber informiert, wenn dies nicht möglich war (Ausnahmefälle). 6 KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 7 • Sicherheit bewertet Systemzustände auf ihre Auswirkungen hinsichtlich des Systems selbst und im allgemeinen Fall auch bezüglich seiner Umwelt einschließlich der dort vorhandenen Menschen. Sicherheit ist eine Sachlage (ein Zustand), bei der das durch Eintrittswahrscheinlichkeit und Schadensausmaß beschriebenes Risiko nicht größer ist als das größte noch vertretbare Risiko. Letzteres wird auch Rest– oder Grenzrisiko genannt. 2.1.1 Echtzeitsysteme Rechtzeitigkeit ist der einzig wirklich wichtige Aspekt von Echtzeitsystemen. Solche Systeme reagieren auf externe Eingaben, welche auf vorhersehbare Art und Weise allerdings zu unbekannten Zeitpunkten eintreffen können. Sie verarbeiten diese Eingaben, treffen“ Entscheidungen und generieren, falls ” notwendig, Ausgaben. Die Frage Wie?“ die Ein- und Ausgaben zu bzw. von ” den System gelangen wird später in diesem Abschnitt erläutert. Neben der aus [RT98] entnommenen kanonischen Definition eines Echtzeitsystems von Donald Gillies: 1. Ein Echtzeitsystem ist ein Informationssystem, dessen Eigenschaften nicht nur von der logischen Ausgabe der Algorithmen bestimmt werden, sondern auch vom Zeitpunkt der Ausgabe. Wird die Anforderung an die Rechtzeitigkeit nicht eingehalten, spricht man von einem Systemfehler. sind laut dieser FAQ auch folgende Auslegungen des Begriffs möglich: 2. Der POSIX Standard 1003.1 definiert Echtzeit für Betriebssystem als die Fähigkeit benötigte Dienste innerhalb einer beschränkten Antwortzeit zu liefern. 3. Manchmal werden schnelle Systeme auch als Echtzeitsysteme bezeichnet. Es sei hier nochmal darauf hingewiesen, das Echtzeit nicht zwangsläufig ein Synonym für Schnelligkeit steht; es ist eben nicht alleine die Antwortzeit an sich ausschlaggebend (diese kann sich im Rahmen von Sekunden bewegen), sondern dass eine maximale Antwortzeit für eine bestimmte Problemlösung vom System garantiert wird. Insbesondere sind zeitlich begrenzte Algorithmen meistens weniger effizient als solche, die nicht einer solchen Beschränkung unterliegen. Ein Beispiel aus dem Alltag ist das Schlangestehen an einer Kasse eines beliebigen Geschäftes. Falls die Schlange ständig wächst und wächst, läuft der KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 8 Prozess des Kassierens nicht in Echtzeit ab. Bleibt hingegen die Länge der Schlange annähernd konstant, es werden im Schnitt so viele Kunden vorne an der Kasse verarbeitet“ wie sich hinten anstellen, läuft das Kassieren in ” Echtzeit. Nach der Klärung des Begriffs Echtzeitsystem folgt eine für diese Arbeit ausreichend genaue Definition des Kommunikationsbegriffs, mit dessen Hilfe der Transport der Ein– und Ausgabe passiert. In [Steu03] fasst der Autor Systeme und Technologien hinsichtlich Verteilter Echtzeitsysteme (siehe Abschnitt 2.1.2) zusammen. Der Begriff Kommunikation ist dort sehr generell gehalten: es handelt sich um den Austausch von Materie, Energie oder Information. Kommunizierende Einheiten in einem System legen gemeinsame Kommunikationsplattformen nahe. Auch wenn die Kommunikation von Materie mittels spezieller und an den kommunizierten Inhalt angepassten Verbindungen und Pfaden (Rohre für Flüssigkeiten, Transportbänder für solide Stoffe) realisiert ist, kann Energie und die darin codierte Information über eine gemeinsame Plattform vermittelt werden. Voraussetzung dafür ist die Existenz standardisierbarer und standardisierter Transportverfahren für Energie und Information, auf denen entsprechende Transportnetze aufgebaut werden können. Bei der elektrischen Energie sind dies zum Beispiel die Leitungen mit ihren genormten Spannungsebenen; bei der Information sind es Bussysteme oder LANs und deren Verbindungen über graphenförmige Netze Transportverfahren. KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 2.1.2 9 Verteilte Systeme Es folgt ein Einschub zu Verteilten Systemen. Dabei wird der Begriff der Verteilung intuitiv verwendet, also vorwiegend im Sinne einer räumlichen Verteilung. Zu den Parametern des Raums (z. B. Abmessungen, Dimensionen, Formen) werden keine weiteren Angaben gemacht. Die so genannte funktionale und logische Verteilung läuft letztendlich in unserer physikalischen Welt auf eine räumliche Trennung von Funktionen hinaus. Wobei die räumliche Entfernung extrem klein ausfallen (SoC) kann oder gar virtueller Natur (Prozesse und Threads in Programmiersprachen) ist. Eine axiomatische Definition beschreibt die strukturellen und physikalischen Grundeigenschaften Verteilter Systeme: • Die Architektur Verteilter informationsverarbeitender Systeme umfasst abgrenzbare Einheiten, darunter eine möglicherweise variable Zahl von Verarbeitungseinheiten, auf denen (Rechen–) Prozesse ablaufen. • Die Kommunikation zwischen den Prozessen erfolgt durch Nachrichtenaustausch über eine von allen Einheiten bzw. Prozessen gemeinsam genutzte Kommunikationsinfrastruktur. • Die Kommunikation zwischen den Prozessen unterliegt einer von Null verschiedenen und variablen Verzögerung. • In einem Verteilten System ist die Existenz von zwei Prozessen mit exakt gleicher Sicht auf ihren gegenseitigen Status nicht möglich. Dasselbe gilt für Sichten auf den Status des Gesamtsystems. • Es existiert eine systemweite Steuerung für die dynamische (Inter–) Prozesskommunikation und für das Ablaufmanagement der Prozesse. Eine Unterteilung solcher Systeme kann anhand der Verteilung des Status des Gesamtsystems vorgenommen werden: zentrale, verteilte und replizierte Verwaltung. 2.2 Technische Grundlagen Zwei großen Kontrahenten beim Design und der Implementierung einer Network Engine sind die Menge der ausgetauschten Daten (data throughput) und die durch das Netzwerk bedingten Laufzeiten (latency) der versendeten Nachrichten. Die Datenmenge die zwischen den teilnehmende Knoten ausgetauscht werden muss, hängt nicht von der gewählten Topologie des Netzes ab: Das KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 10 klassische Client-Server Modell bündelt das Gros des Datenaufkommens beim Server. Je mehr Clients beim Server verwaltet werden, desto größer ist auf der einen Seite die Menge an Daten, die beim Server eintrifft, als auch auf der anderen, die die zu den Clients wieder rausgeht. Dagegen steht die hochgradig vermaschte Topologie des peer-to-peer Netzes, in der jeder Client mit möglichst jedem anderen verbunden ist. Dadurch entfällt die Datenbündelung beim Server – sie muss jedoch von jedem Client abgefangen werden. Der Vorteil der vollen Vermaschung liegt in der verkürzten Laufzeit einer Nachricht zwischen den Clients. Hier fehlt die datenvermittelnden Instanz des Servers. Der Nachteil diese Architektur liegt im steigenden Daten- und Verwaltungsaufwand bei jedem Knoten. Die Laufzeiten einer Nachricht, auch Verzögerung genannt, hängen direkt mit den Zusicherungen der gewählten Plattform und deren Protokollen zusammen. Die in der Arbeit gewählte Plattform ist das paketorientierte Internet Protokoll. Da es sich dabei um einen best effort service handelt, kann es keine festen Zusicherung an die Laufzeiten eines Paketes (Nachricht) geben. Im Grunde kann noch nicht einmal die Auslieferung der Pakete zugesichert werden: Es können verschickte Pakete gar nicht, mehrfach oder in einer anderen Reihenfolge beim Adressaten eintreffen. 2.2.1 Simulationen Computerspiele sind Simulationen. Simulationen sind Programme, die virtuelle Welten (Spielräume) berechnen und den aktuellen Simulationszustand ausgeben. Die hier betrachteten Simulationen bestehen aus zwei logisch und in der Regel auch physikalisch getrennten Einheiten: Server und Client. Der Server unterhält die Referenzsimulation. Der Server verwaltet ausserdem eine beliebige Anzahl an Clients und dient diesen als Synchronisierungs- und Nachrichtenaustauschinstanz. Ein Client unterhält eine Kopie der Referenzsimulation und präsentiert diese dem Benutzer. Benutzeraktionen können den Zustand der Simulation beeinflussen. Dafür werden die Aktionen von der Clientapplikation interpretiert und in geeigneter Form (als Nachricht) an die Simulation gesendet. Benutzeraktionen treiben das Spielgeschehen an und erhalten die Simulation lebendig: die Reaktion anderer Benutzer auf eine Benutzeraktion äussert sich meist wiederum in Benutzeraktionen. Ändert sich der Zustand der Simulation, müssen alle Benutzer über diesen Umstand möglichst direkt in Kenntnis gesetzt werden. Diese binäre Information reicht theoretisch aus, damit alle Benutzer den neuen Simulationzustand anfordern. KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 2.2.2 11 Resourcemanagement Singhal und Zyda [SiZy99] nennen eine Informationsprinzipgleichung, die die Menge der benötigen Resourcen einer Netzwerkapplikation im direkten Zusammenhang mit der Nachrichtenanzahl und -größe sowie einem Zeitkriterium angibt: Resources = M ∗ H ∗ B ∗ T ∗ P M H B T P Anzahl der ausgetauschten Nachrichten Anzahl der Zielknoten pro Nachricht Größe pro Nachricht Rechtzeitigkeitanforderungen Anzahl der Prozessorzyklen T = Rechtzeitigkeitanforderungen an die Nachrichtenauslieferung P = Anzahl der Prozessorzyklen zum Transfer als auch zum Interpretieren pro Nachricht gebraucht werden Wird der Wert einer beliebigen der fünf Variablen in der Gleichung erniedrigt, so verringert sich der Resourcenbedarf. Diese Einsparung geschieht aber nicht ohne Kosten. Diese können entweder durch eine Erhöhung einer der anderer Variablen kompensiert werden oder die Erfahrungsqualität der Applikation, der Spielspaß, nimmt ab. Die Auswahl der richtigen Variablen und der Spielraum bei den Werten obliegt primär den vorgegebenen Applikationsanforderungen und den durch die Hard- und Software bedingten Voraussetzungen. KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 2.3 12 Bestehende Lösungen Im folgenden werden bereits verfügbare Verfahren und Systeme vorgestellt. 2.3.1 Java Shared Data Toolkit Das Java Shared Data Toolkit (JSDT) unterstützt hochgradig interaktive, kollaborative Java Applikationen. Es bietet eine abstrakte Form einer Session. Damit ist eine Gruppe von Objekten gemeint, die über eine gemeinsame Kommunikationsschicht miteinander verbunden sind und die sich gegenseitig beliebige Nachrichten schicken können. Zusätzlich enthält das Toolkit neben einer effizienten Unterstützung multicast-orientierter Protokolle auch die Fähigkeit sequenzielle Nachrichtenströme einzurichten. Dazu kommt ein tokenbasierter Synchronisationmechanismus und die Möglichkeit eine gemeinsame Sicht auf Bytearrays zwischen den Teilnehmern einer Session zu gewährleisten. JSDT stellt lediglich eine einfache Schnittstelle für generelle Mehrparteienkommunikation dar, hinter der eine ganze Reihe von Implementierungen die eigentliche Arbeit leisten können. Welcher Protokoll Stack zur Laufzeit aktiv ist, sogar die Auswahl dessen, wird vor dem JSDT-Nutzer versteckt. JSDT wird standardmäßig mit drei Implementierungen geliefert: Socket (TCP und UDP), HTTP (inklusive SSL und Tunneling) und LRMP. Letzteres steht für das Light-Weight Reliable Multicast Protocol, welches auf IP-Multicast aufsetzt. 2.3.2 OpenSkies Cybernets Real Time Intelliget Routing Technology, genannt OpenSkies, ist eine verteilte Servertechnik auf der Basis von HLA für den Datentransfer dynamischer und hoch-interaktiver Netzwerkapplikationen in Echtzeit. Dabei werden nur solchen Datenströme weitergeleitet, die für einen bestimmten Client von Interesse sind. Als Grundlage beschreibt Cybernet die Lösung zur Datenmengenreduktion bei statischen (Web-) Inhalt und bewertet diese für den Einsatz im dynamischen Fall als nicht passend. OpenSkies verwendet ein software-basiertes System, das in einem Netzwerk von verteilten Server läuft. Die Clients (hier Federates) verbinden sich mit einem dieser Server (genannt FedHosts), nachdem der LobbyManager einen passenden aussuchte. Die FedHosts agieren nun zusammen als Datenverkehrskontrolleure, die die Datenströme in die richtige Richtung leiten. Unter der Vorraussetzung einer ordentlichen Culling-Implementierung, ist jeder KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 13 Abbildung 2.1: Übersicht eines OpenSkies-basierten Netzwerks FedHost hauptsächlich mit der Behandlung seiner Federates beschäfigt. Daraus resultiert eine lineare Abhängigkeit zwischen der Anzahl der vom System unterstützten Clients und Zahl an FedHosts. Entwickler können eigene Culling-Kriterien implementieren und als FedHost-Module installieren, deren Strategien sich direkt an der zugrundeliegende Applikation orientieren daraus Vorteile schöpfen können. Beispiele sind Kriterien wie verschiedene Radiofrequenzen, Distanz zwischen Einheiten, Blickwinkel und eine Aussortierung der Daten nach Rang oder Sicherheitsstufen. Da die Sprache mit der die Culling-Regeln definiert werden C++ ist, können die Regeln beliebig einfach oder komplex sein. Lineare Skalierung ist laut Cybernet der Schlüssel zur Bandbreitenverwaltung in Netzwerken: Um den Aufwand von mehr Clients aufzufangen, können entsprechend neue FedHost in das System aufgenommen werden. KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 2.3.3 14 Terazona Die Firma Zona, Inc. bietet ein MMPOG Produkt namens Terazona an. Terazona ist ein software development kit (SDK), das eine Umgebung für Onlinespiele zur Verfügung stellt, in der pro Cluster bis zu 32.000 Spieler verwaltet werden können. Abbildung 2.2: Übersicht der Komponenten von Terazona Terazona bietet für die Lösung der Skalier- und Sicherheitsprobleme von MMPOGs eine speziell designte, abstrakte Netzwerkschicht. Spielapplikationen können ohne Rücksicht auf die zugrundeliegende Netzinfrastruktur geschrieben werden. Dieser Ansatz ermöglicht, dass sich die Spieleprogrammierer ausschließlich mit dem Design der Spiele beschäftigen können und sich nicht um Netzwerkdesign kümmern müssen. Dadurch werden die Entwicklungskosten signifikant gemindert. Serverseitig verteilt Terazona die anfallende Last auf mehrere Rechner mit speziellen Aufgabengebieten (n-tier server architecture) wie Applikationsmanagement und Datenbankzugriff. Diese Serverschichten bestehen wiederum aus mehreren eng miteinander gekoppelten Komponenten, bei deren Implementierung auf Erweiter- und Wiederverwendbarkeit durch den Spieleprogrammierer geachtet wurde. Dazu gehören Module wie eine Validation Engine, eine Prediction Engine und ein GameState Event Processor. Plattformunabhängig der Clients, Sicherheit des Gesamtsystems und dessen Fernsteuerbarkeit sind weitere Aspekte, zu denen Terazona eine Lösung KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 15 anbietet. Clients sind über eine Kommunikationskomponente mit den Servern verbunden, die die eigentliche Komplexität verbirgt und ein Bild eines einzelnen Servers erzeugt. Abbildung 2.3: Beispielhafte Installation von Terazona Eine externe Firewall kontrolliert die Datenströme zwischen den Clients und dem Zona Dispatcher Server. Der Dispatcher dient als Login Server und leitet eingeloggte Benutzer an einen geeigneten Game Server weiter. Mit Hilfe periodischer Heartbeats aktualisiert er auch ständig den Status aller Server. Die Game Server verarbeiten eintreffende Game States aufgrund der Spiellogik und senden validierte States an alle Clients, die ihnen zugeordnet sind. Ein Sphere Server überwacht die Aktivitäten der Game Server und stößt im Absturzfall eines solchen eine Neuzuteilung der betroffenen Clients durch den Dispatcher an. Zudem kann der Sphere Server auch die Last zwischen den Game Servern zum Beispiel nach Regionen verteilen. Hier werden auch die angeschlossen Datenbanken bedient. KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 2.3.4 16 Sun Game Server Technology In einem fünfseitigen White Paper [Sun 04] gibt die Firma Sun im Juni 2004 einen Überblick über das in der Entwicklung befindliche Konzept Sun Game Server Technology. Dieses trägt den Anforderungen hochgradig skalierbarer Spielsimulationen Rechnung, indem es kurze Antwortzeiten, eine hohe Bandbreite und große Ausfalltoleranz aufweist. Sun ordnet Spiele in die Gruppe der fast Echtzeitsimulationen ein, die sehr starke Performanzanforderungen besitzen (siehe Abschnitt 2.1.1). Zwei weitere Punkte werden von den Autoren im Rahmen der MMOG als entscheidend über Erfolg und Misserfolg eingestuft: Very Low Latency. Die Gesamtantwortzeit des Systems auf Anfragen jeglicher Art darf wenige Millisekunden nicht überschreiten. From Hundreds To Tens of Thousands of Players. Das System soll nicht nur am oberen Ende, der Fall mit 10.000+ Spielern, den Anforderungen gewachsen sein, sondern eben auch bei niedriger Benutzung, nur einige 100 Spieler sind online, entsprechend weniger Resourcen verbrauchen. Einige Spielformen können im Laufe ihrer Dauer starke Schwankungen der Mitspieleranzahl erfahren. Darauf muss das System stufenlos reagieren können. Desweiteren nennt Sun den Spieleentwickler als primäre Zielperson ihrer Game Server Technology und grenzt diesen anhand seiner Fähigkeiten gegenüber dem normalen corporate und enterprise developer ab: Er habe kein Wissen in den Bereichen Nebenläufigkeit und Datenbanken sowie nur wenig Erfahrung in der Entwicklung skalierender Systeme. Deshalb soll das vorgestellte Konzept die Einarbeitung des Spieleentwicklers in diese Gebiete abnehmen. Diese Ziele führen dann auch zu den Grundpfeilern des Sun Game Server Technology Designs: Simple Application Model. Eine Applikation besteht aus einer Welt von serialisierbaren Simulationsobjekten (SO). Um (Simulations-)Aktionen auszuführen werden die auf ihnen definierten Methoden aufgerufen. Automated Execution Model. (Die Methoden der) Simulationsobjekte werden ereignis-gesteuert, race und deadlock-proof in einer ExecutionUmgebung aufgerufen, die auch für die Fehlerbehandlung zuständig ist. Transparent Continuous Persistence. Die Details der SO-Akquise, das Beenden einer Ausführungseinheit und das Speichern der Ergebnisse übernimmt die Execution-Umgebung. Jede Spielaktion wird mit dem Zeitpunkt ihrer Beendigung dauerhaft. KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 17 Transparent Massive Scalability. Die Ausführung von Spielaktionen wird transparent auf ein horizontal skalierbares Feld von unabhägigen Prozessoren mit getrennten Speichern verteilt. Dabei kann das Feld einen oder eben sehr viele Prozessoren enthalten. Die Autoren beschreiben die Game Server Technology mit einem Kernsatz: a single, event-driven, monothreaded programming environment. Die Programme, innerhalb dieser Umgebung ausgeführt, werden jedoch transparent auf mehrere Prozessoren verteilt, bieten automatische Persistenz und sind fehlertolerant. Das Konzept sieht eine vertikale Unterteilung in drei Schichten vor: Communications, Simulation Logic und Object Store. Die Object Store Schicht enthält alle Zustände aller Spiele die auf dem Game Server laufen. Es stellt eine hoch effiziente (Bruchteile einer Millisekunde pro Operation), skalierbare und fehlertolerante Datenbankanbindung dar, die verschränkungsfreien Zugriff auf die Simulationsobjekte bietet. Die Simulation Logic Schicht führt die eigentlich Spielalgorithmen aus. Hier werden aus eingehenden Ereignissen Aufgaben erzeugt, die bei ihrer Abarbeitung Objekte aus dem Object Store anfordern, verändern und wieder an die Store-Schicht zurückgeben. Die Communications Schicht gruppiert die Kommunikation der Spieler. Sie leitet die Datenpakete sowohl zwischen den Spielern und Simulation Logic KAPITEL 2. GRUNDLAGEN, SYSTEME UND VERFAHREN 18 Schicht als auch direkt unter den Spielern weiter. Hier wird ebenfalls zwischen verschiedenen Netzwerkprotokollen, wie HTTP und GPRS, übersetzt. Die Vorteile dieser Architektur liegen laut Sun in der extremen Skalierbarkeit, der hohen Fehler- und Ausfalltoleranz, der sehr guten Prozessornutzung und der totalen Persistenz aller Objekte. Diese Vorzüge werden alle in einem einfachen Programmiermodell vereinigt, das dem Spieleentwickler die Last der serverseitigen Programmierung nimmt und ihn stattdessen ermöglicht, mehr Zeit in seine Hauptaufgabe zu investieren: die Entwicklung großartiger Spiele. Zudem kann die beschrieben Technologie gleich mehrere Spiele nebeneinander ausführen, was den einfachen Einsatz in Serverfarmen ermöglicht und dessen Administration stark vereinfacht. 2.3.5 Zusammenfassung Die vorgestellten Systeme bieten alle die Möglichkeit multiplayerfähige Spiele umzusetzen. Eine Gemeinsamkeit aller betrachteten Systeme ist die Umsetzung eines Client-Server Modells. Dabei zeigen sich sowohl Stärken als auch Schwachstellen hinsichtlich MMOGs. Die starre Einteilung der Spielwelt in disjunkte Regionen ist als Lösungsansatz der Lastverteilung zu unflexibel. Weitere Kritikpunkte sind die begrenzte Skalierbarkeit, eine minimale Fehlertoleranz, ineffiziente Nutzung vorhandener Prozessoren und eingeschränkte Dauerhaftigkeit der Simulationsdaten. JSDT definiert zum Beispiel lediglich die öffentliche Schnittstellen für die Entwicklung kollaborativer Anwendungen. Computerspielprogrammierer können diese grundlegenden Kommunikationsmethoden für die Übertragung spielrelevanter Daten nutzen – müssen aber höhere Konzepte wie Spielermanagement und Persistenz der Weltdaten nachträglich einpflegen. Cybernets Ansatz setzt auf intelligentes Filtern von unerwünschten Datenströmen. Als Filterkriterien werden bei Openskies nicht nur die Zieladressen der Pakete herangezogen, sondern auch deren applikations-spezifischen Inhalte. Ingesamt ist die von Sun beschriebene Technologie sehr vielversprechend. Die Aufgaben der einzelnen Komponenten sind klar definiert und es existieren bereits Speziallösungen, die den gestellte Anforderung der jeweiligen Gebiete gewachsen sind. Leider fehlen, da sich das Projekt noch in der Forschungsphase befindet, jegliche Eckdaten zur Leistungsfähigkeit im realen Einsatz. Im folgenden Kapitel werden die konkreten Anforderung an eine MMOG Engine gestellt. Kapitel 3 Anforderungsanalyse Die Betrachtung der Computerspielegeschichte und die Analyse bestehender Systeme aus dem vorherigen Kapitel zeigen, dass während der Entwicklung einer MMOG Engine eine Reihe von interessanten Herausforderungen gemeistert werden müssen. Zunächst ist hier die Erstellung und Verwaltung großer und veränderbarer Welten zu nennen. Zudem sind die Spieler dieser simulierten Welten online – Aktionen eines Spielers können den Spielzustand ändern und müssen den Mitspielern mitgeteilt werden. Die Welt und die Spielerdaten sollen offline-Phasen überdauern und müssen damit speicherbar sein. Eine der größten Herausforderungen ist die hohe Anzahl gleichtzeitig agierender Spieler, welche eine sehr effiziente Nutzung der Netzwerkresourcen erfordert. Aus diesen Rahmenbedingungen ergeben sich Anforderungen sowohl an das Gesamtsystem als auch an Client und Server, die im Weiteren dargestellt werden. 3.1 Anforderungen an das System Es folgen allgemeine Zielsetzungen an die Beschaffenheit des Gesamtsystems, die im Design und in der Implementierung umzusetzen sind. Echtzeit Die Spieler interagieren mit der Welt nicht rundenbasiert oder in starren Zeitfenstern. Jeder Spieler hat zu jeder Zeit die Möglichkeit die im Kontext zur Verfügung stehenden Aktion auszuführen. Der Spieler sieht immer den aktuellen Stand der Welt und kann unmittelbar agieren und reagieren: fast paced action – that’s the game. Gleichzeitigkeit Es sollen gleichzeitig hunderte Spieler in ein und derselben Welt interagieren. 19 KAPITEL 3. ANFORDERUNGSANALYSE 20 Abstraktion Die API soll von der Implementierung getrennt sein. Der Nutzen dieser Vorgehensweise liegt zum einen in der selbst–beschreibenden Eigenschaft API. Dort sind alle Komponenten, deren Aufgaben, Rollen und Fähigkeiten deklarativ festgehalten. Zum anderen ist die Implementierung komponentenweise oder auch komplett austauschbar. Referenzimplementierung Das Basissystem soll die API beispielhaft implementieren und dadurch die Machbarkeit belegen. Optimierungen spielen dabei eine untergeordnete Rolle. Klarheit Die Schnittstellen, die die Anwendungsprogrammierer ansprechen, sollen klar nach Themen– und Aufgabengebieten strukturiert sein. Die Namensgebung soll selbsterklärend und intuitiv verständlich sein. Skalierbarkeit Das System soll sich, gestützt durch entsprechende Hardware, an höhere Anforderungen und Belastungen anpassen können. Diese Anpassung sollte im Idealfall zur Laufzeit geschehen, ohne die dabei Simulation abzubrechen und neuzustarten. Erweiterbarkeit Anwendungsprogrammierer sollen eigene Welten erschaffen können. Die dazu benötigten Erweiterungen sollen sich problemlos in das bestehende System an dafür vorgesehene Stellen einfügen lassen. Robustheit Da Netzwerke, die auf dem Internet Protokoll basieren, sehr dynamisch hinsichtlich ihrer Zusammensetzung sind muss sich das System den Änderungen anpassen können. Der Ausfall eines Knotens soll das System in seiner Funktion nicht beeinträchtigen. Weiterhin sichert das Internet Protokoll selbst lediglich eine best effort Dienstqualität zu. Die damit verbunden Unsicherheiten (beispielsweise Paketverlust, Paketverdopplung oder Paketunordnung) müssen vom System erwartet, behandelt und, soweit möglich, behoben werden. Laufzeitstatistik Es sollen bereits zur Laufzeit wichtige Leistungsdaten ermittelt und ständig aktualisiert werden. Mithilfe dieser Größen soll das System dynamisch an die aktuelle Belastungen angepasst werden. Wichtige Kennzahlen im technischen Kontext zum Beispiel die Anzahl aktiver Spieler und die Bandbreite, die jeder Spieler verwendet. 3.2 Anforderung an den Client Ein Client besteht aus vielen Komponenten, die ihre jeweilige Aufgabe erfüllen. Zu diesen Komponenten gehören unter anderem die graphische Benut- KAPITEL 3. ANFORDERUNGSANALYSE 21 zerschnittstelle, das Audiosystem, die lokale Simulation und auch das Abfragen der Benutzereingaben. Die globale Simulation beeinflussenden Eingaben müssen über die Netzwerkkomponente, hier der Einfachheit halber Client genannt, an den Server geschickt werden. Im Folgenden sollen die Anforderungen an eben diese Netzwerkkomponente vorgestellt werden. Einfache Integration Der Client soll lediglich eine weitere Komponente im Objektbaukasten des Spieleprogrammierers sein. Als solche hat sie sich an die Standards der Namensgebung und der vom Programmierer erwarteten Verhaltens- und Funktionsweisen zu halten. Nebenläufigkeit Der Client soll die Applikation nicht in ihrem eigentlichen Fluss unterbrechen. Das bedeutet zum Beispiel, dass das Warten auf Daten vom Server nicht das Rendern der Welt unterbrechen darf. Dem Spieleprogrammierer soll dennoch eine Möglichkeit an die Hand gegeben werden, mit der er bewusst den Fluss der Applikation an Ereignisse des Clients koppeln kann. Synchronisierung Da der Client auf die gleichen Datenfelder zugreift wie die anderen Komponenten, muss der Zugriff entweder zeitlich getrennt oder durch ein geeignetes Verfahren (Kopieren beim Lesen) gegeneinander entkoppelt werden. 3.3 Anforderungen an den Server Ebenso wie der Client muss der Server neben den allgemeinen Anforderungen an das Gesamtsystem spezielle Bedingungen erfüllen. Echtzeit Änderungen in der Simulation, die entweder durch Benutzeraktionen oder durch das Fortschreiten der Zeit eintreten, sollen in möglichst kurzer Zeit an den Clients vermittelt werden. Diese Anforderung an den Server hat einen großen Einfluss auf die Spielbarkeit und damit den Spielspaß eines Onlinespiels. Skalierbarkeit Mehrerer Prozessoren sollen (durch Threads) und/oder Lastverteilung auf vertrauenswürdige, meist eng in einem LAN gekoppelte Rechner unterstützt werden. Persistenz Die für die Simulation relevanten Daten, meist in Objekten gekapselt, sollen einen Neustart des Servers automatisch überstehen ohne dass der Spieleprogrammierer dafür extra einen Speichermechanismus KAPITEL 3. ANFORDERUNGSANALYSE 22 anstoßen muss. In diese Kategorie fallen zum Beispiel die Charakterdaten wie Namen, Fähigkeits- und Erfahrungspunkte sowie die im Besitz des Spielers befindlichen Gegenstände. Sicherheit Fehlerhafte Nachrichten, falscher Inhalt oder zeitliche Ungereimtheiten, sollen erkannt werden können. Als Reaktion stehen mehrere Stufen des Ausschlusses zur Verfügung: • Der Spieler kann unverzüglich vom Spiel ausgeschlossen werden(kick). • Der Zugang zum Spiel kann für bestimmten Internetadressen mit zeitlicher Begrenzung oder bis auf Widerruf durch einen Administrator verweigert werden (IP ban). • Wenn das Spiel einen eindeutigen Schlüssel für jeden Client vorsieht, kann auf dieser Ebene eine Sperrung eingerichtet werden (key ban). Kontrolle Der Server soll zur Laufzeit ohne Spielbeitritt eines Administrators konfigurierbar sein. Darunter fällt neben der transparenten Sicht in das laufende System die Möglichkeit dort Veränderungen zu veranlassen. Kapitel 4 Design Im folgenden Kapitel wird das Design einer Massive Multiplayer Network Engine vorgestellt, die die Anforderungen der vorangegangenen nalyse umsetzt. Sie erhält das Akronym Mamuneen. 4.1 Mamuneen Mamuneen ist eine an die Anforderungen von MMOGs serverseitig angepasste Variante des klassischen Client-Server Konzepts. Server Client Client Client I N T E R N E T Proxy Transceiver Server Proxy Database Server Proxy Server Abbildung 4.1: Die fünf Komponenten von Mamuneen sind Client, Proxy, Transceiver, Server und Database Der Server ist in die drei Schichten Transceiver, Server und Database aufgeteilt. Die Transceiver verwalten für jeden Client eine lokales Abbild (Proxy), über das die Daten von und zu den Clients vermittelt werden. Jeder Server 23 KAPITEL 4. DESIGN 24 enthält das gesamte Regelwerk und kann die Simulation vorantreiben. Die Database dient der gemeinsamen Datenspeicherung aller Server. Mamuneen folgt im Aufbau strikt dem Model-View-Controller (MVC) Designpattern [GHJV95]. Die logisch eng miteinander gekoppelten Einheiten Modell (M) und Steuerung (C) werden hier von der Datenbank und den Servern übernommen. Letztere vereinigen die Simulationlogik und bilden somit das Regelwerk des Spiels ab. Die Server stellen die Schiedsrichterinstanz gegenüber den Clients (entferntes V) dar. Das Modell beinhaltet die Daten in Reinform und stellt zunächst bequeme und schnelle Zugriffsmethoden auf einzelne Datenelemente zur Verfügung. Die Zugriffsmethoden werden von der Steuerungseinheit verwendet um Änderungen im Modell vorzunehmen. Eine weitere Aufgabe liegt in der Verwaltung von Ansichten (V), die, sobald sich das Modell ändert, von diesem über die Art der Änderung benachrichtigt werden. Diese Ansichten sind ihrerseits die Steuerungseinheiten der Clients und werden im vorliegenden Design als Proxys bezeichnet. Die Clients nehmen demnach lediglich die Rollen entferntes View und eingeschränkter Controller ein. Entfernt bedeutet in diesem Zusammenhang, dass die Clients auf einem anderen Rechner als die Serverkomponenten laufen. Eingeschränkt beschreibt, dass ein Server (genauer das implementierte Regelwerk) die vom Client initiierten Modelländerungen anpassen, abändern oder sogar ablehnen kann. Dieses Vetorecht des Servers ist insbesondere hinsichtlich unfairer Spieler (Cheater) von Nöten. Zusätzlich bietet die zentrale Datenverwaltung auch eine einfache Möglichkeit der Abrechnung, falls die angebotenen Leistungen oder Spielzeiten kostenpflichtig sein sollen. 4.2 Drop’n Pick! B A C Abbildung 4.2: Drop’n Pick! Eine detailierte Beschreibung einer typischen Spielsituation mit drei Spielern soll einen anschaulichen Einstieg in die Funktionsweise der in Abschnitt 4.1 skizzierten Komponenten von Mamuneen bieten. Der Fokus liegt auf der Analyse des Flusses von Informationseinheiten in einer Mehrspielersimulation. Bereits in diesem Fall lassen sich die Phänomene der unweigerlich eintretenden Inkonsistenz verteilter Anwendungen beobachten. Diese Beobachtung sind Grundlagen der später festgehaltenen Aufgaben und Pflichten der Serverkomponente. KAPITEL 4. DESIGN 25 Das betrachtete Spiel ist sehr simpel aufgebaut. Die drei Spieler bewegen sich frei auf einer Ebene und können auf dem Boden befindliche Gegenstände aufheben und bereits aufgenommen Gegenstände wieder ablegen. Spieler A wird zunächst einen Gegenstand ablegen. Die beiden Kontrahenten versuchen dann diesen aufzuheben. 4.2.1 DropItemCommand Die Clientapplikation interpretiert eine Aktion des Benutzers A (Tastaturdruck, Maustastenklick oder ähnliches) als dessen Wunsch einen Gegenstand auf der Erde abzulegen. Dazu generiert die Applikation das entsprechende Kommando new DropItemCommand(34527), mit dem genau zwei Fakten festgehalten werden. Erstens wird die gewünschte Aktion eindeutig durch den Namen des Kommandos festgelegt und zweiClient tens bestimmt der (hier zufällig gewählte) Zahlparameter das Objekt der Handlung. Das Kommando wird zu einem Datenpaket konvertiert und an den Server versendet. Dabei nutzt das Paket den bereits etablierten Kanal zu seinem Proxy. Serverseitig wird jedes Paket von genau einem Transceiver entgegenommen. Dort angekommen, kann das Paket anhand seines Proxys genau dem Benutzer A zugeordnet werden. Damit ist die Position, an der das System gleich Transceiver einen Gegenstand generieren wird, eindeutig bestimmbar. Doch bis dahin ist es noch ein langer Weg. Zunächst bleibt das Datenpaket intakt – und sein Inhalt unangetastet. Nachdem ein Paket vollständig empfangen wurde, wird es zusammen mit einem Eingangsstempel in eine Arbeitskapsel gesteckt. Ein Eingangsstempel gibt Auskunft über den Absender und die Empfangszeit der Kapsel. Alle Arbeitskapseln werden in der Reihenfolge ihrer Erstellung am Ende einer Warteschlange angefügt. Am Kopf dieser Schlange stehen dann die eigentlichen Arbeiter“: die Server. ” Die Server enthalten den Code der die Simulation beschreibt. Ein Server nimmt sich immer genau einer Kapsel an. Sollten alle Server beschäftigt sein – es steht also kein Server freier Arbeiter“ am Kopfende der Schlange – und gleich” zeitig neue Arbeitskapsel entstehen, wächst diese bis zu einer kritischen Länge. Dem Transceiver steht zur Pufferung der Kapseln zu wenig Arbeitsspeicher zur Verfügung. Spätestens jetzt müssen Gegenmaßnahmen eingeleitet werden. KAPITEL 4. DESIGN 26 Es bieten sich zwei Möglichkeiten an um die Schlange zu verkürzen. Defensiv durch den Ausschluss von Spielern: Die damit einhergehende Reduktion der kapselgenerierenden Clients verringert die Produktionsrate neuer Aufträge. Weiterhin man kann alle Kapseln der ausgeschlossenen Clients aus der Schlange herausnehmen. Dieses Vorgehen zieht aber unweigerlich den Unmut der hinter den Clients sitzenden Benutzer auf die Serverbetreiber. Offensiv durch das Engagement neuer Arbeiter“: Es werden neue ” Arbeiter“ engagiert, bis die Warteschlange schrumpft. Wenn auf der ” Gegenseite die Kapselproduktion herunterfährt, entlässt ein geeigneter Mechanismus gerade so viele Server, bis sich Produktion und Konsum die Waage halten. Das ist das optimale Verhalten eines im Abschnitt 2.1.1 beschriebenen Echtzeitsystems. Nach diesem Exkurs zum Producer/Consumer–Pattern geht es nun zurück zum eigentlichen Verarbeitungsprozess. Hat ein Server eine Datenkapsel zugeteilt bekommen, macht er sich schnellstmöglich an deren Verarbeitung. Da- Server zu wandelt er das gekapselte Datenpacket in das entsprechende Kommando-Objekt um. Das resultierende Objekt wird als Parameter der Methode execute(DataCapsule, Database DropItemCommand) übergeben. Hier kann nun endlich die Spiellogik greifen und die gewünschte Aktion ausführen. In diesem Beispiel fordert der Server von der Datenbank zwei Referenzen zur Änderung an: das Spielerobjekt des Benutzers A und die Liste aller Bodenobjekte. Die Nummer des Objekts, das fallengelassen werden soll, wird aus dem Kommando extrahiert. Als nächstes wird die Nummer (und somit der Gegenstand) aus dem Inventar des Spielers gelöscht und dann der Bodenobjektliste hinzugefügt. Dabei erhält der Gegenstand eine Position in der Nähe des Spielers A. Beide Referenzen werden wieder an die Datenbank zurückgegeben und damit wird die Transaktion abgeschlossen. Zu diesem Zeitpunkt ist die Veränderung in der Simulationsdatenbank bereits dauerhaft und wird sich in der nächsten Anforderung der veränderten Objekte widerspiegeln. Diese Beobachtung wird im Abschnitt 4.2.2, der das Aufheben dieses Gegenstandes beschreibt, eine große Rolle spielen. A KAPITEL 4. DESIGN 27 Zum Abschluss fehlt noch die Benachrichtung an alle Benutzer, dass ein neuer Gegenstand auf dem Boden liegt. Dieses Ereignis wird durch das Kommando new NewItemCommand(34527, COIN, x, y) beschrieben. Neben der Identifikationsnummer ist der Typ und der Ort des Gegenstandes codiert. Die Information, wer den Gegenstand fallen ließ, geht in diesem Fall verloren. Der Vorteil dieses generischen Kommandos ist, dass es dadurch auch dazu verwendet werden kann, neue, system–generierte Gegenstände in das Spiel einzuführen. Das NewItemCommand–Kommando wird zu einem Datenpaket konvertiert und in einer Ausgangskapsel eingebettet, die Auskunft über die Zielgruppe gibt. Die Datenkapsel wird an eine Multicastgruppe gesendet, in der jeder Server Transceiver als Konsument registriert ist. Empfängt ein Transceiver eine auswärts gerichtete Kapsel über die Multicastgruppe, entpackt er diese und fügt jedem Proxy eine Kopie des gekapselten Datenpakets hinzu. Das Kopieren ist notwendig, da jeder Proxy die Daten in einem anderen Tempo an seinen Client sendet. Damit ist die serverseitige Bearbeitung des Commands abgeschlossen – das NewItemCommand-Datenpaket trifft bei den Receivern der Clientapplikationen ein. Da es hier jeweils nur einen Kanal gibt, aus dem Daten (vom Server) kommen können, reicht eine einzige aktive Instanz zur Bearbeitung eingetroffener Pakete. Der Algorithmus, der dem empfangenen Kommando ein Verarbeitungsmethode zuordnet, gestaltet sich analog zu dem Algorithmus der beim Server beschriebenen Verfahren. 4.2.2 PickItemQuery Im weiteren Spielverlauf sehen alle Benutzer den neuen Gegenstand (fast) gleichzeitig auf ihrem Bildschirm auftauchen. Selbst dieser spezielle und sehr unwahrscheinliche Fall kann den weiteren Ablauf des Spiels nicht konistenter machen: Spieler B und C wollen beide den neuen Gegenstand aufnehmen und senden diesen Wunsch gleichzeitig ab. Sollten die zwei Kommandos, weiterhin parallel, auch noch in verschiedenen Servern abgearbeitet werden, muss spätestens hier eine Synchronisierung stattfinden. Diese Aufgabe übernimmt die Datenbank. Die nächsten Absätze beschreiben den Vorgang detailierter. Succeeded Spieler B sieht in der Nähe von Spieler A einen neuen Gegenstand auf dem Boden erscheinen. B verkürzt die Distanz und erhält durch das Benutzerinterface eine Möglichkeit angeboten, den Gegenstand aufzuheben. B gibt den entsprechenden Befehl, woraufhin die Clientapplikation KAPITEL 4. DESIGN 28 das new PickItemQuery(34527) generiert. Das Kommando wird konvertiert, verpackt und versendet. Bei einem Server angekommen, nimmt es seinen Weg bis hin zu der PickItemReply execute(PickItemQuery) Methode. Wie beim Fallenlassen werden zwei Referenzen von der Datenbank angefordert: das Spielerobjekt des Benutzers B und die Liste aller Bodenobjekte. Da zu dem Zeitpunkt der Anforderung keine weiteren Zugriffe auf das Spielerobjekt oder die Liste der Bodenobjekte stattfinden, erhält der aktive Server prompt die benötigten Referenzen. Die Transaktion ist abgeschlossen, sobald der Gegenstand der Bodenobjektliste entnommen und der Inventarliste von B hinzugefügt wurde. Von diesem Zeitpunkt an können wieder andere Server auf die Objekte in der Datenbank zugreifen. Danach werden zwei Kommandos erzeugt: 1. Dem Spieler B muss signalisiert werden, dass er den neuen Gegenstand erfolgreich aufnahm. Damit kann die Clientapplikation des Spieler B ihrerseits zwei Änderung an der visuellen Präsentation der Welt vornehmen: das Entfernen des Gegenstandes vom Boden und die Aktualisierung der Inventarliste. Damit gleicht die von B gesehene Welt – zumindest in Bezug auf den Gegenstand 34527 – wieder der im Server vorliegenden Version. 2. Jedem anderen Spieler muss zur konsistenten Darstellung der Welt mitgeteilt werden, dass der Gegenstand nicht länger auf dem Boden liegt. Dazu dient das Kommando RemoveItemCommand(34527). Sobald Client A und C dieses Kommando ausführen, ist ihre Ansicht wieder auf dem aktuellen Stand der Dinge. Allerdings ist bis zur Ausführung des Kommandos der Gegenstand auf dem Bildschirm präsent und es ist für A und C legitim diesen aufheben zu wollen. Somit kann es während der gesamten Zeitspanne zwischen den Kommandos NewItemCommand und RemoveItemCommand zu Konflikten kommen. Eine mögliche Konfliktbehandlung wird im nächsten Abschnitt erläutert. Failed Auch Spieler C möchte den Gegenstand, der gerade in der Nähe von Spieler A auf dem Boden erschien, aufnehmen. Das von diesem Client generierte Kommando new PickItemQuery(34527) erreicht einen anderen Server als das von Spieler B gesendete Pendant. Die zur gleichen Zeit ausgeführte Behandlungsmethode fordert von der Datenbank neben des Spielerobjekts C ebenfalls die Bodenobjektliste zum Verändern an. In diesem Fall ist die parallele von Spieler B angestoßene Transaktion noch nicht beendet. Dieser Zugriffskonflikt führt zu einer Verzögerung der Datenbankanfrage. Sobald die KAPITEL 4. DESIGN 29 Datenbank die Referenz auf die Liste der Bodenobjekte an diesen Server gibt, kann die Verarbeitung fortgesetzt werden. Nun wird bei dem Versuch, das gewünschte Objekt aus der Bodenobjektliste zu entfernen, festgestellt, dass sich kein Objekt mit dieser Nummer in der Liste befindet. Dieser Fehlstand wird von der Methode mit der Generation eines PickItemReply$Failed Kommandos als Rückgabewert der Methode quittiert. Der Transceiver übermittelt das Kommando so schnell wie möglich an die Clientapplikation des Spielers C. Dort angekommen, kann das Benutzerinterface den misslungenen Versuch, den Gegenstand 34527 aufzunehmen, geeignet darstellen (akustische oder optische Fehlermeldung). Desweiteren kann auf Grund dieser Information versucht werden den Gegenstand aus dem Datenmodell des Clients zu entfernen. 4.2.3 Zusammenfassung Die detailierte Beobachtung einer Mehrspielersituaton konnte neben der Identifikation einzelner Komponenten und deren Aufgaben auch einige inhärente Konsistenzprobleme und mögliche Lösungsansätze darstellen. • Die dabei willkürlich gewählte Beschränkung auf drei Spieler ist kein Verstoß gegen die Allgemeingültigkeit der gemachten Beobachtung. • Das dem Datentransfer zugrundeliegende Netzwerkprotokoll sollte verbindungsorientiert sein und die Übertragung der Kommandos zusichern. • Auf Client– und Serverseite werden Kommandos verpackt, versendet, empfangen und ausgeführt. Daher liegt es nahe, dafür eine gemeinsame Basisklasse vorzusehen. Bevor die Komponenten sowie ihre Aufgaben und Zuständigkeiten dokumentiert werden, wird eine Lösungsmöglichkeit für den Beitritt neuer Spieler in ein laufendes Spiel beschrieben. KAPITEL 4. DESIGN 4.3 30 Late-Join Im Allgemeinen versteht man unter einem Late-Join Vorgang die Integration einer neuen Komponente in ein bereits bestehendes System. Die Schwierigkeit des Vorgangs wird durch die Aktivitäten der Systemkomponenten bedingt, da sich bei hoher Aktivität der Systemzustand laufend ändert. Die Integration gestaltet sich bei Systemen mit wenig und selten miteinander kommunizierenden Komponenten einfacher als bei solchen, die viel und ständig Informationen austauschen. In einem ereignisgesteuerten System wie Mamuneen reicht es daher aus, die Ereignisbehandlung der von der Integration betroffenen Komponenten für der Änderungszeitraum zu unterbinden. Dadurch kann sich der Zustand der Komponenten nicht ändern; sie sind eingefro” ren“. Die neue Komponente kann nun in die fixierte Umgebung integriert werden. Nachdem dieser Vorgang möglichst zügig abgeschlossen ist, wird die Ereignisbehandlung für alle Komponenten, inklusive der neuen, aktiviert. Das Late-Join Problem lässt sich in dieser Arbeit an zwei Stellen erkennen: bei dem Beitritt eines neuen Spielers in die bereits belebte virtuelle Welt und bei der Zuschaltung neuer Serverkomponenten, um der anfallenden Mehrbelastung durch viele neue Clients Rechnung zu tragen. Im Folgenden wird der konkrete Ablauf eines Late-Join Vorgangs eines neuen Spielers beschrieben. 4.3.1 Late-Join eines Clients Der Server muss der Clientapplikation des neuen Spielers, im Folgenden Joiner genannt, den aktuellen Spielstand übermitteln. Der gesamte Spielstand setzt sich aus den Zuständen der einzelnen Spielobjekte zusammen. Diese Objekte kapseln entweder sehr primitive (ein Zahlwert, eine Zeichenkette) oder beliebig komplexe Datenstrukturen (Listen oder Bäume von Objekten). In der hier vorgestellten Lösung muss für jedes dieser Spielobjekte ein Query/Reply - Kommandopaar vom Spielprogrammierer implementiert werden. Mit dem Querykommando spezifiziert der Joiner das Spielobjekt eindeutig und fordert vom Server dessen Zustand an. Der Server packt in das Replykommando alle benötigten Daten, aus denen der Joiner das Spielobjekt vollständig rekonstruieren kann. Zur Zeit des Packens der Spielobjektdaten darf keine Veränderung an diesen vorgenommen werden. Das Replykommando stellt demnach eine Momentaufnahme des kompletten Objekts zu einem bestimmten Zeitpunkt dar. Sobald das Auslesen der Daten beendet ist, wird es auf dem normalen Weg über das Proxyobjekt zum Joiner übermittelt. Danach können andere Kommandos die Daten wieder ändern. Je nach Datenmenge kann die Übermittlung des Replykommandos etwas länger dauern. KAPITEL 4. DESIGN 31 Alle weiteren Veränderungen, die das Spielobjekt in der Zwischenzeit erfährt, werden an die Warteschlange des Proxys angefügt. Beispiel Anhand der Liste aller auf dem Boden befindlichen Gegenstände des im vorherigen Abschnitt beschriebenen Spiels Drop’n Pick wird ein Late-Join Vorgang ausgeführt. Zunächst sendet der Joiner mittels eines AllItemsQuery-Kommandos die Anfrage an den Server und wartet auf den Empfang der entsprechenden AllItemsReply-Instanz. Der Server packt die Daten aller Gegenstände in das Reply-Kommando und stellt sicher, dass in dieser Zeit kein DropItemCommand oder PickItemQuery die Liste verändern kann. Das je nach Größe mit Java-Boardmitteln komprimierte AllItemsReply-Paket wird an den Proxy des Joiners übergeben und von diesem schnellst möglich übertragen. Damit ist sichergestellt, dass alle nun folgenden NewItemCommand und RemoveItemCommands auf der aktuellen Version der Liste arbeiten. Die Liste aller Spieler und deren jeweilige Daten wie ID und Name stellt Mamuneen dem Joiner automatisch bereit. 4.4 Komponenten Die abgebildeten Klassendiagramme veranschaulichen die Mitglieder der Typen und geben erste Hinweise auf ihre Anwendung und Aufgaben im Gesamtkonzept. Wichtige Eigenschaften sind genauer erläutert, andere bewusst, da selbsterklärend, kommentarlos belassen. 4.4.1 Command Die abstrakte Klasse Command ist als FlyWeight [GHJV95, Mets02] modelliert, da zur Laufzeit sehr viele Instanzen dieser Klasse erzeugt werden und mehrere Komponenten diese Instanzen referenzieren. Die Spieleentwickler müssen ihre Kommandoklassen von dieser Basisklasse ableiten. Dadurch wird sichergestellt, dass jedes Kommando sich selbständig aus einem Datenpuffer wiederherstellen und auch umgekehrt seine Daten in einen Puffer schreiben kann. Dazu dienen die folgenden Methoden: void dataIn(ByteBuffer in) throws BufferUnderflowException; void dataOut(ByteBuffer out) throws BufferOverflowException; Die in der Signatur verwendete Klasse ByteBuffer und die beiden Exceptions sind im Paket java.nio enthalten. ByteBuffer-Objekte beschleunigen den nativen Zugriff auf die in ihnen verwalteten Daten durch das Betriebssystem. KAPITEL 4. DESIGN 32 Zudem sollen ableitende Klassen die Änderungen, die dem Modell bei der Ausführung eines solchen Commands widerfahren, möglichst in ihrem Namen genau beschreiben: class RemoveArbitraryItemFromFloor extends Command { ... } Der voll-qualifizierte Klassenname dient zur eindeutigen Identifikation der Command-Klasse. Dadurch können spezielle Beschleunigungstrukturen (wie indizierte Listen) zum Einsatz kommen, die auch gleichzeitig die Datengröße eines serialisierten Kommandoobjekts minimieren. 4.4.2 Application Das Interface Application repräsentiert ein durch Command-Objekte (siehe Abschnitt 4.4.1) gesteuertes und angetriebenes Objekt. Es bildet die Grundlage der exekutiven Komponente von Mamuneen und ist die Basisklasse der nachfolgend beschriebenen Client- und Serverklassen. «interface» mamuneen::Application + + + + + + + + + + + + + + + + addCommandClass(Class) : void addContext(Context) : void addExecutor(Executor) : void getName() : String getPhase() : Phase getRunningMillis() : long getState() : State isRunning() : boolean isStartable() : boolean isStopped() : boolean isVerbose() : boolean process(Command) : void setName(String) : void setVerbose(boolean) : void start() : void stop() : void Der Lebenszyklus einer Application gestaltet sich dreistufig: • Erzeugung (inklusive Vorinitialisierung) • Initialisierung (optional, aber empfohlen) • Start/Stopp (wechselseitig) KAPITEL 4. DESIGN 33 Erzeugung Durch die Erzeugung eines Application-Objekts erhält der Anwender eine Referenz auf dieses zurück. Die Eigenschaften des Objekts sollen nach der Erzeugung sinnvoll vorbelegt (vorinitialsiert) sein und ein Starten ohne weitere Initialisierung ermöglichen. Initialisierung Die Initialisierung eines Application-Objekts erfolgt nach dessen Erzeugung. Eine bestimmte Reihenfolgen der Initialisierungsschritte ist implementationsabhängig, sollte jedoch möglichst willkürlich geschehen dürfen, damit eine hohe Flexibilität bezüglich der Vorlieben des Anwenders gewährleistet ist. Die wichtigsten Initialisierungsmethoden sind: setName(String name) Setzt den Namen der Applikation auf name. Der Name einer Applikation soll nicht nur in der toString() Methode anwendung finden, sondern insbesondere dem Endanwender visuell präsentiert werden. addCommandClass(Class commandClass) Registriert die angebene Klasse commandClass in der Liste der der Application bekannten CommandKlassen. Die Behandlung von Duplikaten ist der Implementierung überlassen. addContext(Context context) Registriert das bereits instantiierte Context-Objekt context. Der erste Aufruf dieser Methode soll den sogenannte Root-Context registrieren. Zusicherungen an die Konstrukturen werden hier nicht gemacht. Es sollten jedoch sinnvolle Konstruktoren dem bequemen Anwender an die Hand gegeben werden, die das erzeugte Application-Objekt mittels der eben beschriebenen Methoden entsprechend initialisieren. Insbesondere der leere Konstruktor soll das erzeugte Objekt in einem startbaren Zustand abliefern. Start/Stopp Die start()-Methode startet ein bereits initialisiertes Application-Objekt. Dabei ist der Implementierung überlassen, wie sie auf nicht ausreichend initialsierte Felder reagiert oder ob sie auch das Setzen neuer Werte zur Laufzeit zulässt. Eine Nachbedingung der start()-Methode ist, dass der aufrufende Thread1 zurückkehrt und nicht in einer Endlosschleife, insbesondere nicht in 1 Thread of control, Kontrollfluss KAPITEL 4. DESIGN 34 der Command-Execution-Loop, verharrt. Dieses Verhalten gleicht dem der Methoden Thread#start() und JComponent#show(). Nach ihrer Beendigung soll start() das Application-Objekt in einem aktivem Zustand überlassen, indem es angelieferte Command-Objekte verarbeiten kann. Unbekannte Command-Objekte, also Instanzen von vorher nicht via Application#addCommandClass(Class) der Applikation mitgeteilten Klassen, sind zu ignorieren. Die stop()-Methode hält die Applikation an und setzt sie optional in den Zustand vor dem Start um einen schnellen Neustart ohne Neuerzeugung des Application-Objekts zu ermöglichen. Diese Methode soll erst dann zum Aufrufer zurückkehren, wenn alle Threads unterbrochen und beendet sind. Kapitel 5 Implementierung Nachdem aus der Anforderungsanalyse das Mamuneen Konzept entwickelt wurde, wird in diesem Kapitel dessen Umsetzung in Auszügen beschrieben. 5.1 Externe Bibliotheken Neben den Standardpaketen des Java Development Kits wurden Bibliotheken dritter Anbieter eingesetzt. Die im Rahmen dieser Diplomarbeit wichtigsten sollen im Folgenden erläutert werden. 5.1.1 Logging Loggen ist ein gängiges Mittel den Ablauf eines Programmes nachvollziehbar zu machen. Für die Erstellung der Log-Nachrichten wird sowohl beim Server als auch beim Client Jakarta Commons Logging (JCL) von Apache verwendet: The Logging package is an ultra-thin bridge between different logging libraries. Commons components may use the Logging API to remove compile-time and run-time dependencies on any particular logging package, and contributors may write Log implementations for the library of their choice. Die Referenzimplementierung nutzt das Paket Apache Log4j, weil es sehr effizient programmiert ist und sich das Verhalten ohne Neukompilieren des Spiels anpassen lässt. 35 KAPITEL 5. IMPLEMENTIERUNG 5.1.2 36 Webadmin Eine Anforderung an den Server ist die Übersicht und Kontrolle über das aktuelle Spielgeschehen. Die in dieser Arbeit implementierte Lösung bietet diese Kontrolle ohne dass der Administrator in das Spiel einsteigen muss. Jetty ist der Name für ein komplett in Java geschriebenen Webserver und Servletcontainer von Mortbay. Jetty bietet neben dem eigenständigen Betriebsmodus auch die Möglichkeit innerhalb eines anderen Programmes eingebettet zu werden. Durch die Intergration in einen Mamuneen Server stehen dem Webserver alle Daten des Spiels zur Verfügung. Diese Daten können bei der Generation der Webseiten einfach aus den Java-Objekten ausgelesen werden. Damit wird dem Administrator ein unmittelbarer Einblick in den Spielzustand gewährt. Der direkte Zugriff des Webservers auf die Methoden der Java-Objekte kann auch aktiv genutzt werden. Dazu muss ein Servlet die vom Administrator gewünschte Aktion interpretieren und in einen Methodenaufruf umwandeln: private void perfomAction(String action, HttpServletRequest request) { ... // Kick client from ${slot}. if (action.equalsIgnoreCase("kick")) { int slot = Integer.parseInt(request.getParameter("slot")); User user = server.roster.all[slot]; server.roster.kick(user, "Be gone!"); return; } } HTML Dateien bilden normalerweise die Grundlage der von Webservern angebotenen Webseiten. Da die Inhalte der Webadmin-Seiten jedoch den aktuellen Stand der Dinge widerspiegeln müssen, wurde ein Verfahren erarbeitet, das statische HTML Texte mit dynamischen, Java-ähnlichen Elementen mischt. Bevor eine solche Webseite an den Browser gesendet werden kann, muss der Webserver die dynamischen Element auflösen und in normalen HTML Text umwandeln. Diese Aufgabe übernimmt in dieser Implementierung das TemplateServlet, welches mittlerweile in das Groovy-Projekt aufgenommen wurde. Groovy ist eine an Java angelehnte Programmiersprache, die auch zur Laufzeit interpretiert werden kann. Im folgenden Ausschnitt der index.html Datei wird die Liste aller Benutzer mit Hilfe einer for-Schleife aufgebaut. Dabei ist der Groovy Code entweder von den Markern <% %> eingerahmt oder, falls lediglich ein Textfragment generiert werden soll, mit ${} KAPITEL 5. IMPLEMENTIERUNG 37 gekennzeichnet. Alle anderen Passagen stellen normalen, HTML formatierten Text dar, der unverändert in das Zieldokument übernommen wird. <html> ... <h2>Roster</h2> <dl> <% for (user in server.roster.all) { %> <dt> #${user.getSlot()} <b>${user.getName()}</b> [<a href="/server?action=kick&slot=${user.getSlot()}">kick</a>] </dt> <dd> ${user.toString()} </dd> <% } %> </dl> ... </html> Links • http://jakarta.apache.org/commons/logging/ • http://logging.apache.org/log4j/ • http://jetty.mortbay.org/ • http://groovy.codehaus.org/ • TemplateServlet.java KAPITEL 5. IMPLEMENTIERUNG 5.2 5.2.1 38 Umsetzung CAPI: Die Command API Das CAPI-Objekt listet alle verfügbaren Kommandos eines Spiels auf. Diese Liste wird in der Initialisierungsphase der Applikation erstellt und muss auf beiden Seiten, also beim Server und den Clients, identisch aufgebaut sein. Sie stellt somit die zur Laufzeit unveränderliche Kernkomponente einer Applikation dar, die beim Versenden und Empfangen von Kommandos und bei der Zuordnung von Kommados zu ihren execute()-Methoden eine zentrale Rolle spielt. Die CAPI Liste lässt sich in die zwei Teile Basis- und Spielkommandos spalten. Die Basiskommandos sind in der Datei de.ctsr.mamuneen.Mamuneen deklariert und zur besseren Übersicht ebenfalls nach ihrer Funktion getrennt. Der erste in Auszügen gedruckte Funktionsblock dient hauptsächlich zum Testen der Implementierung und nennt sich SystemCapi. Der zweite Block mit den Namen UserCapi gruppiert sämtliche Kommandoklassen, die zur Verwaltung der Benutzerlisten benötigt werden. interface Mamuneen { interface SystemCapi { class Beep extends VoidCommand {} class Message extends StringCommand {...} ... } interface UserCapi { class Quit extends VoidCommand {} class Quitted extends UserCommand {...} class JoinQuery extends StringCommand {...} interface JoinReply extends Command { class Failed extends ByteCommand implements JoinReply {} class Succeeded extends AbstractCommand implements JoinReply {...} } class Joined extends UserStringCommand {...} ... } } KAPITEL 5. IMPLEMENTIERUNG 39 Die Namen der Kommandoklassen geben, wie gefordert, Auskunft über ihre Aufgaben und Einsatzgebiete. Zum Beispiel stellt das Kommando UserCapi.Quit eine Aufforderung dar, den sendenden Client aus dem Spiel zu entfernen. Da der Server den Client eindeutig über dessen Kanal bestimmt, braucht keine zusätzliche Information mitgesendet werden, die den Client spezifiziert. Deshalb kann die datenlose Basisklasse VoidCommand erweitert werden. Nachdem der Server alle nötigen Änderungen an der Referenzsimulation vorgenommen hat, sendet er seinerseits das Kommando UserCapi.Quitted an alle noch verbundenen Clients. Dieses Kommando enthält ausschließlich die ID, die dem Client zugeordnet war, welcher das Spiel soeben verließ. Mit den zwei Informationen Quitted und ID können die Clientapplikation ebenfalls die nötigen Änderungen vornehmen. Analog verhält es sich mit dem UserCapi.Joined Kommando. Der Server sendet damit die eindeutige ID, den Namen und weitere Daten über einen neuen Mitspieler an die bereits spielenden Clients. Dem entsprechenden UserCapi.Join Kommando wird ausserdem das reservierte Wort Query angehangen. Dadurch wird der Server angehalten eine Antwort, hier entweder JoinReply.Succeeded oder JoinReply.Failed, an den sendenden Client zu schicken. Normalerweise blockiert die Clientanwendung die Verarbeitung parallel durch andere Server verschickter Kommandos solange, bis die einer Query entsprechende Antwort Reply empfangen und ausgeführt wurde. Bevor der Server den Beitritt einen Mitspieler publiziert, überprüft dieser den vom Client im JoinQuery codierten Benutzernamen und vergleicht das Passwort mit dem in der Datenbank gespeicherten Version. Bei einer negativen Überprüfung der Angaben sendet der Server lediglich das Antwortkommando JoinReply.Failed an den sendenden Client. Andernfalls generiert der Server eine JoinReply.Succeeded Antwort. Dieses Kommando enthält die komplette Liste aller aktiven Spieler, inklusive dem neuen. Das macht ein zusätzliches Senden eines UserCapi.Joined Kommados überflüssig. Das Ausführen eines Kommandos in den Kommando-getriebenen Komponenten Server und Client geschieht durch einen Methodenaufruf innerhalb der jeweiligen Komponente. Der nächste Abschnitt erläutert diesen Vorgang genauer. KAPITEL 5. IMPLEMENTIERUNG 5.2.2 40 Binden von Kommandoklassen und executeMethoden Nachdem die CAPI-Liste fixiert ist, kann mit der Zuordnung der Kommandoklassen zu den entsprechenden Behandlungsmethoden begonnen werden. Eine nötige Vorbedingung ist, dass jedes Kommando nur genau einmal in der CAPI-Liste enthalten sein darf. Ist ein Kommando mehrfach aufgeführt, kann keine eindeutige Zuordnung vorgenommen werden. Die Zuordnung schlägt ebenfalls fehl, falls mehr als eine passende Behandlungsmethode für eine Kommandoklasse existiert. Das ist der Fall, sobald zwei verschiedene Klassen jeweils eine Methode mit equivalenter Signatur deklarieren und beide als Executoren bei der Applikation registriert sind. Unter Umständen kann eine einzige Behandlungsmethode von mehreren Kommandoklassen aus der CAPI-Liste als Ziel angesprungen werden. Dazu muss die Methode einen nicht direkt instantiierbare Typ als Parameter erwarten. Das kann entweder ein Interface oder eine abstrakte Klasse sein. Da diese beide Typen nicht explizit in der CAPI-Liste enthalten sein können, muss jedes eingetrage Kommando der Liste auf Zuweisbarkeit geprüft werden. Jede ungebundene Kommandoklasse, die als Parameter der untersuchten Methode in Frage kommt, wird mit dieser Methode verbunden. Als Beispiel ist das Paar class JoinQuery und interface JoinReply zu nennen. Zum JoinReply existiert eine Behandlungsmethode, die doppelt gebunden ist: das Kommando JoinReply.Succeeded und JoinReply.Failed werden beide von der selben Methode, nämlich void execute(UserCapi.JoinReply reply), behandelt. Durch diese Abbildung wird die folgende Struktur der Programmflusskontrolle nachgebildet und kann, wie danach beschrieben, durch eine generische Version ersetzt werden: void execute(Command command) { if (command.getClass() == UserCapi.Joined) { // // create and add new user object to the world // } else if (command.getClass() == UserCapi.Quitted) { // // remove specified user from the world // } KAPITEL 5. IMPLEMENTIERUNG 41 ... else { // // command class not processed // } } Stattdessen übernimmt folgender Code die Aufgabe des Methodenaufrufs: void execute(Command command) { Method method = (Method) execmap.get(command.getClass()); if (method == null) { throw new Error("Command class not bound."); } method.invoke(command); } /** * Create and add new user object to the world. */ void execute(UserCapi.Joined command) { ... } ... Die Erstellung der im zweiten Ausschnitt verwendeten Variablen execmap geschieht mithilfe der Java-internen Reflektionswerkzeuge aus dem Paket java.lang.reflect. Der Einsatz einer auf Hashing basierenden Map-Implementierung garantiert einen konstanten und gleichzeitig sehr geringen Zeitaufwand zum Identifizieren der zur Kommandklasse passenden Methode. Mehr Information können dem beiliegenden Quellcode entnommen werden. KAPITEL 5. IMPLEMENTIERUNG 42 Der Vorteil dieses generischen Verfahrens liegt zunächst in der besseren Übersicht durch die Trennung der Behandlungsquellcode in verschiedene Methoden. Zum Einen wird damit eine potentielle Spaghetticode-Methode verhindert. Man stelle sich die oben zuerst angeführte execute-Methode für mehr als 100 Kommandos vor. Zum Anderen erleichtern moderne Java Entwicklungsumgebungen den Programmierern das Auffinden von Methoden durch Baumansichten und andere visuelle Komponenten. Der größte Vorteil liegt jedoch in der einfachen Erweiterbarkeit des Systems durch neue Kommandos und ihren entsprechenden Behandlungsmethoden. Aufgrund des dynamischen Bindens muss kein bestehender Quellcode geändert und neukompiliert werden. KAPITEL 5. IMPLEMENTIERUNG 5.2.3 43 Java NIO Selector Die Transceiver sind an einer performanzkritischen Position des Designs angesiedelt. Deshalb wurde bei der Implementierung ein besonderes Augenmerk auf die Verwendung systemnaher Verfahren und Methoden gelegt. Das Paket java.nio bietet seit der Version J2SE 1.4 Unterstützung für gemultiplexte und nicht blockierende I/O Operationen. Die dort angestammten Klassen und Funktionen werden in den Serverkomponenten verwendet. Durch den Einsatz von Selektoren, selektierbaren Kanälen und Selektionsschlüsseln können zum Beispiel die Transceiver mehr Clients verwalten als mit einem Thread-orientierten und blockierenden Ansatz. Hierbei müssen pro Client zwei Threads gestartet und abgestellt werden, die die lesende und schreibende Kommunikation mit die Socketverbindung übernehmen. Das ist problematisch, da die Anzahl der Threads, die ein Betriebsystem effizient verwalten kann, limitiert ist. Gerade auf Maschinen mit nur einem Hauptprozessor kann der Zeitaufwand der Threadverwaltung sehr hoch werden und somit das komplette System ausbremsen. Das hier vorgestellte Verfahren kommt im Minimalfall mit lediglich einem Thread aus. Dabei können hunderte Clients effizient und betriebsmittelschonend mittels eines multiplexenden Selektors verwaltet werden. Das Selektorobjekt bietet dafür unter anderem eine blockierende Methode select() an. Der aufrufende Thread verweilt solange in dieser Methode, bis mindestens ein Kanal Behandlung erfordert. Ein Transceiver erzeugt zunächst einen ServerSocketChannel. Diesen Kanal, genauer das darunter liegende ServerSocket Objekt, bindet der Transceiver an eine wohl-bekannten Portnummer und versetzt ihn in den nicht blockierenden Kommunikationsmodus. Den so konfigurierten Kanal registriert der Transceiver in einem bereits geöffneten Selektor. Außerdem wird bei der Registrierung eines selektierbaren Kanals festgelegt, bei welchen Operationen der Selektor Arbeit signalisieren soll. Es gibt vier Operationen, die in der Klasse java.nio.SelectionKey als Konstanten definiert sind: OP_ACCEPT, OP_CONNECT, OP_READ und OP_WRITE Es können, sofern der Kanal diese zulässt, auch beliebige Operationskombinationen gebildet werden. Die Menge an Operationen, die beim Registrieren angegeben wird, nennt sich interest set. Zum Beispiel ist OP_ACCEPT die einzig gültige Operation der ServerSocketChannel Klasse. Sobald ein Client versucht eine Verbindung zu dem wohlbekannten Port aufzubauen, signalisiert das der Selektor, in dem er den bei der Registrierung erzeugten Selektionsschlüssel in der Menge der selektierten Schlüssel an den Transceiver zurückgibt. Dabei kann jeder Schlüssel eine beliebige, von KAPITEL 5. IMPLEMENTIERUNG 44 Null verschiedene Menge an fertigen Operationen enthalten. Diese Menge nennt sich ready set. In diesem Fall kann auf dem ServerSocketChannel die Methode accept() aufgerufen werden, die als Resultat einen normalen SocketChannel liefert. Das ist nun der serverseitige Endpunkt der Socketverbindung zu einem neuen Client. Bevor dieser Kanal dem Selektor hinzugefügt werden kann, muss er ebenfalls in den nicht blockierenden Kommunikationsmodus geschaltet werden. Da über diesen Kanal Daten vom Client gelesen und an den Client versendet werden sollen, wird bei der Registrierung sowohl OP_READ als auch OP_WRITE angegeben. Sobald ein Client Daten an der Server geschickt hat, selektiert der Selektor den entsprechenden Selektionsschlüssel für das ready set und setzt den Schalter für die Leseoperation OP_READ. Man sagt, dass der Schlüssel lesbar ist. Es wird jedoch keine Aussage über die Datenmenge gemacht, sondern nur darüber informiert, dass Daten an dem dem Schlüssel zugeordneten Kanal zum Lesen bereit stehen. Diese können nun durch den Selektionsthread gelesen und weiterverarbeitet werden. Nachdem alle momentan vorliegenden Daten an einen bestimmten Client versendet wurden, muss der Schalter OP_WRITE aus dem interest set des Selektionsschlüssels entfernt werden. Das verhindert eine Selektion des Schlüssels beim nächsten Aufruf von select(). Sollen weitere Daten an den Client geschickt werden, muss der Schalter erneut gesetzt werden. Dazu bietet die Klasse SelectionKey die Methode interestOps(int). Sollten beim Akzeptieren, Lesen oder Schreiben Fehler auftreten, kann das auf eine vom Client aus unterbrochene Verbindung hinweisen. Diese Ausnahmefälle werden, wie in Java üblich, durch die Klasse java.io.IOException dargestellt und von der Implementierung abgefangen. Mit dem Schließen eines Kanals werden die entsprechenden Selektionsschlüssel ungültig und automatisch aus der Menge der vom Selektor beobachteten Schlüssel entfernt. Bei diesem Vorgang generiert die Java Implementierung der Firma Sun in der Version 1.4 für Windows einen Zugriff auf eine ungültige Referenz. Dieser Fehler ist im Kapitel 7 detailierter beschrieben. Kapitel 6 Ein Demospiel 6.1 Design Im Folgenden wird das Konzept des Demospiels vorgestellt. 6.1.1 Allgemein Namensfindung Das Spiel wird auf Run To The Hills getauft. Dieser Titel beruht einerseits auf der hügeligen Beschaffenheit des Spielareals, andererseits auf dem gleichnamigen Song der Band Iron Maiden aus dem Jahre 1982. Hintergrund Verschiedene Fraktionen wollen einen Planeten besiedeln. Dazu haben sie jeweils eine stationäre, nicht einnehmbare Basis auf der Oberfläche errichtet. Von dieser aus entsenden sie holographische Projektionen ihrer selbst, um Territorium für sich einzunehmen. Ziel Die Spieler versuchen Flaggenpunkte der gegnerischen Teams einzunehmen und die eigenen Flaggen zu verteidigen. Punkte sammeln sie, indem sie Flaggen zum eigenen Team umfärben oder andere Spieler zu ihren Spawnpunkten zurückschicken. Das Team, welches als erstes eine bestimmte Punktezahl erreicht, gewinnt die laufende Runde. Ist in einer Welt eine voreingestellte Anzahl von Runden gespielt worden, beginnt ein neues Spiel in einer neuen Welt. 45 KAPITEL 6. EIN DEMOSPIEL 46 Ein möglicher Spielablauf Der Spieler startet die Client Applikation und wird nach seinem Namen gefragt und nennt sich Dude. Zusätzlich kann er noch weitere Einstellungen, die seinen Rechner betreffen, angeben. Danach verbindet er sich mit einem ihm bekannten Server, auf dem bereits ein Spiel im Gange ist. Es befinden sich 23 Spieler, aufgeteilt in drei Teams (Rot, Grün und Blau) auf dem Server. Da Team Grün zur Zeit zahlenmäßig unterlegen ist, wird Dude automatisch diesem Team zugewiesen. Jetzt wählt Dude seine Rolle, die er im Spiel ausüben will. Er entscheidet sich für den schnellen jedoch mit schwachem Energieschild ausgerüsteten Angreifer. Außerdem bestimmt er Flagge 4 als seinen Startpunkt, da Team Grün bereits mehrere Flaggen kontrolliert. Er wartet die verbleiben Sekunden seiner Spawnzeit ab. [. . . ] Dude erscheint in der Nähe der Flagge 4. Diese befindet sich auf einer kleinen, bewaldeten Insel. Zusammen mit zwei Teamkollegen watet er durch das seichte Küstenwasser in Richtung Flagge 6, welche gerade von fünf Spielern aus Team Rot kontrolliert wird. Der folgende Ansturm ist kurz und erfolglos: Dude und seine Mitangreifer werden durch Antiprotonenstrahlen aufgelöst. Die drei glücklosen Angreifer müssen erneut einen Startpunkt und eine Rolle wählen, sowie einige Sekunden auf die Rekalibrierung ihrer Projektionsmatrix warten. [. . . ] Eine halbe Stunde später endet die Runde mit einem Sieg von Team Rot! 6.1.2 Anforderungsanalyse √ und × siehe Abschnitt 6.2. Zur Bedeutung der Symbole Allgemein • Große, zufällige Spielwelten • Viele Spieler √ √ • Mehr als zwei Teams √ • Spieler sind unbelebte Hologramme √ • Spieler starten bei festgelegten Flaggenpunkten √ • Gegnerische Flaggenpunkte einnehmen um Punkte zu erzielen • Eigene Flaggenpunkte verteidigen √ • Anzahl der Flaggen richtet sich nach Weltgröße × √ KAPITEL 6. EIN DEMOSPIEL 47 • Einteilung in Runden und Welten × • Möglichkeit, andere Spieler zu einem √Spawnpunkt (Startpunkt) zurückzuschicken um Punkte zu erzielen • Verschiedene Spielerrollen × • Verschiedene Ausrüstungsgegenstände × • Bonuskisten × • Chatfunktion (Texte versenden, global oder teamintern) √ • Teaminterne Schnellkommandos per Tastenkombination × • Punktelisten / Highscore, sowohl für Team als auch Einzelspieler × • Statistiken × • Continuous World (Spiel ist immer offen) √ • Spectators (Zuschauer) sind erlaubt × • Verschiedene Serveroptionen √ • Verschiedene Clientoptionen, wie Bildschirmauflösung und Farbtiefe Der Server Bei Serverstart werden unter anderem festgelegt: • Weltdimension, z. B. 2562 Tiles √ • Maximale Spielerzahl (Kapazität), z. B. 128 • Anzahl der Teams, zwischen 2 und 8 √ √ • Maximale Punkte pro Runde, z. B. 100 × • Runden pro Welt, z. B. 3 × • Friendly Fire-Anteil, z. B. 50% × Zur Laufzeit stehen folgende Funktionen zur Verfügung: √ KAPITEL 6. EIN DEMOSPIEL 48 Administration Über ein Webfrontend können Spieler in andere Teams eingeteilt, oder vom Server gekickt (entfernt) oder gebanned (dauerhaft ausgeschlossen) werden. √ Außerdem können Mitteilungen an die Spieler gesendet werden. Auto Balancing Bei Eintritt in die Welt wird dem neuen Spieler ein Team vorgeschlagen. √ Dieser Vorschlag richten sich nach der aktuellen Teambalance. Die Welt • Sämtliche Eigenschaften werden zufällig generiert √ • Verschiedene Themen, wie Mond, Erde und Fantasiewelt × • Verschiedene Landschaften, am Beispiel Erde: – Animiertes Wasser (Meere, Flüsse, Seen) – Küste √ – Grasland – Wald √ √ √ – Gebirge √ • Stufenloses Höhenprofil √ • Wege und Straßen × • Einfluss der Oberflächenbeschaffenheit auf den Spielfluss × • Persistente Veränderungen von Höhenprofil und Oberflächen × • Animierbare Objekte, wie Spieler, Bäume und Kisten Der Spieler • Name √ • Teamzugehörigkeit / Farbe • Punkte pro Runde × • Rang pro Welt × √ √ KAPITEL 6. EIN DEMOSPIEL • Position in der Welt (x, y, z) • Ausrichtung 49 √ √ • Geschwindigkeit × • Rolle, wie Angreifer und Verteidiger × • Ausrüstung, wie Waffen und Schutzschild × • Hitpoints √ Das Team • Farbe, ev. auch Wappen √ • Punkte pro Runde × • Gewonnene Runden pro Welt × • Homebase, Holocore, Heimatpunkt. Dieser ist nicht von anderen Teams √ einnehmbar Punktewertung Es gibt zwei Möglichkeiten, Punkte zu erlangen: 1. Desintegrieren eines Gegners Für den Spieler 1 Punkt, für das Team ebenfalls 1 Punkt × 2. Umfärben einer Flagge Für den Spieler 2 Punkte, für das Team sogar 2 Punkte pro beteiligtem Spieler × 6.2 Implementierung Wie in der Aufgabenstellung festgelegt, wurde das Demospiel in Zusammenarbeit mit Thomas Schuster entwickelt, auf dessen Diplomarbeit in diesem Zusammenhang verwiesen wird.[Schu04] Aufgrund der begrenzten Zeit, die für die Implementierung zur Verfügung stand, konnten nicht alle der angedachten Spielinhalte verwirklicht werden. Um zu zeigen, welche Zielsetzungen aus dem vorigen Abschnitt erreicht wurden und√ welche nicht, sind diese mit entsprechenden Symbolen gekennzeichnet. Ein steht für eine erfolgreiche Umsetzung, ein × für ein Fehlen innerhalb des Spiels. Für anspruchsvollere KAPITEL 6. EIN DEMOSPIEL 50 Graphiken, d. h. Texturen und Sprites, stand ebenfalls nicht genug Zeit zur Verfügung. Beispielsweise sollte die Spielfigur statt der Mensch-ärger-dich” nicht“-Variante flüssig animiert und in verschiedenen Rotationen gerendert sein. 6.3 Screenshots Die folgenden Screenshots wurden bei einer Auflösung von 1024x768 Pixeln und 32 bit Farbtiefe aufgenommen. Sie sollen den Funktionsumfang und die Qualität von Run To The Hills im Bereich der Graphik demonstrieren. KAPITEL 6. EIN DEMOSPIEL 51 Abbildung 6.1: Übergang von seichtem in tiefes Wasser. Der Strand besteht aus zwei unterschiedlichen Landtypen (trockener und feuchter Sand). Dazu kommt ansatzweise Gras als dritter Landtyp. Unter dem Wasser liegt in Küstennähe der feuchte Sand, weiter draußen ein dunkler Meeresgrund als insgesamt vierter Landtyp in diesem Bild KAPITEL 6. EIN DEMOSPIEL 52 Abbildung 6.2: Strand und seichtes Wasser. Die verwendeten Landtypen entsprechen denen aus Abbildung 6.1 KAPITEL 6. EIN DEMOSPIEL 53 Abbildung 6.3: Übergang von Strand nach Wald. Der Wald besteht aus statischen Sprites (inklusive Schatten per Alphakanal) und Gras als Landtyp Kapitel 7 Ergebnisse Die in dieser Arbeit vorgestellte Architektur einer Massive Multiplayer Online Game Engine erfüllt die gestellten Anforderungen. Dazu gehören unter anderem die geringen Antwortzeiten, die Verwaltung vieler, konkurierender Spieler und nicht zuletzt die einfache Integration in ein Demospiel. • Echtzeit: Durch die unmittelbare Übertragung der Kommandos von den Clients zum Server und umgekehrt kann das Echtzeitgefühl der Spieler gewährleistet werden. • Gleichzeitigkeit: Das Design von Mamuneen ermöglicht sehr vielen Spielern gleichzeitig in einer Welt zu interagieren. Leider konnten genaue Messungen der serverseitigen Belastbarkeit aus Zeitgründen nicht angefertigt werden. • Skalierbarkeit: Der Einsatz nicht-blockierender Ein- und Ausgabetechnologie bietet allerdings eine resourcenschonende und schnelle Basis für hunderte von Clientverbindungen. Durch das mehrschichtige Design der Serverkomponenten kann die Last gut aufgeteilt werden. • Klarheit und Abstraktheit: Es wurde beim Design und während der Implementierung die Verwendung von Design Patterns und deren Nomenklatur verfolgt. Zudem können die Softwaremodule durch die strikte Trennung ihrer Schnittstellenbeschreibungen (interfaces) problemlos ausgetauscht werden. • Referenzimplementierung: Im Rahmen dieser Arbeit wurde das Design prototypisch implementiert und erfolgreich im Demospiel zum Einsatz gebracht. 54 KAPITEL 7. ERGEBNISSE 55 • Erweiterbarkeit: Die Erweiterbarkeit demonstriert das in Rahmen dieser Diplomarbeit erstellte Spiel Run To The Hills: Das Demospiel kann die Leistungsfähigkeit beider Frameworks (Mithril [Schu04] und Mamuneen) eindrucksvoll unter Beweis stellen. Das Spiel läuft auf unterschiedlichster Hardware bis hin zu Laptops mit Graphikchips, deren 3D-Tauglichkeit bzw. Graphikleistung im Allgemeinen als eher zweifelhaft anzusehen ist. Dies kann zusätzlich als eine Erfüllung des Neben” ziels“, auch weniger leistungsstarke Graphikhardware zu unterstützen, gewertet werden. • Robustheit: Die Wahl des verbindungsorientierten und zuverlässigen TCP/IP Protokolls sichert in der prototypischen Implementierung die Übertragung der Kommandos. Ausfälle einzelner Knoten werden erkannt und behandelt. • Laufzeitstatistik: Es wird sowohl serverseitig als auch bei den Clients eine Reihe an Kenngrößen ermitteln. Anhand dieser Werte kann eine Anpassung des Systems zur Laufzeit vorgenommen werden. – Application: Gesamtzahl der bisher ausgeführten Kommandos – Application: Anzahl der in der letzen Sekunde ausgeführten Kommandos – Proxy/Client: Round Trip Time/Ping eines Kommandos – Proxy/Client: Anzahl der gesendeten/empfangen Kommandos • Kontrolle: Der integrierte Webserver verschafft den Administratoren Einblicke in das laufenden System und ermöglicht Veränderungen vorzunehmen. Der nächste Abschnitt beschreibt die Integration im Detail. KAPITEL 7. ERGEBNISSE 7.1 56 Serveradministration via Webbrowser Die folgenden Abbildungen zeigen das integrierte Webinterface. Die Implementierung ist in Abschnitt 5.1.2 beschrieben. Abbildung 7.1: Serveradministration via Webbrowser – Kein Spieler ist online. Es werden lediglich Basisinformation im Browserfenster angezeigt. Der Server wurde mit einer maximalen Kapazität von 8 gestartet. KAPITEL 7. ERGEBNISSE 57 Abbildung 7.2: Serveradministration via Webbrowser – Mittlerweile sind vier Benutzer eingeloggt, die in einer Liste dargestellt werden. Zusätzlich werden technischen Angaben wie Laufzeitnummer und entfernte Internetadresse pro Spieler aufgeführt. Die serverseitigen Aktionen für den Administrator sind freigeschaltet. Neben der Textsendefunktion say kann er außerdem auch beliebige Benutzer aus dem Spiel entfernen. Dazu dient ein Hyperlink namens [kick], der für jeden Benutzer generiert wird. KAPITEL 7. ERGEBNISSE 58 Abbildung 7.3: Serveradministration via Webbrowser – Es sind nur noch zwei Benutzer verbunden. Die beiden anderen wurden vom Administrator gekickt“. ” KAPITEL 7. ERGEBNISSE 7.2 59 NIO: Fehler in Sun JDK 1.4 für Windows Im Laufe der Implementierung des Transceivers kam es zu unvorhersehbaren Problemen und Fehlermeldungen. Dabei handelte es sich um einen sogenannte NullPointerException, die auf eine ungültige Objektreferenz hinweist: java.lang.NullPointerException at sun.nio.ch.WindowsSelectorImpl$SubSelector.processFDSet (WindowsSelectorImpl.java:309) at sun.nio.ch.WindowsSelectorImpl$SubSelector.processSelectedKeys (WindowsSelectorImpl.java:282) at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$2600 (WindowsSelectorImpl.java:245) at sun.nio.ch.WindowsSelectorImpl.updateSelectedKeys (WindowsSelectorImpl.java:427) at sun.nio.ch.WindowsSelectorImpl.doSelect (WindowsSelectorImpl.java:142) at sun.nio.ch.SelectorImpl.lockAndDoSelect (SelectorImpl.java:59) at sun.nio.ch.SelectorImpl.select (SelectorImpl.java:70) at sun.nio.ch.SelectorImpl.select (SelectorImpl.java:74) at de.ctsr.mallorn.server.socket.SocketReceiver.run (MallornSocketReceiver.java:120) at java.lang.Thread.run(Thread.java:534) Diese Probleme tauchten zuerst unregelmäßig auf und schienen die Stabilität des Systems nur manchmal zu beinträchtigen. Mit steigender Benutzeranzahl häuften sich jedoch die serverseitigen Fehlerfälle und machten eine genauere Investigation unumgänglich. Nach einer gründlichen Untersuchung der eigenen Quellcodezeilen konnte ein Fehler des Autors bei der Anwendung der NIO Klassen ausgeschlossen werden. Das Problem war somit innerhalb einer der NIO Klassen anzusiedeln. Verstärkt wurde dieser Verdacht durch den Fakt, dass die Fehler abhängig von der verwendeten Java Runtime Version und des zugrundeliegende Betriebssystem auftraten. Die betroffenen Systeme sind: • Sun JDK 1.4.2 03 (client) auf Windows 2000 (SP4) • Sun JDK 1.4.2 04 (client) auf Windows 2000 (SP4) • Sun JDK 1.4.2 04 (client) auf WindowsXP (SP1 + Hotfixes) KAPITEL 7. ERGEBNISSE 60 • Sun JDK 1.4.2 05 (client) auf Windows 2000 (SP4) • Sun JDK 1.4.2 05 (client) auf WindowsXP (SP1 + Hotfixes) Keine Probleme machten folgende Kombinationen: • IBM JDK 1.4.1 IBM build 20040301a auf Windows 2000 (SP4) • Sun JDK 1.4.2 04 (client) auf Linux (Kernel 2.6.6-rc2) • Sun JDK 1.4.2 06 (client) auf Windows 2000 (SP4) • Sun JDK 1.5.0 beta1 (client) auf Windows 2000 (SP4) • Sun JDK 1.5.0 beta2 (client) auf Windows 2000 (SP4) • Sun JDK 5 (client) auf Windows 2000 (SP4) Zur Eingrenzung der fehlerhaften Codestelle wurde eine Applikation programmiert, die aus einem Server und einem Client bestand. Die Clientinstanzen konnten mit dem Server verbunden werden und einwandfrei Kommandos austauschen. Sobald jedoch einer der Clients die Verbindung beendete, stellte der selektierende Serverthread mit der oben genannten Meldung seine Arbeit ein. Damit konnten keine Daten mehr übertragen werden. Beide offensichtlichen Lösungen der Situation, der Wechsel zu Linux als Serverplatform und der Einsatz des sich zu diesem Zeitpunkt noch in der Entwicklung befindlichen JDK 1.5, wurden verfolgt. Die Fehlerdatenbank von Sun (http://bugs.sun.com/bugdatabase/) listet seit Oktober 2004 unter anderem folgende kürzlich gelöste Probleme: 4892104: Null pointer exception while deregistering key from selector 4729342: Selector.select() throws CancelledKeyException KAPITEL 7. ERGEBNISSE 7.3 61 Fazit und Ausblick Das in dieser Diplomarbeit behandelte Gebiet der Onlinecomputerspiele für viele Spieler vereinigt interessante Spezialbereiche der Informatik samt ihrer Probleme und Herausforderungen. Aus der Kombination der jeweiligen Lösungen und Verfahren konnte ein System entwickelt werden, das einen robusten Rahmen für weitere Forschungsarbeiten dargestellt: Allgemeine Optimierungen An vielen Stellen der prototypischen Implementierung wurden die Voreinstellungen der Komponenten verwendet. Dazu zählen die verwendete Virtual Machine, Threadprioritäten, Socketoptionen, Puffer- und Queuegröße und andere laufzeitkritische Einstellungen. Interessenmanagement Aura, Fokus, Nimbus . . . Hierbei wird serverseitig eine Reduktion der Zielknoten (Clients) einer Nachricht vorgenommen. Es macht zum Beispiel keinen Sinn die Bewegungsaktionen zweier Spieler zu übermitteln, die sehr weit voneinander entfernt sind. Unter der Vorraussetzung, dass sich die Spieler möglichst gleichmäßig in der Welt verteilen, lässt sich durch ein geschicktes Interessenmanagement die Datenmenge, die Server an die Clients senden muss, stark verringern. Hochperformante Datenbanken Der Einsatz leistungsstarker und dynamisch skalierender Datenbanksysteme kann die Antwortzeit der Serverkomponenten reduzieren. Sofern genügend Arbeitsspeicher zur Verfügung steht, sollten vor allen Dingen die sogenannten in-memory databases einen extremen Geschwindigskeitszuwachs liefern. Literaturverzeichnis [Bliz] Blizzard. Blizzard Entertainment. 5 [Gamo] Gamona.de. Interview mit Chris Sigaty, 14.09.04, World of Warcraft. 5 [GHJV95] Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional. 1995. 24, 31 [Mets02] Steven John Metsker. Design Patterns Java Workbook. AddisonWesley. 2002. 31 [RT98] Newsgroup RT. Realtime Computing FAQ. Technischer Bericht Version 3.5, comp.realtime, July 1998. This posting provides an overview of newsgroup comp.realtime by summarizing the history, common past topics, and frequently asked questions. 7 [Schu04] Thomas Schuster. Entwicklung einer isometrischen Grafik-Engine in Java, 2004. Diplomarbeit. Universität Koblenz-Landau, Standort Koblenz. 49, 55 [SiZy99] Sandeep Singhal und Michael Zyda. Networked Virtual Environments – Design and Implementation. Addison-Wesley Professional. 1999. 11 [Steu03] Hartwig Steusloff. Verteilte Echtzeitsysteme und Eingebettete Systeme - Über Systeme und Technologien. In Peter Holleczek und Birgit Vogel-Heuser (Hrsg.), Verteilte Echtzeitsysteme. GI, Springer, 2003, S. 1–12. Fachtagung der GI-Fachgruppe 4.4.2, Echtzeitprogrammierung und PEARL (EP). 8 [Sun 04] Inc. Sun Microsystems. Sun Game Server Technology – An Executive Overview. Technischer Bericht, 2004. 16 62