Alcatraz: Unterschied zwischen den Versionen

Aus Callooh Wiki
Zur Navigation springen Zur Suche springen
Rentom (Diskussion | Beiträge)
Zeile 404: Zeile 404:


[[Kategorie:FH]]
[[Kategorie:FH]]
= Protokoll der Präsentation vom 9.3.2011 =

Version vom 9. März 2011, 18:17 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, 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. 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.

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, 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 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 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 Server aufgerufen wird sobald alle aller Mitspieler registriert sind und ihren GameClient gesetzt haben.
  • Clients erhalten über die vom Server aufgerufene Methode getPlayerName 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

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 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

  • jeder Server hat eine lokale RMI Registry. Die RMI Registries der Server sind synchronisiert.
  • das RegistryServer Objekt ist redundant. Registrierungen eines Clients werden über ein mit Spread implementiertes Primary-Backup Protokoll repliziert.
  • Ein Koordinator wird mittels Election nach Bully Algorithmus gewählt sobald ein Server einen Ausfalls des Koordinators feststellt. (Dabei wird unterschieden ob die Server in der Startup-Phase sind oder der vorherige Koordinator ausgefallen ist)
  • Server, die nach crash wieder verfügbar werden, müssen die lokale RMI Registry mit jener eines anderen Servers synchronisieren (via Registry.getRegistry(String host), und Auruf der list() Methode am Registry Objekt), sowie eine Kopie eines RegistryServer Objekts erhalten (durch clone()), bevor sie der Multicast-Gruppe wieder beitreten können.

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

RegistryServer

  • String bind(java.RMI.Remote remote)
  • void registerRequest(Registration reg)
  • void unregister(String id)
  • void setGameClient(GameClient client)

Registration

  • String getPlayerName()
  • void accept()
  • void deny()
  • void startInitPhase()
  • 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
  • abort() 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

Protokoll der Präsentation vom 9.3.2011