Alcatraz: Unterschied zwischen den Versionen

Aus Callooh Wiki
Zur Navigation springen Zur Suche springen
Keine Bearbeitungszusammenfassung
 
(136 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, der nach dem Bully Algorithmus gewählt wird.  
Ein Server fungiert als Koordinator (siehe [[#Koordinator und Election]]).


Registrierungs-Anfragen 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 Based Backup Protokoll.  
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 Object und registriert es bei seiner lokalen Registry. In weiterer Folge wird dieses RegistryServer Objekt nur auf Anweisung des als Koordinator fungierenden Servers aktualisiert.
* 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 bind-Request, der an einen der Registry-Proxies ergeht, wird an den Koordinator weitergeleitet, der eine eindeutige URL vergibt, und weiters diesen Request auf alle Server repliziert.
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]]


Die RMI-Proxy Funktionalität (bind-Methode) wird vom RegistryServer Objekt bereitgestellt.
=== Lokalisieren und Kontaktieren eines Registry Servers ===
Clients senden ein UDP Packet an eine konfigurierbare IP Adresse, auf einen konfigurierbaren Port.  


== Kontaktieren des Registry Servers ==
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.
Clients haben eine Liste mit RMI-URLS via Konfigurations-Datei hinterlegt. Sobald ein Client gestartet wird, versucht er mit ''Naming.lookup()'' der Reihe nach über eine der URLS ein RegistryServer Object zu erhalten.
 
=== 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 =
2 Phasen: Registrierungsphase und Initialisierungs-Phase.
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.


== Registrierungsphase ==
== Registrierungs-Phase ==
Clients versuchen sich, nachdem sie ein RegistryServer Object erhalten haben, sich über dieses zu registrieren.
Eine Registrierung eines Clients ist jederzeit möglich.
Dazu muss lokal vom Client ein Register Object erstellt werden, und diese in der RMI Registry (via Proxy) registriert werden.  
In der Pre-Registrierungs-Phase ist jedoch der Timer, der den Ablauf der Registrierungs-Phase signalisiert noch nicht gestartet.
* vom Client wird eine Register-Request Methode aufgerufen, die als Parameter die rmi-URL des Register Objekts enthält.
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
** 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 reject oder accept Methode des Registry Objects auf, und propagiert diese Entscheidung an die anderen Server
* 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 diese Phase wird den Clients vom Koordinator (über einen Aufruf am Request Object) mitgeteilt, und an die anderen Server propagiert.
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 ''at.falb.games.alcatraz.api'' Object
* Clients initialisieren ein ''GameClient'' Objekt
* Clients initialisieren ein Alcatraz-RMI-Adapter Objekt, und registrieren dieses über den RMI-Proxy.
* 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 diese URL an das RegistryServer Objekt.
* 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 URL's der Alcatraz-RMI-Adpater Objekte ihrer Mitspieler über eine Methode des Register Objekts, die vom Server aufgerufen wird, sobald alle URL's aller Mitspieler registriert sind.  
* Clients erhalten über die vom ''Coordinator'' aufgerufene Methode ''setPlayerID'' des ''GameClient'' Objekts ihre Player-ID (diese wird dem ''Alcatraz'' Objekt übergeben)
* Mit dem übertragen dieser URL's endet die Aufgabe des Servers, und das Spiel beginnt.
* 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 =


Über ein RMI fähiges Adapter Objekt für die ''at.falb.games.alcatraz.api.Alcatraz'' und die gameWon Methode des ''at.falb.games.alcatraz.api.MoveListener'''s werden die Züge der Spieler bzw. Information über gewonnenes Spiel ausgetauscht.
# 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.
Der Ablauf erfolgt analog zur Test-Klasse at.falb.fh.vtsys.Test, nur dass statt dem
# 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.
''at.falb.games.alcatraz.api.Alcatraz'' Objekt die RMI fähige Adapter Klasse für die anderen Spieler (''other[]'') verwendet wird.
# 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.


'''TODO:'''
== Klassendiagramm Client ==
* Wie könne Ausfälle von Clients behandelt werden ? Bzw. - wie können Spieler in laufendem Spiel entfernt werden (Einigung aller Clients erforderlich!!)?
[[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 ist redundant
* jeder Server hat eine lokale RMI Registry. Die RMI Registries der Server sind synchron.
* RegistryServer Object ist redundant. Registrierungen eines Clients werden über ein mit Spread implementiertes Primary Backup Protokoll redundant gehalten.
* 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.


'''TODO:''' abklären ob rmiregistry eventuell geclone't werden kann - dann könnten ausgefallene server einen restore machen.  
= Remote Interface-Definition des Registrierungsservers (inkl. typisierten Methodenparametern) =
falls das nicht geht ist ein ausgefallener server für immer weg !!


= 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) =
= Remote Interface-Definition des Game Clients (inkl. typisierten Methodenparametern) =


== Alcatraz RMI Adapter ==
'''Registration''':
* void doMove(Player player, Prisoner prisoner, int rowOrCol, int row, int col)
public interface Registration extends java.rmi.Remote, Serializable {
* void gameWon(Player player)
    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:


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

  1. jeder Client erzeugt ein lokales at.falb.games.alcatraz.api.Alcatraz Object
  2. 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.
  3. 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.
  4. das GameClient Objekt wird dem RegistryServer übergeben.
  5. der Spieler erhält vom RegistryServer die GameClient Objekte der anderen Spieler
  6. der Spieler erhält vom RegistryServer die Player-ID, die die Reihenfolge der Spieler bestimmt.
  7. Am Alacatraz Objekt werden lokal die Methoden init() und start() aufgerufen, um das Spiel zu starten
  8. 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.
  9. 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;
}

Links

Alcatraz svn

Projekt Javadoc

Spread Javadoc

Alcatraz Javadoc