Alcatraz: Unterschied zwischen den Versionen
Reini (Diskussion | Beiträge) |
Reini (Diskussion | Beiträge) |
||
| (129 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
= Architektur des Gesamtsystems = | = Architektur des Gesamtsystems = | ||
== Technologie == | |||
Zur Kommunikation der Clients untereinander, sowie der Clients mit den Servern wird Java RMI eingesetzt. | |||
Die Kommunikation zwischen den Servern erfolgt ausschließlich via Spread, über serialisierte Objekte und Text Nachrichten in ''SpreadMessage''s. | |||
== Server Struktur == | == Server Struktur == | ||
Ein Server fungiert als Koordinator | Ein Server fungiert als Koordinator (siehe [[#Koordinator und Election]]). | ||
Anfragen von Spiele-Clients können an jeden der Server ergehen, allerdings müssen diese an den Koordinator weitergeleitet werden. Nur der Koordinator entscheidet ob eine Registrierung erfolgreich ist, und propagiert in diesem Fall die erfolgte Registrierung an alle Server. Dies entspricht einem Primary-Backup Protokoll. | |||
== RMI Registry == | === Koordinator und Election === | ||
==== Variante 1: Spread ==== | |||
Der Koordinator ist bekannt - über die ''getMembers'' Methode des ''MembershipInfo'' Objekts kann die Liste der private Groups bezogen werden, der jeweils erste in dieser Liste ist der Koordinator. | |||
Sobald durch Änderung der Mitglieder ein neuer Koordinator "gewählt" wird, sendet der bisherige Koordinator diesem sein ''ServerState'' Objekt. Der neue Koordinator wird erst aktiv, sobald er das ''ServerState'' Objekt erhalten hat. | |||
'''Diese Variante wird im Alcatraz Projekt eingesetzt''' | |||
==== Variante 2: Bully Algorithmus ==== | |||
Sobald ein Server über ein zu konfigurierendes Zeitinterval hinweg keine ''Coordinator'' Message erhält, muss ein Ausfall des als Koordinator fungierenden Servers angenommen werden. | |||
In diesem Fall sendet der Server, der diesen Ausfall bemerkt, einen ''ElectionRequest'' als Multicast Message an die Multicast Gruppe. | |||
(Der Versand des Requests sollte jedoch um ein zufälliges Intervall verzögert werden, und falls in diesem Interval bereits ein ''ElectionRequest'' eines anderen Servers eingeht, storniert werden) | |||
Jeder Server hat lokal eine Referenz auf ein ''Coordinator'' Objekt. Ist diese Referenz ''null'' wird ebenfalls ein ''ElectionRequest'' ausgelöst, falls nicht innerhalb eines bestimmten Zeitraums eines ankommt. | |||
Solange kein gültiges ''Coordinator'' Objekt als lokale Referenz vorhanden ist, dürfen Server keine Requests von Clients annehmen. Eventuell ist für diesen Fall ein temporärer Fehler über eine spezielle Exception an den Client zu melden. | |||
Erhält ein Server einen ''ElectionRequest'', antwortet er mit einem ''ElectionResponse'' an die MultiCast Gruppe. Die Identität des Response Objekts wird dabei mit den Werten der ''ElectionRequest'' Identität initialisiert. | |||
Hat ein Server ein ''ElectionRequest'' Objekt und ''ElectionResponse'' Objekte erhalten (nach konfiguriertem ''ElectionTimout''), wird ausgewertet welches der Response Objekte den höchsten ''uptime'' Wert aufweist. Der entsprechende Absender, identifiziert durch das ''hostname'' Attribut ist der neue Koordinator. Dieser sendet ein ''Coordinator'' Objekt an die Multicast Gruppe. Erhält ein Server ein ''Coordinator'' Objekt, obwohl er bereits eine gültige Referenz auf ein ''Coordinator'' Objekt besitzt, wird das zuletzt eingegangene Objekt als neuer Koordinator gesetzt. Weisen innerhalb einer Election mehrere Server den gleichen ''uptime'' Wert auf, inkrementieren die betroffenen Server diesen Wert um einen zufälligen Wert, und senden danach erneut eine ''ElectionResponse'' Objekt. | |||
[[Datei:Election.jpg]] | |||
=== Synchronisation nach (Re-)Join === | |||
Wenn ein Server Objekt die Multicast Gruppe joint (hier wird durch Spread automatisch eine Membership-Message ausgelöst), sendet der Koordinator diesem ein ''ServerState'' Objekt. Diese beinhaltet unter anderem die ''PlayerList'', eine Liste der Namen der in der Registry registrierten Objekte, den Koordinator-Host, die Signatur (ein Spread PrivateGroup Objekt), und die momentanen Game- und Player- IDs. | |||
Der Server führt daraufhin folgenden Aktionen durch: | |||
* setzen des ''ServerState'' Objekts | |||
* starten des lokalen Registry | |||
* Synchronisieren lokalen Registry mit der Registry, die vom Koordinator verwaltet wird anhand der Liste der registrierten Objekte. | |||
* starten des Discovery Services (''DSCResponder'' Objekt) | |||
=== RMI Registry === | |||
* Jeder Server hat eine lokale Registry. | * Jeder Server hat eine lokale Registry. | ||
* Jeder Server initialisiert ein RegistryServer | * Jeder Server initialisiert ein ''RegistryServer'' Objekt und registriert es bei seiner lokalen Registry. | ||
* Jeder Server fungiert als Registry-Proxy für die Clients (siehe RMI Proxy) | Jedes ''RegistryServer'' Objekt hält eine Referenz auf ein ''ServerState'' Objekt, welches in weiterer Folge nur auf Anweisung des als Koordinator fungierenden Servers aktualisiert wird. | ||
* Jeder Server fungiert als Registry-Proxy für die Clients (siehe [[#RMI Proxy]]) | |||
== RMI Proxy == | === RMI Proxy === | ||
Jeder Server fungiert als RMI Proxy für einen Client. | Jeder Server fungiert als RMI Proxy für einen Client. | ||
Jeder | Jeder Bind-Request, der an einen der Registry-Proxies ergeht, wird an den Koordinator weitergeleitet. Dieser vergibt einen eindeutigen Objektnamen und sendet das zu registrierende Objekt gemeinsam mit dem Objektnamen an alle übrigen Server, die das Objekt unter diesem Namen in ihre lokalen RMI Registry importieren. | ||
Die RMI-Proxy Funktionalität (bind-Methode) wird vom ''RegistryServer'' Objekt bereitgestellt. | |||
In den meisten Fällen wird diese Proxy Funktionalität implizit vom Server aufgerufen, für den Client besteht bislang keine Notwendigkeit die bind-Methode explizit aufzurufen. | |||
==== Ablauf ==== | |||
Ein RMI Proxy erstellt ein ''BindRequest'' Objekt, und sendet dieses via Multicast Message. Dieses Objekt enthält das zu bindende Objekt und eine systemweit eindeutige Objekt-ID (OID Objekt). Jeder Server speichert den ''BindRequest'' vorerst lokal in der liste der zu behandelnden ''Request'' Objekte. Der Koordinator erstellt ein ''BindOrder'' Objekt, das als Attribut neben der Objekt-ID auch ein Attribut mit der URI (RMI URL ohne Protokoll, Hostname und Port) des zu bindenden Objekts enthält. Das ''BindOrder'' Objekt wird via Multicast Message gesendet. Sobald Server eine ''BindOrder'' erhalten, registrieren sie das enthaltene ''Remote'' Objekt unter dem angegebenen Namen und löschen das gespeicherte ''Request'' Objekt. | |||
Der Ablauf als Sequenz-Diagramm: | |||
[[Datei:Bind-Proxy.jpg]] | |||
=== Lokalisieren und Kontaktieren eines Registry Servers === | |||
Clients senden ein UDP Packet an eine konfigurierbare IP Adresse, auf einen konfigurierbaren Port. | |||
== | Server lauschen auf den definierten IP Adressen (und Ports). Diese Adresse ist optimalerweise (aber nicht notwendigerweise) eine Broadcast Adresse. Sobald ein Server ein solches DISCOVER Packet empfängt, sendet er die RMI URL zu seinem ''RegistryServer'' Objekt via UDP Packet an die Versender-IP-Adresse, auf einen konfigurierten Port, zurück. | ||
=== Ablauf der Kommunikation zwischen den ''RegistryServer''n bzw. zwischen ''Coordinator'' und ''RegistryServer'' === | |||
[[Datei:MessageFlow_700.jpg]] | |||
=== Spread Messages === | |||
Der Nachrichtenaustausch erfolgt in Form serialisierter Objekte, die mithilfe von ''SpreadMessage'' Objekten versendet werden. | |||
Ordering der Spread Messages ist ''Fifo by Sender'', da zwar die Reihenfolge der Nachrichten pro versendende Instanz relevant ist, causal ordering aber nicht erforderlich (Koordinator). | |||
=== Paket Diagramm === | |||
[[Datei:Alcatraz_Packages.jpg]] | |||
=== Klassen-Diagramme Server === | |||
[[Datei:Server_700.jpg]] | |||
= Spezifikation des Ablaufs zwischen Client und Registrierungsserver zum Starten des Spiels = | = Spezifikation des Ablaufs zwischen Client und Registrierungsserver zum Starten des Spiels = | ||
Es gibt mehrere Phasen: | |||
* Registrierungs-Phase und Pre-Registrierungs-Phase ([[#Registrierungs-Phase]]) | |||
* Initialisierungs-Phase und Spielstart-Phase [[#Initialisierungs-Phase]] | |||
Voraussetzung für diese Phasen ist, dass die Registrierungsserver online sind, und das RegistryServer Object in der RMI Registry registriert haben. | Voraussetzung für diese Phasen ist, dass die Registrierungsserver online sind, und das RegistryServer Object in der RMI Registry registriert haben. | ||
== | == Registrierungs-Phase == | ||
Clients versuchen | Eine Registrierung eines Clients ist jederzeit möglich. | ||
In der Pre-Registrierungs-Phase ist jedoch der Timer, der den Ablauf der Registrierungs-Phase signalisiert noch nicht gestartet. | |||
* vom Client wird | Eine erfolge Registrierung in der Pre-Registrierungs-Phase startet die Registrierungs-Phase. | ||
Clients versuchen, nachdem sie ein RegistryServer Object erhalten haben, sich über dieses zu registrieren. | |||
* Der Client erstellt ein lokales ''Registration'' Objekt. | |||
* vom Client wird die ''registerRequest'' Methode des ''RegistryServer'' Objekts aufgerufen. Diese Methode erhält als Parameter das ''Registration'' Objekt. | |||
* dieser Request wird an den Koordinator weitergeleitet | * dieser Request wird an den Koordinator weitergeleitet | ||
* der Koordinator registriert das ''Registration'' Objekt in der RMI Registry und sendet Objekt und vergebenen Namen an die anderen Server. | |||
* der Koordinator prüft den Request nach folgenden Voraussetzungen: | * der Koordinator prüft den Request nach folgenden Voraussetzungen: | ||
** ist der Name des Spielers noch frei | ** ist der Name des Spielers noch frei | ||
** die Maximal-Anzahl der Spieler bereits erreicht | ** ist die Maximal-Anzahl der Spieler bereits erreicht | ||
** ist die Registrierungsphase bereits abgeschlossen | ** ist die Registrierungsphase bereits abgeschlossen | ||
* der Koordinator ruft die | * der Koordinator ruft die ''deny'' oder ''accept'' Methode des ''Registration'' Objects auf, und propagiert diese Entscheidung an die anderen Server | ||
Die Registrierungsphase läuft ab wenn: | Die Registrierungsphase läuft ab wenn: | ||
| Zeile 41: | Zeile 109: | ||
* die maximale Spieranzahl erreicht ist | * die maximale Spieranzahl erreicht ist | ||
Sobald die Registrierungsphase abgelaufen ist, beginnt die Initialisierungs-Phase. Der Beginn | Sobald die Registrierungsphase abgelaufen ist, beginnt die Initialisierungs-Phase. Der Beginn dieser Phase wird den Clients vom Koordinator (über dem Aufruf der Methode ''startInitPhase'' am ''Request'' Objekt) mitgeteilt, und an die anderen Server propagiert. | ||
== Initialisierungs-Phase == | == Initialisierungs-Phase == | ||
* Clients initialisieren ein '' | * Clients initialisieren ein ''GameClient'' Objekt | ||
* Clients übergeben das ''GameClient'' Objekt an die ''setGameClient'' Methode des ''RegistryServer'' Objekts. Der Server überprüft anhand der ''getPlayerName'' Methode des ''GameClient'' Objekts ob diese Aktion überhaupt erlaubt ist. | |||
* Clients übergeben | * Clients erhalten ''GameClient'' Objekte ihrer Mitspieler über eine Methode des ''Registration'' Objekts, die vom ''Coordinator'' aufgerufen wird sobald alle aller Mitspieler registriert sind und ihren ''GameClient'' gesetzt haben. | ||
* Clients erhalten | * Clients erhalten über die vom ''Coordinator'' aufgerufene Methode ''setPlayerID'' des ''GameClient'' Objekts ihre Player-ID (diese wird dem ''Alcatraz'' Objekt übergeben) | ||
* Mit dem | * Mit dem Übertragen der ''GameClient'' Objekte endet die Aufgabe des Servers, und das Spiel beginnt | ||
== Gesamt-Ablauf als Sequenz-Diagramm == | |||
[[Datei:RegisterRequest_700.jpg]] | |||
= Spezifikation des Ablaufs zwischen Client und Client zum Austausch der Züge = | = Spezifikation des Ablaufs zwischen Client und Client zum Austausch der Züge = | ||
# jeder Client erzeugt ein lokales ''at.falb.games.alcatraz.api.Alcatraz'' Object | |||
# jeder Client erzeugt ein ''GameClient'' Objekt, das eine Referenz auf das ''Alcatraz'' Object erhält. Dieses Objekt kann als RMI fähiges Pendant zum ''MoveListener'' verstanden werden. Es implementiert eine ''moveDone()'' Methode, in der die ''doMove(..)'' Methode am ''Alcatraz'' Objekt aufgerufen wird (damit auch lokal am GUI der Zug des anderen Spielers sichtbar wird). Die ''moveDone()'' wird remote von den anderen Spielern aufgerufen. | |||
# jeder Client erzeugt ein Objekt, das ''at.falb.games.alcatraz.api.MoveListener'' impementiert. In der Methode ''moveDone()'' erfolgt ein Aufruf der ''moveDone()'' Methoden der ''GameClient''-Objekte der anderen Spieler. | |||
# das ''GameClient'' Objekt wird dem ''RegistryServer'' übergeben. | |||
# der Spieler erhält vom ''RegistryServer'' die ''GameClient'' Objekte der anderen Spieler | |||
# der Spieler erhält vom ''RegistryServer'' die Player-ID, die die Reihenfolge der Spieler bestimmt. | |||
# Am ''Alacatraz'' Objekt werden lokal die Methoden ''init()'' und ''start()'' aufgerufen, um das Spiel zu starten | |||
# Während ein Client auf Züge der anderen Spieler wartet, prüft er nach einer gewissen Zeit, ob sein Nachbar (jeder ''GameClient'' achtet auf genau einen Nachbar-''GameClient'') noch erreichbar ist. Antwortet dieser über einen zu definierenden Zeitraum nicht, wird auf allen Remote ''GameClient'' Objekten die ''abort()'' Methode aufgerufen, und das Spiel daraufhin abgebrochen. | |||
# Kann ein Client seinen Zug nicht an alle anderen ''GameClient'' Objekte propagieren, erfolgt nach einer gewissen Zeit ebenfalls ein Spiel Abbruch. | |||
== Klassendiagramm Client == | |||
[[Datei:Alcatraz_Client.jpg]] | |||
= Spezifikation der Mechanismen um Serverausfälle tolerieren zu können = | = Spezifikation der Mechanismen um Serverausfälle tolerieren zu können = | ||
* RMI Registry | * jeder Server hat eine lokale RMI Registry. Die RMI Registries der Server sind synchron. | ||
* RegistryServer | * das ''RegistryServer'' Objekt ist redundant. Registrierungen eines Clients werden über ein mit Spread implementiertes Primary-Backup Protokoll repliziert. | ||
* Koordinator | * Ein Koordinator ist jederzeit vorhanden. Dieser sendet seinen State an neue Mitglieder der Multicast Gruppe. | ||
* Jeder Server verwaltet eine Liste mit noch nicht beantworteten oder abgearbeiteten ''Request'' Objekten. Bei Erhalt eines ''Response'' Objekts wird das korrespondierende (über systemweite Objekt Identifizierer) ''Request'' Objekt aus der Liste entfernt. Neue Koordinatoren arbeiten die Liste mit den offenen Requests (z.B. Registrierungen, Unregister-Requests, Übergabe von ''GameClient'' Objekten) ab. Weiters wird der Status noch ungestarteter Spiele von neuen Koordinatoren geprüft und entsprechend gehandelt. | |||
''' | * Server, die der Multicast Gruppe beitreten (nach crash wieder verfügbar, oder neuer Server), synchronisieren die lokale RMI Registry mit jener des als Koordinator fungierenden Servers. Erst dann sind sie vollwertiges (und von Clients nutzbares) Mitglied der Multicast-Gruppe. | ||
= Remote Interface-Definition des Registrierungsservers (inkl. typisierten Methodenparametern) = | = Remote Interface-Definition des Registrierungsservers (inkl. typisierten Methodenparametern) = | ||
public interface RegistryServer extends java.rmi.Remote { | |||
boolean registerRequest(Registration r) | |||
throws java.rmi.RemoteException; | |||
void setGameClient(GameClient g, int payerID, int gameID) | |||
throws java.rmi.RemoteException; | |||
boolean unregister(int playerID, int gameID) | |||
throws java.rmi.RemoteException; | |||
} | |||
= Remote Interface-Definition des Game Clients (inkl. typisierten Methodenparametern) = | |||
'''Registration''': | |||
public interface Registration extends java.rmi.Remote, Serializable { | |||
String getPlayerName() throws java.rmi.RemoteException; | |||
void accept() throws java.rmi.RemoteException; | |||
void deny(String msg) throws java.rmi.RemoteException; | |||
void startInitPhase() throws java.rmi.RemoteException; | |||
void startGame(GameClient[] g) throws java.rmi.RemoteException; | |||
void setPlayerID(int id) throws java.rmi.RemoteException; | |||
int getPlayerID() throws java.rmi.RemoteException; | |||
void setGameID(int id) throws java.rmi.RemoteException; | |||
int getGameID() throws java.rmi.RemoteException; | |||
} | |||
''' | '''GameClient''': | ||
public interface GameClient extends java.rmi.Remote { | |||
void moveDone(at.falb.games.alcatraz.api.Player player, | |||
at.falb.games.alcatraz.api.Prisoner prisoner, | |||
int rowOrCol, | |||
int row, | |||
int col) throws java.rmi.RemoteException; | |||
/** | |||
* get the player name | |||
* @return String | |||
* @throws java.rmi.RemoteException | |||
*/ | |||
String getPlayerName() throws java.rmi.RemoteException; | |||
/** | |||
* set player id | |||
* @param id | |||
* @throws java.rmi.RemoteException | |||
*/ | |||
void setPlayerID(int id) throws java.rmi.RemoteException; | |||
/** | |||
* get player id | |||
* @return int | |||
* @throws java.rmi.RemoteException | |||
*/ | |||
int getPlayerID() throws java.rmi.RemoteException; | |||
/** | |||
* check if client is alive | |||
* @return boolean | |||
* @throws java.rmi.RemoteException | |||
*/ | |||
boolean isAlive() throws java.rmi.RemoteException; | |||
/** | |||
* abort game | |||
* @param reason | |||
* @throws java.rmi.RemoteException | |||
*/ | |||
void abort(String reason) throws java.rmi.RemoteException; | |||
} | |||
= Links = | = Links = | ||
[https://lyekka.callooh.com/svn/alcatraz/ Alcatraz svn] | |||
[https://lyekka.callooh.com/~reini/alcatraz_apidoc/ Projekt Javadoc] | |||
[http://www.spread.org/docs/javadocs/index.html Spread Javadoc] | |||
[https://lyekka.callooh.com/~reini/alcatraz-doc/ Alcatraz Javadoc] | [https://lyekka.callooh.com/~reini/alcatraz-doc/ Alcatraz Javadoc] | ||
[[Kategorie:FH]] | [[Kategorie:FH]] | ||
Aktuelle Version vom 3. Mai 2011, 20:42 Uhr
Architektur des Gesamtsystems
Technologie
Zur Kommunikation der Clients untereinander, sowie der Clients mit den Servern wird Java RMI eingesetzt. Die Kommunikation zwischen den Servern erfolgt ausschließlich via Spread, über serialisierte Objekte und Text Nachrichten in SpreadMessages.
Server Struktur
Ein Server fungiert als Koordinator (siehe #Koordinator und Election).
Anfragen von Spiele-Clients können an jeden der Server ergehen, allerdings müssen diese an den Koordinator weitergeleitet werden. Nur der Koordinator entscheidet ob eine Registrierung erfolgreich ist, und propagiert in diesem Fall die erfolgte Registrierung an alle Server. Dies entspricht einem Primary-Backup Protokoll.
Koordinator und Election
Variante 1: Spread
Der Koordinator ist bekannt - über die getMembers Methode des MembershipInfo Objekts kann die Liste der private Groups bezogen werden, der jeweils erste in dieser Liste ist der Koordinator. Sobald durch Änderung der Mitglieder ein neuer Koordinator "gewählt" wird, sendet der bisherige Koordinator diesem sein ServerState Objekt. Der neue Koordinator wird erst aktiv, sobald er das ServerState Objekt erhalten hat.
Diese Variante wird im Alcatraz Projekt eingesetzt
Variante 2: Bully Algorithmus
Sobald ein Server über ein zu konfigurierendes Zeitinterval hinweg keine Coordinator Message erhält, muss ein Ausfall des als Koordinator fungierenden Servers angenommen werden.
In diesem Fall sendet der Server, der diesen Ausfall bemerkt, einen ElectionRequest als Multicast Message an die Multicast Gruppe. (Der Versand des Requests sollte jedoch um ein zufälliges Intervall verzögert werden, und falls in diesem Interval bereits ein ElectionRequest eines anderen Servers eingeht, storniert werden)
Jeder Server hat lokal eine Referenz auf ein Coordinator Objekt. Ist diese Referenz null wird ebenfalls ein ElectionRequest ausgelöst, falls nicht innerhalb eines bestimmten Zeitraums eines ankommt.
Solange kein gültiges Coordinator Objekt als lokale Referenz vorhanden ist, dürfen Server keine Requests von Clients annehmen. Eventuell ist für diesen Fall ein temporärer Fehler über eine spezielle Exception an den Client zu melden.
Erhält ein Server einen ElectionRequest, antwortet er mit einem ElectionResponse an die MultiCast Gruppe. Die Identität des Response Objekts wird dabei mit den Werten der ElectionRequest Identität initialisiert.
Hat ein Server ein ElectionRequest Objekt und ElectionResponse Objekte erhalten (nach konfiguriertem ElectionTimout), wird ausgewertet welches der Response Objekte den höchsten uptime Wert aufweist. Der entsprechende Absender, identifiziert durch das hostname Attribut ist der neue Koordinator. Dieser sendet ein Coordinator Objekt an die Multicast Gruppe. Erhält ein Server ein Coordinator Objekt, obwohl er bereits eine gültige Referenz auf ein Coordinator Objekt besitzt, wird das zuletzt eingegangene Objekt als neuer Koordinator gesetzt. Weisen innerhalb einer Election mehrere Server den gleichen uptime Wert auf, inkrementieren die betroffenen Server diesen Wert um einen zufälligen Wert, und senden danach erneut eine ElectionResponse Objekt.
Synchronisation nach (Re-)Join
Wenn ein Server Objekt die Multicast Gruppe joint (hier wird durch Spread automatisch eine Membership-Message ausgelöst), sendet der Koordinator diesem ein ServerState Objekt. Diese beinhaltet unter anderem die PlayerList, eine Liste der Namen der in der Registry registrierten Objekte, den Koordinator-Host, die Signatur (ein Spread PrivateGroup Objekt), und die momentanen Game- und Player- IDs.
Der Server führt daraufhin folgenden Aktionen durch:
- setzen des ServerState Objekts
- starten des lokalen Registry
- Synchronisieren lokalen Registry mit der Registry, die vom Koordinator verwaltet wird anhand der Liste der registrierten Objekte.
- starten des Discovery Services (DSCResponder Objekt)
RMI Registry
- Jeder Server hat eine lokale Registry.
- Jeder Server initialisiert ein RegistryServer Objekt und registriert es bei seiner lokalen Registry.
Jedes RegistryServer Objekt hält eine Referenz auf ein ServerState Objekt, welches in weiterer Folge nur auf Anweisung des als Koordinator fungierenden Servers aktualisiert wird.
- Jeder Server fungiert als Registry-Proxy für die Clients (siehe #RMI Proxy)
RMI Proxy
Jeder Server fungiert als RMI Proxy für einen Client. Jeder Bind-Request, der an einen der Registry-Proxies ergeht, wird an den Koordinator weitergeleitet. Dieser vergibt einen eindeutigen Objektnamen und sendet das zu registrierende Objekt gemeinsam mit dem Objektnamen an alle übrigen Server, die das Objekt unter diesem Namen in ihre lokalen RMI Registry importieren.
Die RMI-Proxy Funktionalität (bind-Methode) wird vom RegistryServer Objekt bereitgestellt.
In den meisten Fällen wird diese Proxy Funktionalität implizit vom Server aufgerufen, für den Client besteht bislang keine Notwendigkeit die bind-Methode explizit aufzurufen.
Ablauf
Ein RMI Proxy erstellt ein BindRequest Objekt, und sendet dieses via Multicast Message. Dieses Objekt enthält das zu bindende Objekt und eine systemweit eindeutige Objekt-ID (OID Objekt). Jeder Server speichert den BindRequest vorerst lokal in der liste der zu behandelnden Request Objekte. Der Koordinator erstellt ein BindOrder Objekt, das als Attribut neben der Objekt-ID auch ein Attribut mit der URI (RMI URL ohne Protokoll, Hostname und Port) des zu bindenden Objekts enthält. Das BindOrder Objekt wird via Multicast Message gesendet. Sobald Server eine BindOrder erhalten, registrieren sie das enthaltene Remote Objekt unter dem angegebenen Namen und löschen das gespeicherte Request Objekt.
Der Ablauf als Sequenz-Diagramm:
Lokalisieren und Kontaktieren eines Registry Servers
Clients senden ein UDP Packet an eine konfigurierbare IP Adresse, auf einen konfigurierbaren Port.
Server lauschen auf den definierten IP Adressen (und Ports). Diese Adresse ist optimalerweise (aber nicht notwendigerweise) eine Broadcast Adresse. Sobald ein Server ein solches DISCOVER Packet empfängt, sendet er die RMI URL zu seinem RegistryServer Objekt via UDP Packet an die Versender-IP-Adresse, auf einen konfigurierten Port, zurück.
Ablauf der Kommunikation zwischen den RegistryServern bzw. zwischen Coordinator und RegistryServer
Spread Messages
Der Nachrichtenaustausch erfolgt in Form serialisierter Objekte, die mithilfe von SpreadMessage Objekten versendet werden.
Ordering der Spread Messages ist Fifo by Sender, da zwar die Reihenfolge der Nachrichten pro versendende Instanz relevant ist, causal ordering aber nicht erforderlich (Koordinator).
Paket Diagramm
Klassen-Diagramme Server
Spezifikation des Ablaufs zwischen Client und Registrierungsserver zum Starten des Spiels
Es gibt mehrere Phasen:
- Registrierungs-Phase und Pre-Registrierungs-Phase (#Registrierungs-Phase)
- Initialisierungs-Phase und Spielstart-Phase #Initialisierungs-Phase
Voraussetzung für diese Phasen ist, dass die Registrierungsserver online sind, und das RegistryServer Object in der RMI Registry registriert haben.
Registrierungs-Phase
Eine Registrierung eines Clients ist jederzeit möglich. In der Pre-Registrierungs-Phase ist jedoch der Timer, der den Ablauf der Registrierungs-Phase signalisiert noch nicht gestartet. Eine erfolge Registrierung in der Pre-Registrierungs-Phase startet die Registrierungs-Phase.
Clients versuchen, nachdem sie ein RegistryServer Object erhalten haben, sich über dieses zu registrieren.
- Der Client erstellt ein lokales Registration Objekt.
- vom Client wird die registerRequest Methode des RegistryServer Objekts aufgerufen. Diese Methode erhält als Parameter das Registration Objekt.
- dieser Request wird an den Koordinator weitergeleitet
- der Koordinator registriert das Registration Objekt in der RMI Registry und sendet Objekt und vergebenen Namen an die anderen Server.
- der Koordinator prüft den Request nach folgenden Voraussetzungen:
- ist der Name des Spielers noch frei
- ist die Maximal-Anzahl der Spieler bereits erreicht
- ist die Registrierungsphase bereits abgeschlossen
- der Koordinator ruft die deny oder accept Methode des Registration Objects auf, und propagiert diese Entscheidung an die anderen Server
Die Registrierungsphase läuft ab wenn:
- eine bestimmte Zeit vergangen ist
- die maximale Spieranzahl erreicht ist
Sobald die Registrierungsphase abgelaufen ist, beginnt die Initialisierungs-Phase. Der Beginn dieser Phase wird den Clients vom Koordinator (über dem Aufruf der Methode startInitPhase am Request Objekt) mitgeteilt, und an die anderen Server propagiert.
Initialisierungs-Phase
- Clients initialisieren ein GameClient Objekt
- Clients übergeben das GameClient Objekt an die setGameClient Methode des RegistryServer Objekts. Der Server überprüft anhand der getPlayerName Methode des GameClient Objekts ob diese Aktion überhaupt erlaubt ist.
- Clients erhalten GameClient Objekte ihrer Mitspieler über eine Methode des Registration Objekts, die vom Coordinator aufgerufen wird sobald alle aller Mitspieler registriert sind und ihren GameClient gesetzt haben.
- Clients erhalten über die vom Coordinator aufgerufene Methode setPlayerID des GameClient Objekts ihre Player-ID (diese wird dem Alcatraz Objekt übergeben)
- Mit dem Übertragen der GameClient Objekte endet die Aufgabe des Servers, und das Spiel beginnt
Gesamt-Ablauf als Sequenz-Diagramm
Spezifikation des Ablaufs zwischen Client und Client zum Austausch der Züge
- jeder Client erzeugt ein lokales at.falb.games.alcatraz.api.Alcatraz Object
- jeder Client erzeugt ein GameClient Objekt, das eine Referenz auf das Alcatraz Object erhält. Dieses Objekt kann als RMI fähiges Pendant zum MoveListener verstanden werden. Es implementiert eine moveDone() Methode, in der die doMove(..) Methode am Alcatraz Objekt aufgerufen wird (damit auch lokal am GUI der Zug des anderen Spielers sichtbar wird). Die moveDone() wird remote von den anderen Spielern aufgerufen.
- jeder Client erzeugt ein Objekt, das at.falb.games.alcatraz.api.MoveListener impementiert. In der Methode moveDone() erfolgt ein Aufruf der moveDone() Methoden der GameClient-Objekte der anderen Spieler.
- das GameClient Objekt wird dem RegistryServer übergeben.
- der Spieler erhält vom RegistryServer die GameClient Objekte der anderen Spieler
- der Spieler erhält vom RegistryServer die Player-ID, die die Reihenfolge der Spieler bestimmt.
- Am Alacatraz Objekt werden lokal die Methoden init() und start() aufgerufen, um das Spiel zu starten
- Während ein Client auf Züge der anderen Spieler wartet, prüft er nach einer gewissen Zeit, ob sein Nachbar (jeder GameClient achtet auf genau einen Nachbar-GameClient) noch erreichbar ist. Antwortet dieser über einen zu definierenden Zeitraum nicht, wird auf allen Remote GameClient Objekten die abort() Methode aufgerufen, und das Spiel daraufhin abgebrochen.
- Kann ein Client seinen Zug nicht an alle anderen GameClient Objekte propagieren, erfolgt nach einer gewissen Zeit ebenfalls ein Spiel Abbruch.
Klassendiagramm Client
Spezifikation der Mechanismen um Serverausfälle tolerieren zu können
- jeder Server hat eine lokale RMI Registry. Die RMI Registries der Server sind synchron.
- das RegistryServer Objekt ist redundant. Registrierungen eines Clients werden über ein mit Spread implementiertes Primary-Backup Protokoll repliziert.
- Ein Koordinator ist jederzeit vorhanden. Dieser sendet seinen State an neue Mitglieder der Multicast Gruppe.
- Jeder Server verwaltet eine Liste mit noch nicht beantworteten oder abgearbeiteten Request Objekten. Bei Erhalt eines Response Objekts wird das korrespondierende (über systemweite Objekt Identifizierer) Request Objekt aus der Liste entfernt. Neue Koordinatoren arbeiten die Liste mit den offenen Requests (z.B. Registrierungen, Unregister-Requests, Übergabe von GameClient Objekten) ab. Weiters wird der Status noch ungestarteter Spiele von neuen Koordinatoren geprüft und entsprechend gehandelt.
- Server, die der Multicast Gruppe beitreten (nach crash wieder verfügbar, oder neuer Server), synchronisieren die lokale RMI Registry mit jener des als Koordinator fungierenden Servers. Erst dann sind sie vollwertiges (und von Clients nutzbares) Mitglied der Multicast-Gruppe.
Remote Interface-Definition des Registrierungsservers (inkl. typisierten Methodenparametern)
public interface RegistryServer extends java.rmi.Remote {
boolean registerRequest(Registration r)
throws java.rmi.RemoteException;
void setGameClient(GameClient g, int payerID, int gameID)
throws java.rmi.RemoteException;
boolean unregister(int playerID, int gameID)
throws java.rmi.RemoteException;
}
Remote Interface-Definition des Game Clients (inkl. typisierten Methodenparametern)
Registration:
public interface Registration extends java.rmi.Remote, Serializable {
String getPlayerName() throws java.rmi.RemoteException;
void accept() throws java.rmi.RemoteException;
void deny(String msg) throws java.rmi.RemoteException;
void startInitPhase() throws java.rmi.RemoteException;
void startGame(GameClient[] g) throws java.rmi.RemoteException;
void setPlayerID(int id) throws java.rmi.RemoteException;
int getPlayerID() throws java.rmi.RemoteException;
void setGameID(int id) throws java.rmi.RemoteException;
int getGameID() throws java.rmi.RemoteException;
}
GameClient:
public interface GameClient extends java.rmi.Remote {
void moveDone(at.falb.games.alcatraz.api.Player player,
at.falb.games.alcatraz.api.Prisoner prisoner,
int rowOrCol,
int row,
int col) throws java.rmi.RemoteException;
/**
* get the player name
* @return String
* @throws java.rmi.RemoteException
*/
String getPlayerName() throws java.rmi.RemoteException;
/**
* set player id
* @param id
* @throws java.rmi.RemoteException
*/
void setPlayerID(int id) throws java.rmi.RemoteException;
/**
* get player id
* @return int
* @throws java.rmi.RemoteException
*/
int getPlayerID() throws java.rmi.RemoteException;
/**
* check if client is alive
* @return boolean
* @throws java.rmi.RemoteException
*/
boolean isAlive() throws java.rmi.RemoteException;
/**
* abort game
* @param reason
* @throws java.rmi.RemoteException
*/
void abort(String reason) throws java.rmi.RemoteException;
}






