eMail
 Suche
 Impressum

Subsections


5. Java-Umsetzung

In diesem Kapitel möchte ich erläutern, wie ich die vorgestellte Architektur in die Implementierung eines Frameworks umgesetzt habe. Aktuelle Dokumentation und Sourcen sind im Internet unter [16] zu finden. Im Menü findet sich ein Link zur Java-Dokumentation und zur HTTP-Oberfläche des MMPGP2P-Servers.


5.1 Entwicklungsumgebung


5.1.1 Programmiersprache

Ich habe mich für den Einsatz von JAVA aus mehreren Gründen entschieden.

Zum einen unterstützt JAVA eine Vielzahl von Hardware-Architekturen und Betriebssystemen. Einmal entwickelt lässt sich der Code meist problemlos auf allen von JAVA unterstützten Systemen einsetzen. Der Nachteil dieser Technik ist, dass ein Interpreter benötigt wird, der auf den Zielsystemen installiert sein muß. Bei der Entwicklung eines Spieles muß dies berücksichtigt werden.

Zum anderen unterstützt das JAVA-SDK eine große Anzahl von Techniken, die für dieses Projekt wichtig sind. Die einfache Handhabung von Threads und Sockets hat die Entwicklung enorm erleichtert.

JAVA wurde in der Version 1.4.2 eingesetzt.


5.1.2 Buildumgebung

Die Entwicklung des Sourcecodes und der übrigen Teile dieser Arbeit wurden unter Debian GNU/Linux 3.1-Sarge durchgeführt. Als Buildumgebung wurde ``make'' verwendet.


5.1.3 Testumgebung

Die immer wiederkehrenden Tests habe ich auf diversen mir zur Verfügung stehenden Systemen durchgeführt.

Als MMPGP2P-Server kam mein Rechner ``anaconda'' zum Zuge, ein AMD Athlon(tm) XP 2200+ mit DSL-Anbindung und fester IP-Adresse 213.146.116.174. Der Server läuft ebenfalls unter Debian GNU/Linux 3.1-Sarge mit Kernel 2.4.27. Ich habe den MMPGP2P-Server ständig auf diesem System laufen lassen und nur während der Weiterentwicklung der Klassen neu gestartet.

Für die Simulation von Clients habe ich zum einen meinen Rechner ``media'' eingesetzt, eine Multimedia-Station mit Intel(R) Pentium(R) 4 CPU 2.40GHz und 100MBit-Netzwerkanbindung an den Server. Auf diesem System lief Debian GNU/Linux 3.1-Sarge allerdings mit Kernel 2.6.11.6. Als weitere Clients kamen diverse Testumgebungen unter VMware Workstation 3.2.0 build-2230 zum Einsatz, darunter Windows XP und Windows 98.

Weiteres ist im Kapitel 7 über Performanzmessungen beschrieben.

5.1.4 Dokumentation

Diese Dokumentation wurde in LaTeX geschrieben und mit pdflatex kompiliert. Die Dokumentation der Java-Klassen wurde mit javadoc erzeugt.


5.2 Konfigurationdateien

Die verschiedenen Klassen und Threads erhalten diverse Parameter zur Steuerung ihres Verhaltens. Zur Sammlung und Übergabe dieser benötigten Parameter werden java.util.Properties verwendet. Diese haben den Vorteil, dass man sie leicht ausgeben oder speichern kann und dass sie ebenso einfach eingelesen werden können.

Da am MMPGP2P-System im Prinzip drei Parteien beteiligt sind, nämlich Client, Server und RegionController, werden die Parameter entsprechend benannt. Anstatt eines Parameters port gibt es somit rc.port und server.port. Ein Vorteil dieser Konvention ist, dass man eine einzige Properties-Konfigurationsdatei für alle drei Teile verwenden kann. Hier ist jedoch Vorsicht angebracht, weil unter Umständen der RegionController des Servers mit dem RegionController eines Clients auf demselben Rechner in Konflikt geraten kann.

5.2.1 Beispiel einer Properties-Datei

Nun folgt ein Beispiel einer solchen Properties-Datei. Die hier aufgelisteten Optionen sind nur ein Teil der verfügbaren Optionen, stellen jedoch die wichtigsten dar. Es existieren weitere Optionen, die sich zur Aufnahme in eine Konfigurationsdatei nicht eignen. Diese werden in Abschnitt beschrieben.

# ---- Hier beginnt der Teil für Server ---- #
# Hostname und Port des Servers konfigurieren. Diese Informationen
# stehen dann u.a. in den SessionTickets für die Clients.
server.hostname = www.mmpgp2p.org
server.port     = 18987
# backlog gibt die maximale Anzahl wartender Clients an
# Siehe Java Dokumentation zur Klasse java.net.ServerSocket
server.backlog  = 100

# Der nicename taucht in Protokollen oder SessionTickets auf und
# kann benutzt werden, um zusätzliche Individualität zu verleihen.
server.nicename = MMPGP2P Prototype Server

# Ping an Clients alle 60 Minuten
server.ping_interval = 60000

# Server Debugging-Ausgaben aktivieren
server.debug=1

# Die Ausgaben des Servers in folgende Datei umleiten
server.log_file = server.log


# Der Server kann regelmäßig seine Status protokollieren. Mit diesem
# Parameter gibt man die Zieldatei für die Protokolle an.
server.stats.log_file = server.stats

# append steurt, ob die Protokolle angehängt werden sollen, oder ob der 
# Server die Datei überschreiben soll. Standard ist überschreiben.
server.stats.append   = 0

# Intervall in ms, in dem Stats in die Datei geschrieben werden.
server.stats.inverval = 5000


# HTTP-Interface aktivieren und admin-Passwort setzen
server.http.active = 1
server.http.adminpassword = secret

# Auf der HTTP-Oberfläche wird ein RegionTree grafisch dargestellt. 
Mit diesem Parameter kann man den Zoom einstellen. Wert in 1/1000.. 
server.http.region_tree_zoom = 20

# ---------------------------------------------------- #
# ---- Hier beginnt der Teil für RegionController ---- #

# Hostname und Port, auf dem der RegionController nach eingehenden 
# Verbindungen lauscht. Für Clients-Systeme empfielt sich hier ein
# Port, der nicht durch eine Firewall geblockt wird.
rc.hostname = me.home.org
rc.port     = 18900

# Der nicename taucht in Protokollen auf und kann benutzt werden, um
# dem RC zusätzliche Individualität zu verleihen.
rc.nicename = Me RC Nr. 1

# RegionController Debugging-Ausgaben aktivieren
rc.debug=1

# Die Spielewelt aus folgender Datei laden
rc.game_world_file=/tmp/initial.world

# Start-Timeout des RCs in ms
rc.start_timeout = 30000

# Logfile für RegionController (Ausgabe in Datei anstatt System.out)
rc.log_file = /tmp/rc.log

# Grenzwerte für die Benutzung der Region
rc.maxclients = 15
rc.minclients =  1

# ------------------------------------------ #
# ---- Hier beginnt der Teil für Client ---- #
# Debugging auch für den Client aktivieren
client.debug = 1

# Noch mehr Debugging. Falls hier 1 eingestellt wird, dann werden
# die Befehle in der Sende- und Empfangs- Warteschlange ausgegeben.
client.log_queue_commands = 0

# Logging aller Kommandos, die vom Client ausgeführt werden
client.debug_execute_commands = 0

# Benutzername und Passwort, das der Client beim Authentifizieren benutzt
client.username = testusr
client.password = mysecret


# Die Grösse der zufällig erzeugten TestGameWorld und
# deren tilesize konfigurieren
world.totx=100000
world.toty=100000
world.tilesize=1000

Die Beispielimplementierung (siehe 6.2) unterstützt das Einlesen von solchen Konfigurationsdateien aus dem ``HOME''-Verzeichnis (für Unix-basierte Systeme) oder unter ``Dokumente und Einstellungen'' (für Windows-basierte Systeme) des aktuell angemeldeten Benutzers. Beim Starten von Client oder Server wird dann nach einer entsprechenden Datei client.rc oder server.rc im Ordner .mmpgp2p (Unix) oder mmpgp2p (Windows) unterhalb des Home-Verzeichnisses gesucht und geladen. Das Home-Verzeichnis wird durch die Java-Property ``user.home'' bestimmt.


5.2.2 Weitere Parameter

Nun folgen weitere Parameter, die man nicht in eine Konfigurationsdatei aufnehmen sollte. Sie sollten bei Bedarf durch die Methode setPropery() eingestellt werden. Zum Beispiel der Parameter client.register in einer Konfigurationsdatei würde dazu führen, dass bei jeder Verbindung ein neuer Account registriert wird. Dies würde zu einem Fehler führen.

# Dieser Parameter teilt dem ClientThread mit, dass er die Kombination 
# username:password beim Server registrieren soll. In diesem Fall wird ein 
# REGISTER-Kommando abgesendet.
# Empfängt der Server dieses REGISTER-Kommando, dann wird seine Methode
# registerNewClient() aufgerufen.
# Der Implementierung steht es natürlich frei, die Registrierung proprietär
# und nicht über die entsprechende Methode im ServerThread zu implementieren.
client.register = 1

# Falls man einen Client ohne RC starten will, dann kann man diesen Wert 
# auf 0 setzen. Clients sollten allerdings immer einen RC starten.
rc.start    = 1


5.3 Wichtige Klassem

Die Implementierung verwendet einige Klassen und Datenstrukturen, die hier kurz erläutert werden sollen. Grundsätzlich gibt die Javadoc ausreichend Informationen zur Benutzung.


5.3.1 Ruleset

Das Ruleset (Spielregeln) bietet Methoden an, durch die der Zustand der Spielewelt verändert werden kann. Dabei bietet das Ruleset einige interne Routinen, die vom System aufgerufen werden, und einige wenige abstrakte Methoden, die vom Endanwender implementiert werden sollen, um seine Spielregeln umzusetzen.

Für einen reibungslosen Betrieb des Systems sollten zunächst einige wichtige Punkte beachtet werden.

  1. Niemals System.currentTimeMillis() für das Setzen von Zeitstempeln benutzen, da sich die Uhrzeiten auf verschiedenen Clients deutlich unterscheiden können, unter Umständen im Bereich von Minuten. Hierfür wurde die Methode getTime() (siehe Abschnitt 5.5.3.2 geschaffen, die auf allen Systemen zu einem bestimmten Zeitpunkt diesselben Werte liefert.
  2. Niemals Math.random() für die Erzeugung von Zufallswerten benutzen. Alle RegionController in einem RCPool müssen bei gleichen Eingaben auch gleiche Ergebnisse erzielen, daher müssen auch die Zufallswerte bei allen RegionControllern gleich sein. Hierfür existiert eine Methode random() in der Klasse Ruleset.
  3. Für neue Objekte niemals eine eigens gewählte ID setzen. Die Klasse GameObject bietet einen Konstruktor, dem eine Referenz auf GameWorld übergeben wird. Dieser sorgt für das Setzen einer systemweit eindeutige ID. Dieser Konstruktor fügt das neue Objekt noch nicht der Welt hinzu.
  4. Avatare über die beiden Konstruktoren erzeugen und keinesfalls die ID modifizieren. Da ein Spieler mehrere Avatare besitzen kann, existieren für jeden Besitzer Avatar-IDs, die nur im Zusammenhang mit der ID des Spielers systemweit eindeutig sind (z.B. wenn Spieler 1 die Avatare mit ID 1 und 2 besitzen, kann Spieler 2 durchaus auch Avatare mit ID 1 und 2 besitzen). Die systemweit eindeutige ID eines Avatars wird aus der ID des Besitzers (owner) und der ID des Avatars für diesen Benutzer berechnet.

5.3.2 abstrakten Methoden

Zur Zeit müssen drei abstrakte Methoden implementiert werden, um die Funktionalität des Systems zu gewährleisten. Die Reihenfolge ist an dieser Stelle bewußt nach der erwarteten Komplexität der Methoden sortiert.

Alle Methoden müssen einen Array von GameObjects zurückliefern. In diesem Array sind alle von der Methode manipulierten Objekte enthalten. Das Ausliefern von Updates an die Clients ist abhängig davon, welche Objekte in diesen Array zurückgeliefert wurden.

  • GameObject[] targetReached(GameWorld w, GameObject o)
    Diese Methode wird von der internen Bewegungsroutine aufgerufen, sobald ein Objekt sein Ziel rereicht hat. Die Beispielimplementierung sucht an dieser Stelle ein neues Ziel innerhalb der Grenzen der Teilwelt.
  • GameObject[] applyCommand(GameWorld world, MmpgP2PCommand command)
    Sobald ein (User-) Kommando von einem Client empfangen wird, kommt diese Methode zur Ausführung. In der Regel werden durch diese Methode Befehle wie ``Gehe-Zu'' oder ``Feuere auf'' ausgeführt.
  • GameObject[] doTick(GameWorld w, GameObject o)
    Diese Methode stellt die eigentliche Herausforderung an den Entwickler. In jedem Tick wird diese Funktion mehrfach aufgerufen, für jedes Objekt einmal. Da es durchaus geschehen kann, dass durch die Ausführung der Regeln auf ein Objekt mehrere andere Objekte manipuliert werden, wird hier ebenfalls ein GameObject[] zurückgegeben.


5.3.3 GameWorld und GameObject

Ein GameObject ist die Basisklasse für alle Gegenstände und Objekte, mit denen ein Spieler interagieren können soll. GameObject hat viele Eigenschaften, von denen die meisten durch das Ruleset modifiziert werden. Objekte werden über eine ID verwaltet, die im gesamten System einmalig sein muß.

Die Struktur GameWorld verwalten Objekte des Typs GameObject. Sie stellt Methoden zur Verfügung, über die Objekte innerhalb der Welt hinzugefügt, gelöscht, abgerufen oder manipuliert werden können. GameWorld enthält einen Zähler für die Vergabe von freien IDs. Beim Splitting einer Teilwelt wird dabei auch das Interval freier IDs auf die neu entstehenden Welten aufgeteilt. Objekte sollten daher stets über die von GameWorld zur Verfügung gestellten Operationen erzeugt werden, damit ihre ID nicht doppelt verwendet wird.


5.3.4 GameWorldInfo und RegionInfo

Diese beiden Strukturen dienen zur Speicherung der wichtigsten Information über die Welt oder einer Region. Es werden nur statische Informationen wie Höhe und Breite gespeichert.


5.3.5 RCPool

Der RCPool ist eine einfache Klasse, die mehrere RegionController und die zugehörige Region verwaltet. Es ist einfach, der Klasse weitere RegionController hinzuzufügen oder RegionController zu entfernen (über die Methoden addRCInfo() oder removeRCInfo). Intern werden die Daten zu den RegionControllern als Vektor von RegionControllerInfo-Klassen verwaltet.

Eine Methode sendToEach() erlaubt das Senden eines MmpgP2PKommandos an jeden der beinhalteten RegionController. Da die Klasse serialisierbar ist, kann man die Übertragung der Informationen eines RCPools einfach durch das Übertragen der Klasse RCPool über einen ObjectOutputStream erreichen.


5.3.6 RegionTree

Diese Klasse verwaltet die Aufteilung der Welt in kleinere Regionen anhand eines binärer Suchbaumes (siehe Abbildung 4.3 in Abschnitt 4.1.2.1). Die Knoten innerhalb des Baumes stellen jeweils einen Split-Vorgang dar, der entweder entlang von X- oder entlang von Y-Koordinaten stattgefunden hat. Die beiden Kinder eines Knoten halten die linke und rechte Hälfte bzw. die obere und untere Hälfte der geteilten Region. Einer der beiden Parameter X oder Y ist immer -1, der andere gibt die Position des Splittings an.

Ein Blatt enthält Informationen über die Region selbst und die zugehörigen RegionController in Form einer Referenz auf einen RCPool (siehe 5.3.5). Nach einer Suche wird immer die Information eines Blattes zurückgegeben. Knoten speichern keine Referenzen.

Die Darstellung als binärer Suchbaum ermöglicht das effiziente Auffinden von Regionen zu einer (X;Y)-Koordinate. Beim Splitting einer Region wird das zugehörige Blatt zu einem Knoten mit zwei Blättern, die nun die zwei neuen Regionen darstellen.

5.3.6.1 Operationen

Der RegionTree stellt die im Abschnitt 4.3.6 vorgestellten Operationen über die Methoden mergeRegion() und splitNode() zur Verfügung. Detailierte Informationen sind der Javadoc zu entnehmen.


5.4 Programmablauf und Datenfluß

Im Folgenden werden einige wichtige Programmabläufe beschrieben und zumeist grafisch durch Sequenzdiagramme skizziert.


5.4.1 Login

Ein Client loggt sich in das System ein. Dies geschieht, sobald die von ClientThread abgeleitete Klasse instanziert und über die start()-Methode ausgeführt wird.

Figure 5.1: Login des Clients
Image login

Das Login durchläuft einige Stationen, die hier der Reihe nach aufgelistet werden und die in Abbildung 5.1 grafisch dargestellt sind.

  1. Der Client öffnet eine Verbindung zum Server
  2. Der Client authentifiziert sich mit Benutzername und Kennwort
  3. Der Server überprüft Benutzername und Kennwort
  4. Der Server lädt die Avatare für den Client und erzeugt ein SessionTicket
  5. Der Server öffnet Verbindungen zu den RCs des für den Client zuständigen RC-Pools
  6. Der Server lädt die zum Client gehörigen Avatare und überträgt sie an den RC-Pool
  7. Der Server trennt die Verbindungen zum RC-Pool und speichert, welchem RC-Pool er den Client zugewiesen hat
  8. Der Server antwortet dem Client mit einem SessionTicket, das u.A. die zu kontaktierenden RegionController enthält
  9. Der Client trennt die Verbindung zum Server
  10. Der Client startet seine Befehls-Warteschlangen über die initCommandQueues-Methode. Diese Warteschlange hält Verbindung zum RC-Pool und dient als Schnittstelle zum Übertragen und Empfangen von MmpgP2PCommands.

Der Client ist nun mit seiner Region ``verbunden'' und kann mit dem Spiel beginnen.


5.4.2 Spielstart

Nachdem der Client sich erfolgreich mit dem RC-Pool seiner Region verbunden hat, kann er jederzeit das Spiel starten. Erst nach dem Start-Befehl erhält der Client Informationen über Objekte und andere Spieler.

Figure 5.2: Client gibt Befehl zum Spielstart

Image play

In der Grafik 5.2 sind folgende Schritte skizziert:

  1. Der Client erzeugt ein MmpgP2PCommand vom Typ START_GAME. Dieser wird an die RCs übertragen.
  2. Der RC-Pool reagiert mit der Übertragung der Spielregeln (Ruleset)
  3. Der RC-Pool überträgt die Spieler-Avatare
  4. Der RC-Pool überträgt die für den Spieler sichtbare Welt
  5. Der RC-Pool bestätigt dem Client, dass er nun regulär spielen kann

Nach der erfolgreichen Übertragung der Spielregeln, Avatare und der Spielewelt beginnt das eigentliche ``spielen''. Im folgenden Abschnitt wird der Ablauf dieses schleifenartiken Zykluses dargestellt.


5.4.3 Hauptschleife: Spielzüge und Spielwelt-Updates

Kurz zusammengefasst besteht die Hauptschleife während eines Spiels aus ununterbrochenem Senden von Zügen und Empfangen von Updates auf die Spielewelt. Updates werden regelmässig auf die lokale Kopie der Spielewelt eines Clients angewendet und eventuell auch dann empfangen, wenn der Client keine Züge durchführt. 5.1

Wird ein Objekt in der Welt des RegionControllers durch einen Spielzug geändert, dann wird es im nächsten Zyklus an alle Clients übertragen, die Zugriff auf das Objekt haben bzw. das Objekt besitzen, oder wenn sie das Objekt in Sichtweite (siehe 4.1.1.7) haben.


5.4.4 Wechseln der Region

Durch die Aufteilung der Welt in Regionen wird es zwangsweise dazu kommen, dass Avatare die Grenzen einer Region übertreten. In diesem Fall wechseln sie nicht nur die Region innerhalb der Welt, sondern sie wechseln auch die RegionController, die für sie zuständig sind (waren).

  1. Der Avatar wird über die Grenze der Region bewegt. Dies geschieht noch auf dem RegionController durch die Anwendung einer Bewegungsregel.
  2. Der RegionController bemerkt, dass der Avatar seine aktuelle Region verlassen hat und initiiert den Regionswechsel für ihn.
  3. Nun baut der RegionController eine Verbindung zum Server auf (oder benutzt eine noch offene Verbindung). Er übermittelt CHANGEREGION Kommando inklusive Avatar an den Server.
  4. Nun pausiert er den Client, bis er vom Server eine Reaktion erhält.
  5. Der Server sucht die zuständigen RegionController für die neue Region des Avatars. Er baut eine Verbindung zu diesen auf und übermittelt ihnen den Avatar des Spielers und das zugehörige Session-Ticket des Clients.
  6. Die neuen RegionController erwarten nun die Verbindung des Clients.
  7. Nun übermittelt er den alten RegionContollern ein CHANGEREGION Kommando, als Parameter werden die Session-ID des Clients und der RCPool für die neue Region übermittelt.
  8. Die alten RegionController weisen den Client mit einem NEWREGION Kommando an, dass er sich mit anderen RegionControllern verbinden muß.
  9. Die Command-Queue wird geschlossen. Der Client schließt alle offenen Verdingungen.
  10. Der Client startet eine neue Befehls-Warteschlangen über die initCommandQueues()-Methode. Die Warteschlange öffnet Verbindungen zu den neuen RegionControllern.
  11. Nun ruft der Client abschließend die startGame()-Methode auf und nimmt den regulären Spielbetrieb wieder auf
  12. Der Client spielt weiter


5.4.5 Logout

Beim regulären Verlassen des Spiels überträgt der Client einen Befehl zum Logout (LOGOUT-Kommando).
Figure 5.3: Logout des Clients

Image logout

In der Grafik 5.3 sind folgende Schritte skizziert:

  1. Der Client erzeugt ein MmpgP2PCommand vom Typ LOGOUT. Dieser wird an die RCs übertragen.
  2. Die RegionController des RC-Pools deaktivieren den Client sofort. Er erhält keine Updates mehr und seine Spielzüge werden sofort verworfen.
  3. Die RCs öffnen eine Verbindung zum Server
  4. Die RCs übertragen die Avatare des Clients zum Server und dieser speichert sie persistent
  5. Die RCs teilen dem Server mit, dass sich der Client ausloggt
  6. Der Server deaktiviert den Client und löscht die Zuordnung zu seinem RC-Pool
  7. Der Server bestätigt den Vorgang
  8. Die RCs übertragen ein MmpgP2PCommand vom Typ CLOSE an den Client
  9. Die Command-Queue wird geschlossen. Der Client schließt alle offenen Verdingungen.


5.4.6 Splitting einer Region

Das Splitting einer Region findet primär auf den RegionControllern der Region statt. Die Clients sind nur insofern beteiligt, als dass sie eventuell den RC-Pool wechseln müssen, sobald das Splitting beendet wurde und sich ihr Standort nun in der Region eines anderen RC-Pools befindet. Der Server ist verantwortlich für die Zuweisung von freien RCs für den neuen Pool (hier RCPoolB).

Im Abschnitt 4.3.4 wurden diese Punkte bereits angedeutet.

Figure 5.4: Splitting einer Region

Image split

In der Grafik 5.4 sind folgende Schritte skizziert:

  1. Der RC-Pool signalisiert dem Server, dass er seine Region splitten möchte. Gleichzeitig wird ein Vorschlag übermittelt, wo die Grenze des Splittings verlaufen sollte. 5.2
  2. Der Server sucht freie RegionController (siehe 4.2.3) für den neuen RC-Pool. Die Anzahl an RCs im Pool wird durch Parameter gesteuert.
  3. An jeden dieser RCs schickt er einen MmpgP2PCommand vom Typ PREPARE mit entsprechenden Parametern, um die Übermittlung der neuen Region vorzubereiten.
  4. Nun übermittelt der Server den eigentlichen SPLIT Befehl an die RCs. Der neu erzeugte RC-Pool B wird ebenfalls als Parameter übertragen, damit die RCs des alten RC-Pool A wissen, wohin sie die neue Region übertragen müssen. 5.3
  5. Das Spiel wird in dieser Region für die Zeit des Splittings pausiert.
  6. Die RCs des ``alten'' RC-Pools A führen das Splitting der Region durch.
  7. RC-Pool A kontaktiert RC-Pool B und übermittelt die neue Region in Form einer GameWorld Klasse. Die GameWorld Klasse enthält auch alle Objekte innerhalb dieses Teiles der Region.
  8. Nun werden noch die Avatare an RC-Pool B übertragen.
  9. Das Splitting ist durchgeführt und kann dem Server bestätigt werden.
  10. Der Server aktualisiert seinen RegionTree (siehe 4.1.2.1) mit den neuen Daten.
  11. Die Clients, deren Avatare sich innerhalb der neu entstandenen Region befinden, bekommen einen MmpgP2PCommand vom Typ NEWREGION und verbinden sich mit dem neuen RC-Pool B.
  12. Der RC-Pool B informiert den Server darüber, dass sich ein Client nun innerhalb seiner Region befindet (UPDATE Client). Der Server bestätigt.
  13. Die Clients erhalten den PLAY Befehl und können nun weiterspielen.

Es sei an dieser Stelle angemerkt, dass der Server vor dem Befehl zum Splitting noch diverse Prüfungen auf Plausibilität durchführt oder das Splitting ablehnt, falls es unsinnig erscheint. Diese Details würden jedoch den Rahmen sprengen.

5.4.7 Serververbindungen

Abbildung 5.5 soll verdeutlichen, wie der Server eine eingehende Verbindung verarbeitet. Der Server ist in der Lage, den Typus einer Verbindung zu erkennen und ein dementsprechendes Interface bereitzustellen.

Figure 5.5: Starten des Servers und lauschen auf Netzwerkverbindungen

Image split

Wird eine neue Verbindung zum Server aufgebaut, dann nimmt der ConnectionListener des Servers diese Verbindung an und startet einen ConnectionWorkerThread. Dieser ist ab sofort für die weitere Verarbeitung ein und ausgehender Daten verantwortlich.

Der erste Befehl während einer neuen Verbindung ist ein String, der den Typ der gewünschten Verbindung spezifiziert. Erkennt der Server z.B. eine HTTP-Verbindung, dann wird ein entsprechendes Interface gestartet. Bei eingehenden Client- oder RC-Verbindungen wird ebenfalls ein entsprechendes Interface gestartet.

Die Verbindungen bleiben so lange offen, bis ein konfigurierbarer Timeout auftritt, oder bis das System die Verbindung manuell trennt (z.B. direkt nach der Verarbeitung eines HTTP-Requests).

5.5 Beschreibung wichtiger Klassen


5.5.1 MmpgP2PCommand

Diese Klasse represäntiert ein Kommando und stellt Methoden zur Verarbeitung und Übertragung bereit. Ein Kommando besteht aus einer ID und einem Array von Java-Objekten (oder Argument null). Die Argumente können beliebig verschachtelt werden, solange die verwendeten Objekte das Interface java.io.Serializable implementieren.

Ein Kommando kann man folgendermaßen erzeugen:

int         id = 1000000;
Object [] args = { "Parameter 1", "Parameter 2", "Parameter 3" };
MmpgP2PCommand command = new MmpgP2PCommand (id, args);

Unter den Beispielen befindet sich ein Programm namens SendCommand (siehe 6.1.2), das dieses Prinzip verdeutlichen sollte.

Das Kommando mit der ID 0 ist ein sogenanntes No-Operation (NOP)-Kommando. Dieses wird nicht verarbeitet und beeinflußt somit das System nicht. In diesem Fall ist die Argument-Liste null. Ein NOP-Kommando kann durch command = new MmpgP2PCommand() erzeugt werden und dient dazu, dem Gegenüber zu vermitteln, dass trotz Fehlen von übertragenen Kommandos die Verbindung noch besteht.

Welche Kommandos das Spiel verwendet und interpretiert ist Sache der Implementierung. Die vom MMPGP2P-System bereitgestellten Kommandos (ID $<$ 1024) dürfen nicht von der Implementierung verwendet oder erzeugt werden.

Benutzerdefinierte Kommandos (ID $>=$ 1024) werden durch ein Ruleset verarbeitet, das durch den Endanwender implementiert werden muß. Bei der Definition von benutzerdefinierten Kommandos sind keine Einschränkungen vorhanden.

5.5.2 SessionTicket

Ein SessionTicket erhält der Client vom Server nach erfolgreicher Authentifizierung im Netzwerk. Der Server kümmert sich auch um die Übermittlung des SessionTickets an die RegionController, die für den Besitzer des SessionTickets zuständig sind.

Das SessionTicket enthält unter anderem eine Sitzungs-ID (sessionID), die im gesamten Netzwerk eindeutig ist und den zugehörigen Client eindeutig identifiziert, und einen Sitzungsschlüssel. Die Verwendung des Sitzungsschlüssels ist optional und kann von der Implementierung für kryptografische Funktionen genutzt werden, um beispielsweise die Gültigkeit eines SessionTickets zu beweisen. Das Framework benutzt diesen Sitzungsschlüssel nicht.

Weiterhin enthält ein SessionTicket eine Liste von RegionControllern in Form einer RCPool-Klasse (siehe 5.3.5). Der Client muss die RegionController dieses RCPools kontaktieren, um am Spielgeschehen teilzunehmen. Die Gültigkeit eines SessionTickets kann zeitlich beschränkt werden, hierfür sind ebenfalls Methoden vorhanden.

5.5.3 MmpgP2PServiceThread

Die abstrakte Klasse MmpgP2PServiceThread dient als Basisklasse für verschiedene Threads. Häufig benötigte Methoden sind hier implementiert.

Die implementierenden Unterklassen rufen in ihrem eigenen Konstruktor den Konstruktor MmpgP2PServiceThread(Properties p) auf, damit alle benötigten Einstellungen vorhanden sind, insbesondere Daten zur Verbindung mit dem Server des Systems.

5.5.3.1 Abstrakte Methoden

Einige wenige Methoden müssen von einer abgeleiteten Klasse implementiert werden. Genaue Dokumentation zu der Funktionsweise der jeweiligen Methode befinden sich in der Javadoc.

  • log()
    Um das Ausgeben von Informationen oder das Logging von Fehlern zu erleichtern enthält die Klasse abstrakte log()-Methoden. Ein MmpgP2PServiceThread muß diese Methoden implementieren und kann damit steuern, wo Fehlermeldungen landen. Die Beispiel-Implementierung des ClientThread (ClientImplementation) z.B. gibt die log()-Ausgaben in einem grafischen Fenster aus.
  • incomingConnection()
    Falls eine Netzwerkverbindung zum MmpgP2PServiceThread aufgebaut wird, dann kommt diese Methode zum Einsatz. In der Regel wird diese Methode von einem ConnectionListenerThread einmal aufgerufen, sobald eine Verbindung eingeht und bevor die Verbindung von einem ConnectionWorkerThread gehalten wird.
  • incomingData()
    Jeder MmpgP2PServiceThread muß eingehende Daten verarbeiten können. Dazu dient diese abstrakte Methode, die als Argument einen ConnectionWorkerThread erhält, über den die eingehenden Daten abgerufen werden können. Diese Methode wird in der Regel von einem ConnectionWorkerThread aufgerufen, sobald dieser Daten auf seinem Socket empfängt.
  • closedConnection()
    Falls ein noch offener ConnectionWorkerThread beendet wird und seine Verbindung schließt, dann wird diese Methode aufgerufen. Dem implementierenden MmpgP2PServiceThread bleiben dann noch Möglichkeiten, auf die geschlossene Verbindung zu reagieren.

5.5.3.2 Sonstige wichtige Methoden

Die Klasse bietet einige Methoden zur einfacheren Verwendung des Systems. Einige wichtige sollen hier genannt werden. Dokumentation zu weiteren Funktionen finden sie in der Javadoc.

  • getTime()
    Diese Methode ist eine der wichtigsten Funktionen des Systems. Beim Aufbau einer Verbindung mit dem Server wird die lokale Zeit mit der globalen Zeit des MMPG-Netzwerkes abgeglichen und Abweichungen korrigiert. So wird sichergestellt, dass alle beteiligten Peers beim Aufruf der Methode dieselbe Zeit zurückgeliefert bekommen (mit tolerierbaren Abweichungen im Bereich von Millisekunden). Diese Methode löst das in Abschnitt angeschnittene Problem der zeitlichen Asynchronität der beteiligten Peers.
  • isStarting() und isRunning()
    Mit diesen Methoden kann man den Status des MmpgP2PServiceThread erfragen.
  • stopThread()
    Diese Methode sorgt dafür, dass der MmpgP2PServiceThread sachgemäß beendet wird.
  • waitFor(long timeout)
    Wird diese Methode aufgerufen, dann hält der aktuelle Thread so lange an, bis entweder der Timeout (in ms) erreicht wird oder bis der MmpgP2PServiceThread seinen Status auf running gewechselt hat. Diese Methode ist nützlich, um auf den vollständigen Start eines benötigten Threads zu warten.
  • getConnectionWorker() und getServerConnectionWorker()
    Diese beiden Methoden dienen dazu, eine reguläre Verbindung zu einem anderen MmpgP2PServiceThread aufzubauen. Erst wenn die Verbindung sicher steht, kehrt die Methode zurück.


5.5.4 ConnectionListenerThread

Dieser Thread kann von einem MmpgP2PServiceThread gestartet werden und tut dann nichts anderes, als auf eingehende Verbindungen zu lauschen. Beim Erzeugen eines solchen ConnectionListenerThread wird immer eine Referenz auf den erzeugenden MmpgP2PServiceThread sowie ein Port, auf dem gelauscht werden soll, übergeben. Diese Referenz nenne ich Parent (Vater-Prozess).

Wird eine eingehende Verbindung verarbeitet, dann wird die incomingConnection()-Methode des erzeugenden MmpgP2PServiceThread (Parent) aufgerufen. Anschließend wird die Verbindung durch einen neu erzeugten ConnectionWorkerThread (siehe 5.5.5) gehalten. Danach kehrt der ConnectionListenerThread zurück zum Lauschen auf seinen Port.

Diese Thread-Klasse wird beim Server und RegionController verwendet, um viele eingehende Verbindungen anzunehmen und offen zu halten, ohne den eigentlichen Verarbeitungsprozess zu blockieren. 5.4


5.5.5 ConnectionWorkerThread

Ein ConnectionWorkerThread hält die Verbindung zu einem verbundenen Client offen. Sobald er anliegende Daten erkennt, wird die incomingData()-Methode des Parents aufgerufen. Dieser ist dann für die Verarbeitung der Daten verantwortlich.

Nach einer gewissen Zeit von Inaktivität wird die Verbindung zum Client geschlossen. Es ist also wichtig, dass Clients regelmässig Daten liefern, um die Verbindung aufrecht zu erhalten. Zu diesem Zweck gibt es sogenannte NOP-Kommandos (siehe 5.5.1), die keine Wirkung auf das System oder die Spielewelt haben.


5.5.6 RegionControllerThread

Ein RegionControllerThread ist ein Prozess auf einem beliebigen beteiligten System. Jeder Client muss mindestens einen RCThread starten und somit einen Teil seiner Rechenleistung an das Gesamtsystem abtreten. Da man den Clients jedoch nicht trauen kann, werden Regionen nur an Gruppen von RCs deligiert (RC-Pools), die sich gegenseitig kontrollieren (siehe 4.3.3).

Da die Klasse RegionControllerThread unabhängig von der Klasse ClientThread implementiert wird, ist es möglich, mehrere RegionControllerThreads auf ein und demselben System zu starten. Der Betreiber kann Computer bereitstellt, die RegionControllerThreads (aber keine ClientThreads) starten (vgl. Seti@Home [*]), und so Flachenhälsen vorzubeugen (siehe 4.2.4).

5.5.6.1 Funktionsweise

Als Voraussetzung wird angenommen, dass das komplette System zu jeder Zeit von einem eindeutig definierten Zustand in einen weiteren eindeutig definierten Zustand übergeht. Bei gleichen Eingaben und gleichen Zuständen muss also jeder RegionController diesselben Ausgaben produzieren können. Zufallswerte müssen daher ``global'' verfügbar sein. Dafür existiert eine Funktion random() im Ruleset.

Beim Initialisieren eines neuen Spieles übernimmt der initiale RegionControllerThread des Servers die ersten Instanzen von RCThreads. Folgen dann mit der Zeit genügend Clients mit erreichbaren RegionControllern, werden Bereiche der Welt an diese deligiert. Die initialen Instanzen auf dem Server sind ``trusted'', d.h. sie benötigen keine kontrollierenden Instanzen.

Zur Initialisierung bekommt der neue Regioncontroller die benötigten Daten der Spielewelt übermittelt. Sobald der Transfer abgeschlossen ist, müssen die Clients, welche am übertragenen Teil der Spielewelt Interesse bekunden, den/die neunen Regioncontroller abonnieren.

5.5.6.2 Interner Aufbau

Der RegionControllerThread verwaltet die Objekte, welche seinem Teil der Spielewelt (seiner Region) zugewiesen sind. Er kann nur Objekte manipulieren, welche sich innerhalb der Grenzen seines Teiles befinden. Die Manipulation wird auf der Klasse GameWorld (siehe Abschnitt 5.3.3) ausgeführt.

Jeder RegionControllerThread sollte grundlegende Informationen über die benachbarten Regionen besitzen. Da die Spielewelt nicht homogen aufgeteilt ist, weiß man jedoch nicht im Voraus, zu welcher Region eine Kachel ausserhalb der Region gehört.

5.5.6.3 Hauptschleife und Ticks

Mit Aufruf der start()-Methode des RegionControllerThreads wird die Hauptschleife gestartet. Diese Schleife führt so lange keinerlei Aktionen durch, bis der RegionControllerThreads Spielregeln (in Form eines Rulesets, siehe Abschnitt 5.3.1) und eine Spielewelt (in Form einer GameWorld-Klasse, siehe Abschnitt 5.3.3) empfangen hat.

Hat er beides erhalten, dann führt er sogenannte Ticks aus . Ein Tick führt periodisch Aktionen durch. Wie lange das Intervall zwischen zwei Ticks ist, kann die Implementierung durch Setzen eines Wertes im Ruleset bestimmen. Standardmäßig wird alle 200ms ein Tick ausgeführt. Was während eines Ticks geschieht wird ebenfalls im Ruleset definiert, indem der Endanwender diverse abstrakte Methoden implementiert (siehe Abschnitt 5.3.1).

Der RegionControllerThread kann durchaus seine Hauptschleife ausführen, ohne dass ein Client verbunden wäre. Dies kann vorkommen, falls der letzte Client die Region verlassen hat oder falls der RegionControllerThread ``initial'' ist, das heißt er wurde vom ServerThread gestartet und wartet nun auf die ersten Clients. Die Kommunikation mit den Clients wird im folgenden Abschnitt behandelt.

5.5.6.4 Kommunikation mit Clients

Wie bereits beschrieben verbindet sich ein Client mit seinem RegionController, sobald er vom Server ein gültiges SessionTicket erhalten hat. Über seinen RCQueueWorkerThread wird die Verbindung aufgebaut. Der Ablauf eines Logins wird in Abbildung 5.1 als Sequenzdiagramm dargestellt.

Sobald der erste Client verbunden ist, wird der RegionControllerThread in jedem Tick die Änderungen an den Objekten der Welt übertragen. Er führt folgende Schritte aus:

  1. tickWorld() auf Ruleset ausführen, Spielewelt updaten
  2. Änderungen der Objekte und Avatare in Sichtweite übertragen
  3. Falls ein Objekt die Welt verlässt, dieses an den zuständigen RC übertragen
  4. Warten, bis der nächste Tick fällig ist

Falls der RegionControllerThread durch Überlastung es regelmäßig nicht schafft, alle diese Aktionen innerhalb eines Ticks auszuführen, dann sollte er gesplittet werden. Bei meinen durchgeführten Tests war der schwache Upstream meines DSL-Zugangs des Öfteren dafür verantwortlich, dass ein Tick nicht in der vorgegeben Zeit abgeschlossen wurde.


5.5.6.5 GZIP-Komprimierung

Während der Entwicklung des Frameworks wurden immer wieder Tests mit menschlichen Spielern durchgeführt. Da während dieser Zeit meist nur eine DSL-Anbindung zum Internet verfügbar war, wurde oft die maximale Bandbreite des Upstreams erreicht. Um kurzfristig eine Verbesserung der Übertragungsleistung zu erreichen, wurde die Komprimierung einiger Datenpakete mit GZIP implementiert. Der Traffic wurde dadurch auf weniger als die Hälfte reduziert.

Bei einer Messung wurden bei vier verbundenen Clients durchschnitlich ca. 50KB/S übertragen, nach der Einschaltung der GZIP-Komprimierung waren es noch 24KB/S.

5.5.7 ClientThread

Ein ClientThread ist der spielerseitige Teil des Systems. Zum Teilnehmen am System kontaktiert der Client zunächst den Server und dann die RegionController seiner Region. Eine grafische Oberfläche enthält diese Implementierung nicht.

5.5.7.1 Implementierungen

Da die Klasse abstrakt definiert ist, müssen nur einige wenige Methoden implementiert werden. Bei der Entwicklung wurde wert darauf gelegt, dass die Implementierung möglichst wenig Arbeit selbst erledigen muß. Lediglich das Anzeigen des Zustandes und das Versenden von benutzerdefinierten Kommandos muß eine Implementierung bewerkstelligen. Und natürlich muß die Klasse ClientThread bzw. ihre implementierende Klasse instanziiert und gestartet werden (siehe Abschnitt 6.6).

5.5.7.2 Ablauf eines Logins

Die Methode login() erledigt sämtliche Kommunikation für eine saubere Authentifizierung.

Zunächst baut der Client eine Verbindung zum Server auf. Er übermittelt dann Benutzernamen und Kernnwort und erhält bei Erfolg vom Server ein SessionTicket, mit dem er die Regioncontrollern abonnieren kann. Der Server überprüft, ob der Spieler einen gültigen Account besitzt und meldet entsprechende Objekte an die Regioncontroller. Die Regioncontroller werden somit informiert, dass sich dieser Client abonnieren will.

Hat der Server die Regioncontroller entsprechend informiert bzw. hat er neue RegionController zugewiesen, dann erzeugt er das Ticket und übermittelt es an den Client. Danach kann der Client die Regioncontrollern abonnieren. Die Verbindung zum Server wird wieder getrennt, um Ressourcen zu sparen. Alle weitere Kommunikation des Clients mit dem System sollte nun über die RegionController laufen. Von diesen Erhält das System regelmäßig Updates der Spielewelt sowie sonstige System-Kommandos. Im Abschnitt 5.5.8 werden die Details dieser Kommunikation erläutert.


5.5.7.3 Senden von benutzerdefinierten Kommandos

Über die Methode addCommand() werden Befehle in eine Warteschlange gegeben, die dann vom System an die RegionController übermittelt werden. Die Implementierung sollte keine System-Kommandos senden. Für System-Funktionen bietet ClientThread entsprechende Methoden an. Wie die Übertragung der Kommandos genau funktioniert, wird im nachfolgenden Abschnitt 5.5.8 erläutert.


5.5.8 RCQueueWorkerThread

Der Client muß die im SessionTicket genannten RegionController abonnieren. Um die Kommunikation mit diesen RegionControllern zu vereinfachen und zu abstrahieren, wurde eine weitere Zwischenschicht in Form einer Klasse geschaffen: der RCQueueWorkerThread.

Der Client kommuniziert mit dem MMPGP2P-System auschließlich über Methoden, die der RCQueueWorkerThread zur Verfügung stellt. Soll durch die Methode ClientThread.addCommand() (siehe 5.5.7.3) ein Kommando übertragen werden, dann wird dieses Kommando an den RCQueueWorkerThread weitergeleitet. Er kümmert sich dann um den Versand an alle RegionController.

Der ClientThread überprüft auf der anderen Seite regelmäßig, ob der RCQueueWorkerThread neue Befehle oder Updates empfangen hat. Die neuen Befehle können über die Methode getNextCommand() der Reihe nach abgefragt werden.

Dieses abstahierende Vorgehen erleichtert den Austausch der P2P-Technik, die zum Versenden und Empfangen von Kommandos oder Updates verwendet wird. In der aktuellen Version öffnet der RCQueueWorkerThread Netzwerkverbindungen zu jedem RegionController und versendet Kommandos regelmässig an jeden einzelnen. Beim Empfangen von Kommandos werden diese einfach in die Empfangswarteschlange gelegt und können durch die Methode getNextCommand() vom ClientThread abgerufen werden.

5.5.9 ServerThread

Der ServerThread nimmt eingehende Verbindungen entgegen und überprüft diese. Bei gültigem Login weist er dem Client eine ID und ein SessionTicket zu. Dieser Ablauf wurde bereits in Abbildung 5.5 skizziert.

Die Klasse ServerThread ist abstrakt. Der Entwickler muß also eine kleine Anzahl von Funktionen implementieren, damit die Kommunikation reibungslos verlaufen kann.

Der Haupt-Thread des Servers startet einen Hintergrundthread, der nach neuen Verbindungen lauscht. Wird eine neue Verbindung aufgebaut, dann akzeptiert er diese und reicht sie an den Hauptthread zurück. Danach wartet er auf die nächste Verbindung.


5.5.9.1 Protokollierung von Server-Statistiken

Der Server kann einige Variablen in regelmäßigen Abständen protokollieren. Dazu muß lediglich die Property server.stats.inverval auf einen Wert größer 0 gesetzt werden. Dieser Wert gibt an, in welchen Abständen (Wert in Millisekunden) der Server einen Protokoll-Eintrag schreibt. Für regulären Betrieb sind 5 Sekunden ein ausreichender Wert, für intensive Tests sollten kleiner Intervalle gewählt werden.

Mit der Property server.stats.log_file gibt man die Datei an, in welche protokolliert wird. Setzt man den Wert server.stats.append auf 1, dann werden neue Einträge an eine eventuell bestehende Datei angehängt.

Jede Zeile der Datei enthält eine Protokolleintrag. Eine Zeile, die mit $'$#$'$ beginnt, ist als Kommentarzeile markiert und sollte vor einer Auswertung entfernt werden. Innerhalb einer Zeile sind verschiedene Werte durch Tabulatoren getrennt. Welche Werte in welcher Spalte auftreten, gibt eine Kommentarzeile zu Beginn an.

Hier ein Beispiel:

#Server start at        Sun Sep 25 19:12:10 CEST 2005    Timestamp:     1127668331027
#time   regions clients freerc  bytes   load
52      1       0       0       0       0.10
1052    1       0       0       0       0.10
[...]
14070   1       0       0       0       0.08
15072   1       1       0       0       0.08
16073   1       2       2       0       0.08
17076   1       4       4       0       0.08
18076   1       6       5       0       0.08
19076   1       8       8       0       0.07

Die Spalten bedeuten der Reihe nach:

  1. Spalte enthält die vergangene Zeit seit dem Start des Servers.
  2. Anzahl der Regionen im RegionTree
  3. Verbundene Clients
  4. Freie RegionController
  5. Anzahl an übertragenen Bytes (reserviertes Feld, zur Zeit 0)
  6. Load des Servers. Dieser Wert wird unter Linux aus der Datei /proc/loadavg gelesen.

Das Logfile lässt sich leicht mit gängigen Unix-Tools auswerten. Gängige Tabellenkalkulationen können die Datei als CSV-Datei einlesen, indem man das Feldtrennzeichen auf Tabulator einstellt.


5.6 HTTP-Schnittstelle

Der ServerThread bietet eine einfache HTTP-Schnittstelle (siehe Abbildung 5.6). Diese ermöglicht die Ausführung einiger Funktionen und die Abfrage von Statusinformationen. Das Interface ist in der Klasse ServerThreadHttpInterface umgesetzt und kann leicht erweitert werden. Grundsätzlich sollte das HTTP-Interface problemlos mit jedem Browser funktionieren. Getestet wurde das Interface mit Mozilla 1.8b5, FireFox 1.0.2, Opera 7 und Internet Explorer 6.
Figure 5.6: Die HTTP-Oberfläche des Servers
Image http

5.6.1 Login über HTTP

Erreichbar ist der Server auf dem gleichen Port, auf der auch die Clients verbinden. Standard ist Port 18987. In der Regel läuft auf meinem System immer ein MMPGP2P-Server, der unter [17] erreichbar ist.

Beim ersten Verbinden wird eine Authentifizierung verlangt. Durch Benutzung einer beliebigen Kombination aus Benutzername und Passwort (z.B: hallo:hallo) gelangt man lediglich in ein Informations-Interface. Authentifiziert man sich mit Benutzername ``admin'' und dem zugehörigen Kennwort 5.5, dann hat man zusätzliche Möglichkeiten. Achtung: Es sollte unbedingt ein Kennwort konfiguriert werden, bevor das HTTP-Interface aktiviert wird.

Der Server trennt eine HTTP-Verbindung nach jeder Übertragung. Dieses Vorgehen ist aus technischen Gründen notwendig gewesen.

5.6.2 Kommandos

Auf der Seite existiert ein Eingabefeld, über das man Kommandozeilen an den Server übermitteln kann. Einige Kommandos sind als Direkt-Link oben auf der Seite zu finden, andere muss man eingeben mit Parameter.

  • SPLITX <X>:<Y> bzw. SPLITY <X>:<Y>
    Durch X:Y wird die zu splittende Region eindeutig identifiziert. Bei SPLITX wird entlang der X-Koordinate gesplittet, bei SPLITY entsprechend an der Y-Koordinate.
  • MERGE <X>:<Y>
    Die Region, welche den Punkt X:Y enthält, soll gemerged werden. Der RCPool der Region wird danach frei.
  • SHOWPROPS
    Die Properties des Servers ausgeben.
  • RCINFO
    Eine Liste der freien RegionController, eine Übersicht über den RegionTree sowie Details zum initialen RegionController ausgeben.
  • CLIENTINFO <ID>
    Informationen zum Client mit der angegebenen ID ausgeben.
  • RESETSYSTEMSTATS
    Die SystemStats zurücksetzen, ein neues Protokoll beginnen. Zu weiteren Informatione über die Statistikfunktionen siehe Abschnitt [*].

http://www.psitronic.de/ti/mmpg/mmpg-peer_to_peer/
Menü

Home
Funstuff
Linux
Hardware
Distributionen
Spiele
Kontakt
Projekte
Java
Webcut
Strength and Honor
A-Mobile
Holy-Wars 2
Holy-Wars 3
-> Dokumentation
Biometrie
Performanzermittlung
-> Mmpg
-> Mmpg-peer to peer
Javadoc
Mmpgp2p-Server
Semantic Web
WSA



- Impressum -
designed using WebCut - Copyright (c) 2001-2008 by Markus Sinner