![]() |
|
Spiel-Client Der Spiel-Client kann fast vollständig aus Kapitel 7 übernommen werden. Wurde allerdings festgestellt, dass ein Spiel gewonnen oder verloren ist, so muss der Spiel-Client (Klasse GUISVUser) den RMI-Client aufrufen. Hierzu wurde eine Variable gewonnen und eine innere Thread-Klasse StartRMIClient entwickelt. Diese Klasse ruft dann den RMI-Client server2.Klient2 auf. Zunächst müssen die Variablendefinitionen wie folgt verändert werden:
// Daten für RMI-Client // Spiel ist verloren // Spiel ist gewonnen Die Methode actionPerformed muss nun um den Aufruf des RMI-Clients erweitert werden. Die veränderte Implementierung ist im Folgenden angegeben.
public void actionPerformed(ActionEvent e) { Daten daten; spieler.initialisieren(); netz = new SVClient((GUISVUser) c); } catch (IOException ioe){ ausgabeFenster("Probleme beim Netzaufbau"); } if (netz.SVSocket == null) return; SchiffFenster fenster = new SchiffFenster(spieler, c); fenster.setTitle("Schiffe setzen"); Die oben angegebenen Code-Zeilen sind noch identisch mit der bereits vorgestellten Implementierung. Nun muss aber der RMI-Thread gestoppt werden, falls dieser noch läuft:
// Thread der Datenbank mittels RMI-Client stoppen myThread.stop(); } Die nun folgenden Teile sind wiederum identisch mit der bisherigen Implementierung.
} else{ //Schiessen //Koordinaten bereits markiert? return; daten.status=daten.SCHUSS; try { daten = netz.getData(); }catch (IOException ioe) {} if (daten.status == daten.TREFFER) { //TREFFER //Einfaerben computer.schiffZahl--; if (computer.schiffZahl==0){ Nachdem der Rechner verloren hat, muss die Datenbank aktualisiert werden. Hierzu muss der folgende Aufruf verwendet werden:
// Anbinden an die Datenbank mittels RMI-Client, um if (myThread == null){ myThread = new StartRMIClient(); } Die restliche Implementierung der Methode ist wiederum identisch mit der bereits bekannten Version.
//Gewinnmeldung try { netz.beenden(); } catch (IOException ioe) {} //Neues Spiel anbieten return; } } else { sc1.sa1.tmp.x = sc1.sa1.tmp.y = -1; shoot.setEnabled(true); sc2.repaint(); } } } Auch die Methode rechnerTrifft muss um einen derartigen Aufruf erweitert werden, der aber mit dem oben beschriebenen Aufruf identisch ist. Hierbei wird allerdings die Variable gewonnen auf den Wert false gesetzt. Im Folgenden ist die Realisierung der inneren Klasse StartRMIClient angegeben, die den RMI-Client startet.
// Thread zum Starten des RMI-Clients, um den public StartRMIClient(){ super(); } try { server2.Klient2 kl2 = new server2.Klient2(); System.out.println("YAHOO Gewonnen!"); } else{ System.out.println("Schade Verloren!"); } }catch (Exception ex) {} } } RMI-Client Die Steueranwendung des RMI-Clients wurde ebenfalls bereits erläutert. Sie sieht - inklusive einiger Erweiterungen - wie folgt aus:
package server2; static int spielAntwort; public static void main (String args[]) { DatabaseUpdate2 du = null; Passwort2 pwd; System.err.println("Eingabe: java server.Klient2 <Server> <Port> <0 oder 1>"); } //Neuer Security Manager try { //Binden der Objektinstanz an entfernte Registry } catch (Exception e) { System.err.println("Keine Verbindung"+e); } try { pwd = new PasswortImpl2(); //Schicke 1, also Spieler hat gewonnen } else { //Schicke 0, also Spieler hat verloren } System.out.println("Eintrag erfolgsreich abgeschlossen."); else System.out.println("Eintrag wurde nicht bearbeitet."); }catch (Exception e4) { System.err.println("Problem in Remote-Methode des Clients "); } }// Main } // Hauptklasse Die Definition des Interfaces Passwort wurde dahingehend verändert, dass nun auch die User-ID eingegeben werden kann.
package server2; //Anfordern des Passworts und des User IDs } Die Implementierungsklasse ruft ein Fenster beim Client auf, wenn der Server dies initiiert. Hierzu wurde eine neue Klasse definiert, die im Folgenden beschrieben ist.
package server2; private String uid = ""; System.out.println("Innerhalb PWD-Konstruktor"); } this.uid = u; } this.pwd = p; } System.out.println("Aufruf der Passwort-Methode"); } System.out.println("Aufruf der uid-Methode"); } } Zur Eingabe des Passworts wurde die folgende Klasse entwickelt. Die Definition mag zwar lang erscheinen, beinhaltet allerdings fast ausschließlich die Funktionalität zur Erzeugung des Eingabefensters. Hierbei wird eine innere Klasse verwendet, die das Event-Handling übernimmt, sowie eine innere Klasse, die das Fenster wieder schließt.
package server2; String uid = ""; super(dw, title, true); //{{INIT_CONTROLS label1 = new java.awt.Label("User ID:",Label.RIGHT); textField4uid = new java.awt.TextField(); label2 = new java.awt.Label("Passwort:",Label.RIGHT); textField4pwd = new java.awt.TextField(); OKButton = new java.awt.Button(); setTitle("Eingabefenster"); //}} //{{REGISTER_LISTENERS windowListener = new EFWindowListener(); //}} } //{{DECLARE_CONTROLS java.awt.Label label1; //}} public void setUid(java.lang.String uid){ this.uid = uid; } return this.uid; } this.passwort = passwort; } return this.passwort; } Die folgende Klasse fungiert als Windows-Adapter, um das Fenster wieder zu schließen.
class EFWindowListener extends WindowAdapter { public void windowClosing(WindowEvent e){ dispose(); } dispose(); } } Die folgende Klasse fungiert als Event-Handler.
class SymAction implements java.awt.event.ActionListener{ public void actionPerformed(java.awt.event.ActionEvent event){ Object object = event.getSource(); button1_ActionPerformed(event); } } setUid(textField4uid.getText()); } } Spiel-Server Ein großer Vorteil dieser Anwendung ist, dass der Spiel-Server nicht modifiziert werden muss. Der Aufbau dieser Klassen bleibt daher unverändert. Der Leser sei hierzu auf die Beschreibung in Kapitel 7 verwiesen. RMI-Server Aufgabe des RMI-Servers ist es, ein Objekt anzulegen, dessen Methoden der Client zur Datenbankaktualisierung aufrufen kann. Hierzu wird das in Kapitel 11.4 beschriebene Programm verwendet. Der Vollständigkeit halber ist im Folgenden die Realisierung der main-Methode angegeben.
package server2; public static void main (String args[]) { if (args.length != 2) { System.err.println("Eingabe: java server.Datenbank2 <Server> <Port>"); } //Neuer Security Manager try { //Ort der Registry //Instanz der Datenbankanwendung //Binden der Objektinstanz an entfernte Registry System.out.println("Namensbindung erfolgt"); }catch (Exception e) { System.out.println("Fehler aufgetreten"); } } } Aufgabe dieser Klasse ist die Registrierung der Datenbankanbindung. Die Funktion wurde im Rahmen von Kapitel 11.4 bereits ausführlich erläutert. Zur Implementierung der Funktionalität wird nun das (ebenfalls unveränderte) Interface angegeben.
package server2; //Aktualisierung des Spielstands public int updateDatenbank(int spiel, Passwort2 pwd) throws RemoteException; } Die Implementierungsklasse DatabaseUpdateImpl2 wurde um den Datenbankzugriff erweitert. Diese Klasse wird vom RMI-Client aufgerufen, wenn ein Spiel verloren oder gewonnen wurde. Nachdem die Verbindung zur Datenbank geöffnet wurde, startet diese Klasse die beim Client gespeicherte Klasse PasswortImpl2. Der Benutzer wird anschließend dazu aufgefordert, sein User-ID und ein Passwort einzugeben. Danach wird das Passwort mit dem in der Datenbank abgespeicherten Passwort verglichen. Im Anschluss daran erfolgt die Anfrage an die Datenbank (in diesem Fall der Eintrag in die Datenbank). Danach werden einige Informationen angezeigt (bspw. User-ID, Anzahl der Spiele, Anzahl gewonnener und verlorener Spiele). Zum Schluss wird die Datenbankverbindung geschlossen. Die Implementierungsklasse des Interfaces sieht wie folgt aus:
package server2; protected Connection dbConnection; public int updateDatenbank (int spiel, Passwort2 pw) throws RemoteException { String clientPasswort= ""; Zuerst werden neben der Signatur und den Methoden der Klassen die Klassenvariablen definiert. Der Parameter spiel gibt an, ob der Spieler gewonnen oder verloren hat. Der Parameter pw ermöglicht die Abfrage des Passworts beim Client. Im Anschluss daran erfolgt der Datenbankzugriff:
// Verbinden mit der Datenbank Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); dbConnection = DriverManager.getConnection(dbURL, userID, passwd); PreparedStatement mein_prstmnt = dbConnection.prepareStatement("SELECT * FROM SpielerDatabase WHERE UID=?" ); PreparedStatement mein_prstmnt2 = dbConnection.prepareStatement("SELECT * FROM SpielerDatabase WHERE UID=?" ); PreparedStatement gewinn_prstmnt = dbConnection.prepareStatement("UPDATE SpielerDatabase SET Gewonnen=Gewonnen+? WHERE UID=?" ); PreparedStatement verlust_prstmnt = dbConnection.prepareStatement("UPDATE SpielerDatabase SET Verloren=Verloren+? WHERE UID=?" ); PreparedStatement spiele_prstmnt = dbConnection.prepareStatement("UPDATE SpielerDatabase SET Gespielt=Gespielt+? WHERE UID=?" ); Nach der Definition der Datenbankzugriffe erfolgt die Rückfrage nach dem Passwort beim Client.
UserID = pw.uid(); Nach dem Datenbankzugriff werden die Informationen zunächst auf der Konsole ausgegeben.
System.out.println(); clientPasswort = mein_ergebnis.getString("Passwort"); } Das Ergebnis der Abfrage wird hier in Variablen eingelesen und anschließend weiter verarbeitet.
if (pw.passwort().equals(clientPasswort)){ System.out.println("Passwort OK"); gewinn_prstmnt.setInt(1,1); } // Spiel Verloren spiel = 0 Nach der Prüfung des Passworts (beim Client) erfolgt der Aktualisierungsaufruf der Datenbank. Im Anschluss daran wird verarbeitet, dass der Spieler verloren hat.
else { System.out.println(" spiel: Verloren "); } Nachdem die Datenbankaktualisierung erfolgt ist, werden die neuen Informationen aus der Datenbank ausgelesen.
// Ausgabe der Ergebnisse aus der Datenbank } Im Anschluss daran wird die Verbindung zur Datenbank geschlossen, woraufhin die Methode endet. Wurde allerdings ein falsches Passwort eingegeben, so liefert die Methode den Rückgabewert -1 an den Client zurück.
// Schliesse Verbindung zur Datenbank } // Passwort OK } catch (Exception e){ cleanUp(); } cleanUp(); } public void cleanUp(){ try { System.out.println("Closing database connection"); } catch (SQLException e) { e.printStackTrace(); } } } Die Ausgabe dieses Programmteils ist in Abb. 14-2 dargestellt.
Abb. 14.2: Ausgabe der Datenbankaktualisierung Das Übersetzen der Klassen erfolgt, wie bereits beschrieben, in den folgenden Schritten:
Zum Starten des RMI-Servers wurde eine Batch-Datei geschrieben, die die RMI-Registry aufruft, die CLASSPATH-Variable auf den Wert server2 setzt und die Policy-Datei festlegt (java.policy ). Hierbei muss aber der Wert des Verzeichnisses, in dem die Klassen gespeichert sind, geeignet angepasst werden.
start rmiregistry java -classpath d:\tmp;d:\tmp\server2;%CLASSPATH% -Djava.security.policy=d:\tmp\java.policy server2.Datenbank2 localhost 1234 Ablauf des Spiels Da der Client als Applet ablaufen muss und aufgrund der Sicherheitseinschränkungen der RMI-Aufrufe, muss das Applet als signierte JAR-Datei geladen werden. Hierbei muss man sich verdeutlichen, dass aus dem Applet heraus Netzwerkzugriffe erfolgen, was eben nur erlaubt ist, wenn der Code signiert ist. Das Laden des Applets erfolgt in diesem Beispiel mit dem Java-Plug-In von JDK in der Version 1.2. Es wurde in der Einleitung bereits beschrieben, dass ein sicherer Betrieb der aktuellen Java-Version immer garantiert werden kann, wenn nicht die VM des Browsers, sondern ein Plug-In verwendet wird. Im Einzelnen werden die folgenden Schritte ausgeführt, um das Applet zu erzeugen bzw. aufzurufen. Duplikation der server2-Klassen Die Implementierungsklassen des Packages server2 müssen sowohl auf dem Server als auch auf dem Client verfügbar sein, da der Callback-Mechanismus verwendet wird. Aus diesem Grund muss gewährleistet sein, dass die Dateien sowohl auf dem Server als auch auf dem Client zur Verfügung stehen. Erzeugung der JAR-Datei Der Client lädt das Applet in Form einer JAR-Datei. Um diese zu erzeugen, muss die folgende Anweisung ausgeführt werden:
jar -cf0 client.jar *.class server2/*.class Signatur der JAR-Datei Nachdem die JAR-Datei erzeugt wurde, muss sie signiert werden. Hierzu wurde eine Batch-Datei entwickelt, die diese Aufgabe unterstützt. Diese Datei ist im Folgenden angegeben:
keytool -genkey -alias openJ -dname "cn=Open_Java, ou=KOM,o=TUD, c=GERMANY" Zuerst wird ein Keystore erzeugt, in dem ein Schlüssel angelegt wird, der als Passwort den Bezeichner openjava verwendet. In Abb. 14-3 ist der momentane Inhalt dieses Keystores dargestellt.
Abb. 14.3: Inhalt des Keystores Anschließend wird dieser Schlüssel exportiert und hierzu in einer Datei openjava.x509 abgelegt (Zertifikat). Zur Signatur der JAR-Datei wird dann das Tool jarsigner verwendet, das eine signierte Datei mit Namen sclient.jar erzeugt und hierzu den vorher definierten Schlüssel benutzt. Durch die Ausführung dieser Batch-Datei werden alle Klassen in der JAR-Datei signiert. Dieser Vorgang ist auch in Abb. 14-4 dargestellt.
Abb. 14.4: Signatur der JAR-Datei Resultat der Ausführung der Batch-Datei sind dann die Dateien sclient.jar und openjava.x509 (Signatur). Die Signatur muss jedem Benutzer zur Verfügung gestellt werden.
Abb. 14.5: Ausgabe des Policy-Tools Setzen der Berechtigungen Im nächsten Schritt müssen die Berechtigungen beim Client gesetzt werden, die dem Code eines bestimmten Absenders eingeräumt werden sollen, der über den öffentlichen Schlüssel identifiziert wird. Hierzu wird das Policy-Tool verwendet. In diesem Beispiel werden dem Code mit dem Schlüssel openJ alle Rechte eingeräumt. Es sei darauf hingewiesen, dass dies nur der Einfachheit halber erfolgt und ein Sicherheitsrisiko darstellt. Anschließend werden die folgenden Schritte durchgeführt:
Nachdem die Sicherheitsberechtigungen vorhanden sind, kann nach dem Start der Server die Anwendung ausgeführt werden. Hierzu müssen die jeweiligen HTML-Seiten beim Client im Browser geladen werden. Hierzu wurde die Datei index.html um den Link Starte das Applet erweitert. Wird dieser Link betätigt, so wird die Datei GUISVUserSJar.html aufgerufen, die das Applet im Plug-In startet. Diese HTML-Datei ist im Folgenden angegeben.
<html> <head></head> <body> <p> <h3 text="#ffff80" align=center>OpenJava-Applet</h3> </p> <OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" WIDTH = 100 HEIGHT = 100 codebase="http://java.sun.com/products/plugin/1.2/jinsta ll-12-win32.cab#Version=1,2,0,0"> <PARAM NAME = CODE VALUE = "GUISVUser.class" > <COMMENT> </OBJECT> </body> </html> Die Bildschirmdarstellung dieser Datei ist in Abb. 14-8 dargestellt. Wird das Plug-In ausgeführt, so startet das in Abb. 14-9 angegebene Fenster.
Abb. 14.8: Eintrag im Policy-Tool
Abb. 14.9: Start des Plug-Ins Nachdem der Spieler entweder gewonnen oder verloren hat, wird die Aktualisierung der Datenbank vorgenommen. Hierbei wird der Benutzer um die Eingabe einer User-ID und eines Passworts gebeten. Das dann erscheinende Fenster ist in Abb. 14-10 dargestellt.
Abb. 14.10: Passwortabfrage
|
|
|