Alcatraz: Unterschied zwischen den Versionen
Reini (Diskussion | Beiträge) |
Reini (Diskussion | Beiträge) |
||
| Zeile 15: | Zeile 15: | ||
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, 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. | Die RMI-Proxy Funktionalität (bind-Methode) wird vom ''RegistryServer'' Objekt bereitgestellt. | ||
== Kontaktieren des Registry Servers == | == Kontaktieren des Registry Servers == | ||
Version vom 6. März 2011, 10:59 Uhr
Architektur des Gesamtsystems
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 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 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.
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 AlcatrazClient 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 AlcatrazClient Objekte ihrer Mitspieler (TODO: Testen ob nicht auch Übergabe von AlcatrazClient 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.
- Clients initialisieren ein at.falb.games.alcatraz.api Object (local), rufen init() auf, registrieren die (remote)AlactrazClients der Mitspieler via addMoveListener, und rufen start() auf.
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 via RMI-Proxy registriert
- 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
TODO: Was soll passieren wenn einer der Clients eine Zeit lang nix tut? - eventuell Methode void playerLeftGame(Player player) vorsehen, die aufgerufen werden kann. Jeder setzt Timer nach seinem Zug, und wenn eine Zeit lang die moveDone Methode vom nächsten Player nicht aufgerufen wurden, wird die playerLeftGame() Methode auf allen (remote) AlcatrazClient Objekte aufgerufen. Eventuell ping-Methode im AlcatrazClient Objekt vorsehen, die regelmässig aufgerufen wird: falls kein response -> Client gecrasht
Meinung TN: Was soll Passieren, wenn client gecrasht erkannt wurde? Entfernen der Gefangenen des gecrashten clients und weiterspielen? Kompliziert! Wenn crash erkannt, dann game over und neue Registrierung anleiern.
Hast recht: es gibt keine Möglichkeit einen Spieler zu entfernen, also Game OVer --Reini 11:54, 6. Mär. 2011 (CET)
Spezifikation der Mechanismen um Serverausfälle tolerieren zu können
- RMI Registry ist redundant
- RegistryServer Object ist redundant. Registrierungen eines Clients werden über ein mit Spread implementiertes Primary-Backup Protokoll redundant gehalten.
- 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 !!
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)
Remote Interface-Definition des Game Clients (inkl. typisierten Methodenparametern)
AlcatrazClient:
- void moveDone(Player player, Prisoner prisoner, int rowOrCol, int row, int col) throws java.rmi.RemoteException
- boolean ping() throws java.rmi.RemoteException
- void playerLeftGame(Player player) 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 ping() throws java.rmi.RemoteException;
void playerLeftGame(Player player) 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 do gui:
alcatraz.doMove(player,prisoner,rowOrCol,row,col);
}
public boolean ping() throws java.rmi.RemoteException {
return true;
}
public void playerLeftGame(Player player) throws java.rmi.RemoteException {
}
}
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;
}
}