Alcatraz: Unterschied zwischen den Versionen

Aus Callooh Wiki
Zur Navigation springen Zur Suche springen
Zeile 64: Zeile 64:
# der Spieler erhält vom RegistryServer die ''GameClient'' Objekte der anderen Spieler (bzw. deren URLs)
# der Spieler erhält vom RegistryServer die ''GameClient'' Objekte der anderen Spieler (bzw. deren URLs)
# Am ''Alacatraz'' Objekt werden lokal die Methoden ''init()'' und ''start()'' aufgerufen, um das Spiel zu starten
# Am ''Alacatraz'' Objekt werden lokal die Methoden ''init()'' und ''start()'' aufgerufen, um das Spiel zu starten
# Während ein Client auf den Züge der anderen Spieler wartet, prüft er nach einer gewissen Zeit, ob diese noch erreichbar sind. Antwortet ein Remote ''GameClient'' über einen zu definierenden Zeitraum nicht, wird auf allen Remote ''GameClient'' Objekten die ''abort()'' Methode aufgerufen, und das Spiel daraufhin abgebrochen.
# Während ein Client auf Züge der anderen Spieler wartet, prüft er nach einer gewissen Zeit, ob diese noch erreichbar sind. Antwortet ein Remote ''GameClient'' über einen zu definierenden Zeitraum nicht, wird auf allen Remote ''GameClient'' Objekten die ''abort()'' Methode aufgerufen, und das Spiel daraufhin abgebrochen.


= Spezifikation der Mechanismen um Serverausfälle tolerieren zu können =
= Spezifikation der Mechanismen um Serverausfälle tolerieren zu können =

Version vom 8. März 2011, 21:22 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.

Server Struktur

Ein Server fungiert als Koordinator, der nach dem Bully Algorithmus gewählt wird.

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-Backup Protokoll.

RMI Registry

  • Jeder Server hat eine lokale Registry.
  • Jeder Server initialisiert ein RegistryServer Objekt 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 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, der eine eindeutige URL vergibt, und weiters diesen Request auf alle Server repliziert.

Die RMI-Proxy Funktionalität (bind-Methode) wird vom RegistryServer Objekt bereitgestellt.

Kontaktieren des Registry Servers

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.

Offene Design-Fragen:

  • Prüfen eleganterer Wege, eventuell via Broadcast nach Servern suchen. Oder Server machen Broadcast, und Clients lauschen auf diese Broadcasts ..
  • Liste der Server-URLs am Client eventuell durch Server aktualisieren, etwa wie in diversen P2P Apps.

Spezifikation des Ablaufs zwischen Client und Registrierungsserver zum Starten des Spiels

2 Phasen: Registrierungsphase und Initialisierungs-Phase.

Voraussetzung für diese Phasen ist, dass die Registrierungsserver online sind, und das RegistryServer Object in der RMI Registry registriert haben.

Registrierungsphase

Clients versuchen sich, nachdem sie ein RegistryServer Object erhalten haben, sich über dieses zu registrieren. Dazu muss lokal vom Client ein Register Object erstellt werden, und diese in der RMI Registry (via Proxy) registriert werden.

  • vom Client wird eine Register-Request Methode aufgerufen, die als Parameter die rmi-URL des Register Objekts enthält. (TODO: eventunell diesen Schritt mit Registrierung vom Register Objekt verbinden, also die registerRequest Methode übernimmt statt der URL das Register Objekt)
  • dieser Request wird an den Koordinator weitergeleitet
  • 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 reject oder accept Methode des Registry 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 diese Phase wird den Clients vom Koordinator (über einen Aufruf am Request Object) mitgeteilt, und an die anderen Server propagiert.

Initialisierungs-Phase

  • Clients initialisieren ein GameClient Objekt, und registrieren dieses über den RMI-Proxy.
  • Clients übergeben diese URL an das RegistryServer Objekt (TODO: eventuell obigen Schritt und diesen in eine Methode zusammenfassen)
  • Clients erhalten URL's der GameClient Objekte ihrer Mitspieler (TODO: Testen ob nicht auch Übergabe von GameClient Referenzen möglich wäre) über eine Methode des Register Objekts, die vom Server aufgerufen wird sobald alle URL's aller Mitspieler registriert sind.
  • Mit dem übertragen dieser URL's endet die Aufgabe des Servers, und das Spiel beginnt

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 via RMI-Proxy registriert
  5. der Spieler erhält vom RegistryServer die GameClient Objekte der anderen Spieler (bzw. deren URLs)
  6. Am Alacatraz Objekt werden lokal die Methoden init() und start() aufgerufen, um das Spiel zu starten
  7. Während ein Client auf Züge der anderen Spieler wartet, prüft er nach einer gewissen Zeit, ob diese noch erreichbar sind. Antwortet ein Remote GameClient über einen zu definierenden Zeitraum nicht, wird auf allen Remote GameClient Objekten die abort() Methode aufgerufen, und das Spiel daraufhin abgebrochen.

Spezifikation der Mechanismen um Serverausfälle tolerieren zu können

  • RMI Registry ist redundant
  • RegistryServer Objekt ist redundant. Registrierungen eines Clients werden über ein mit Spread implementiertes Primary-Backup Protokoll repliziert.
  • Koordinator mittels Election nach Bully Algorithmus

TODO: abklären ob rmiregistry eventuell geclone't werden kann - dann könnten ausgefallene server einen restore machen. falls das nicht geht ist ein ausgefallener server für immer weg !! RMI activation?

Remote Interface-Definition des Registrierungsservers (inkl. typisierten Methodenparametern)

RegistryServer

  • String bind(java.RMI.Remote remote)
  • void registerRequest(URL url)
  • void setGameClient(URL url)

Register

  • String getPlayerName()
  • void accept()
  • void deny()
  • void startInitPhase()
  • void startGame(URL[] urls) bzw. void startGame(GameClient[] player)
  • void setPlayerID(int id)

Remote Interface-Definition des Game Clients (inkl. typisierten Methodenparametern)

GameClient:

  • void moveDone(Player player, Prisoner prisoner, int rowOrCol, int row, int col) throws java.rmi.RemoteException
  • boolean isAlive() throws java.rmi.RemoteException
  • String getPlayerName() throws java.rmi.RemoteException

Code Samples

RMI Proxy

public interface BindProxyInterface extends java.rmi.Remote {

   String bind(java.rmi.Remote t)
  	 throws java.rmi.RemoteException, java.net.MalformedURLException;
}


import java.rmi.Naming;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
 
public class BindProxy extends UnicastRemoteObject implements BindProxyInterface {

   private int counter = 0;
   private final String identifier = "rmiIdent";

   private String servername;

   public BindProxy(String servername) throws java.rmi.RemoteException {
  	 super();

  	 this.servername = servername;
   }

   public String bind(java.rmi.Remote t)
  	 throws java.rmi.RemoteException, java.net.MalformedURLException {

  	 String url = "rmi://".concat(servername).concat(":") +
  					  BindServer.port +
  					  "/".concat(identifier + counter++);
  	 Naming.rebind(url, t);
  	 return url;
   }
}


import java.rmi.Naming;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
 
class BindServer  {

   public static final int port = 1099;
   public static final String bindName = "BindProxy";

   public static void main(String args[]) {

  	 String servername;
  	 if (args==null || args.length == 0 || args[0].length()==0) {
  		 System.out.println("Usage: BindServer serverURL");
  		 return;
  	 }
  	 servername = args[0];

  	 //startup registry:
  	 try {
  		 java.rmi.registry.LocateRegistry.createRegistry(port);
  	 } catch (RemoteException e) {
  		 e.printStackTrace();
  		 System.exit(1);
  	 }

  	 try {
  		 BindProxy b = new BindProxy(servername);
  		 Naming.rebind("rmi://".concat(servername).concat(":") +
  							port +
  							"/".concat(bindName), b);
  	 } catch (Exception e) {
  		 e.printStackTrace();
  		 System.exit(1);
  	 }
   }
}


import java.rmi.Naming;
import java.rmi.NotBoundException;

public class BindClient {

   public static void main(String args[]) {

  	 if (args==null || args.length == 0) {
  		 System.out.println("Usage: BindClient serverURL");
  		 return;
  	 }


  	 String serverURL = "rmi://".concat(args[0]).concat(":") +
  							  BindServer.port +
  							  "/".concat(BindServer.bindName);

  	 BindProxyInterface b = null;
  	 try {
  		 b = (BindProxyInterface)Naming.lookup(serverURL);
  	 } catch (Exception e) {
  		 e.printStackTrace();
  	 }

  	 if (b==null) return;

  	 String url = null;
  	 try {
  		 TestObject o = new TestObject(692);
  		 url = b.bind(o);
  		 System.out.println("proxy hat url registriert unter: " + url);
  	 } catch (Exception e) {
  		 e.printStackTrace();
  	 }

  	 if (url==null) {
  		 System.out.println("URL is null!!");
  		 return;
  	 }

  	 TestInterface o = null;
  	 try {
  		 o = (TestInterface)Naming.lookup(url);
  	 } catch (Exception e) {
  		 e.printStackTrace();
  	 }

  	 if (o==null) {
  		 System.out.println("testobject is null!!");
  	 } else {
  		 try {
  			 System.out.println("id ist "  + o.getId());
  		 } catch (java.rmi.RemoteException e) {
  			 e.printStackTrace();
  		 }
  	 }
  	 System.exit(0);
   }
}

GameClient

Proof of Concept mit 2 Spielern, lauffähig, ohne checks ob anderer Client noch lebt:

Usage:

setenv CLASSPATH .... ; rmiregistry

java -cp alcatraz-lib.jar:. Client 0 1

java -cp alcatraz-lib.jar:. Client 1 0

import at.falb.games.alcatraz.api.Player;
import at.falb.games.alcatraz.api.Prisoner;
import at.falb.games.alcatraz.api.MoveListener;

public interface GameClientInterface extends java.rmi.Remote {

   void moveDone(Player player, Prisoner prisoner, int rowOrCol, int row, int col)
  	 throws java.rmi.RemoteException;

   boolean isAlive() throws java.rmi.RemoteException;
}


import java.rmi.Naming;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;

import at.falb.games.alcatraz.api.Alcatraz;
import at.falb.games.alcatraz.api.Player;
import at.falb.games.alcatraz.api.Prisoner;

public class GameClient extends UnicastRemoteObject
  							 implements GameClientInterface {

   private Alcatraz alcatraz;

   public GameClient(Alcatraz a) throws java.rmi.RemoteException {
  	 super();
  	 alcatraz = a;
   }

   public void moveDone(Player player, Prisoner prisoner, int rowOrCol, int row, int col)
  	 throws java.rmi.RemoteException {

  		 //propagate move to gui:
  		 alcatraz.doMove(player,prisoner,rowOrCol,row,col);
   }

   public boolean isAlive() throws java.rmi.RemoteException {
  	 return true;
   }
}


import java.rmi.Naming;

import at.falb.games.alcatraz.api.Alcatraz;
import at.falb.games.alcatraz.api.MoveListener;
import at.falb.games.alcatraz.api.Player;
import at.falb.games.alcatraz.api.Prisoner;

public class Client implements MoveListener {

   private static Integer otherID = null;
   private GameClientInterface other = null;

   public static void main(String[] args) {
  	 if (args==null || args.length < 2) {
  		 System.err.println("Usage: Client ident ident_other");
  		 System.exit(1);
  	 }

  	 int myID = new Integer(args[0]);
  	 otherID = new Integer(args[1]);

  	 if (!((otherID==0 && myID==1) ||
  		  (myID==0 && otherID==1))) {

  		 System.err.println("ident and ident_other has to be 0 1 or 1 0");
  		 System.exit(1);
  	 }

  	 try {
  		 new Client(myID);
  	 } catch (java.rmi.RemoteException e) {
  		 e.printStackTrace();
  	 }

   }


   public Client(int ident) throws java.rmi.RemoteException {
  	 Alcatraz a = new Alcatraz();
  	 GameClient g = new GameClient(a);

  	 try {
  		 Naming.rebind("rmi://localhost:1099/GameClient" + ident, g);
  	 } catch (java.net.MalformedURLException e) {
  		 e.printStackTrace();
  		 System.exit(1);
  	 }

  	 while (other == null) {
  		 other = getOther();
  		 if (other == null) {
  			 System.err.println(".. waiting for player " + otherID);
  			 try {
  				 Thread.sleep(1000);
  			 } catch (java.lang.InterruptedException e) {
  				 e.printStackTrace();
  				 return;
  			 }
  		 }
  	 }

  	 a.init(2, ident);
  	 a.getPlayer(ident).setName("Player " + (ident+1));
  	 a.getPlayer(otherID).setName("Player " + (otherID+1));

  	 a.showWindow();
  	 a.addMoveListener(this);

  	 a.start();
   }

   public void gameWon(Player player) {
  	 System.out.println("Game won by player " + player);
   }

   public void moveDone(Player player, Prisoner prisoner, int rowOrCol, int row, int col) {
  		System.out.println("moving " + prisoner + " to " + (rowOrCol == Alcatraz.ROW ? "row" : "col") + " " + (rowOrCol == Alcatraz.ROW ? row : col));

  		//propagate move from gui to other game client(s):
  		try {
  			other.moveDone(player,prisoner,rowOrCol,row,col);
  		} catch (java.rmi.RemoteException e) {
  			e.printStackTrace();
  		}
   }

   private GameClientInterface getOther() {

  	 try {
  		 return (GameClientInterface)Naming.lookup("rmi://localhost:1099/GameClient" + otherID);
  	 } catch (java.rmi.NotBoundException e) {
  		 System.err.println(e.getMessage().
  								  concat(" - did not find other player" + otherID));
  	 } catch (java.net.MalformedURLException e) {
  		 e.printStackTrace();
  		 System.exit(1);
  	 } catch (java.rmi.RemoteException e) {
  		 e.printStackTrace();
  		 System.exit(1);
  	 }
  	 return null;
   }
}

Links

Alcatraz Javadoc