Netzwerk Programmierung FH Merseburg SS 2002 RMI - Programmierung Erweiterung des Chat-System zu entferntem Methodenaufruf per RMI Was ist RMI (Remote Method Invocation) Theoretische Grundlagen Sockets sind für viele verteilte Anwendungen eine flexible, solide und ausreichende Technik beschränken sich auf Übertragung von Daten (Bytestrom) lassen Semantik der Daten unberücksichtigt es müssen auf Anwendungsebene Protokolle zur Interpretation der Daten entwickelt werden (siehe Socketbeispiele) Rahmenwerk für die Semantik steht zur Verfügung: Objekte Lokal: Objekte kommunizieren mit anderen Objekten über die mit Semantik belegten Methoden Symmetrische Beziehung: jedes Objekt kann ein anderes aufrufen Grundprinzip lokaler Methodenaufruf Beispiel Schläger und Ball Schläger schlägt den Ball Schläger ruft Methode hit() von Ball auf public class Schlaeger { public void play (Ball ball) { ball.hit(); } public static void main (String args[]) { Ball ball = new Ball(); Schlaeger schlaeger = new Schlaeger(); schlaeger.play(ball); } } public class Ball { public void hit(){ System.out.println geschlagen"); } ("Class Ball: Ball wurde } in verteilten Umgebungen: analog RMI Klasse Ball befindet sich auf anderer Maschine Problem: Referenzierung über Pointer nicht möglich (anderer Adressraum) Unterschiedliche Datenformate auf verschiedenen Maschinen RMI ist folglich ein Mechanismus, um Methoden von Objekten aufzurufen, die nicht auf der gleichen VM laufen. Ist in mehreren Schichten aufgebaut Server = Dienstanbieter Client = Dienstnehmer Beides sind normale Objekte, die in Java implementiert sind Server muß gekennzeichnet sein und für den R-Zugriff vorbereitet werden, bevor er vom Client benutzt wird asymmetrische Beziehung 481352650 Seite 1 von 8 Netzwerk Programmierung FH Merseburg SS 2002 Server muß Schnittstelle für R-Zugriff in IF-Beschreibung (von IF-Remote abgeleitet) dokumentieren aus dieser Beschreibung werden dann durch speziellen Compiler (rmic) zusätzliche Klassen STUB und SKELETON erzeugt, die sich um die Kommunikationsabwicklung kümmern STUB ist Stellvertreterobjekt, das die gleiche Schnittstelle wie Serverobjekt anbietet und einen Aufruf zu seinem Serverobjekt weiterleitet muß entweder auf dem Client-Rechner hinterlegt sein oder zur Laufzeit durch RMIClassloader geladen werden SKELETON verbleibt auf Serverseite und nimmt Aufrufe des STUB entgegen bereitet diese auf gibt sie an das Serverobjekt weiter erwartet das Ergebnis und sendet Ergebnis zurück an STUB Server Client Stub Skeleton RMI - Referenzschicht RMI - Transportschicht TCP/IP Referenzschicht dient dazu, um den jeweiligen Kommunikationspartner zu finden Teil dieser Schicht ist der Namensdienst, die Registry rmiregistry Transportschicht verwaltet Kommunikationsverbindungen (nicht OSI Transportschicht) Konkrete Realisierung Verteilte Objekte müssen sich anders verhalten als lokale O. Lokale Objekte erben von java.lang.Object Verteilte Objekte erben von java.rmi.RemoteObject dadurch werden Methoden von java.lang überlagert und an Remote-Bedingungen angepaßt Serversicht von java.rmi.RemoteObject ist java.rmi.UnicastRemoteObject abgeleitet, die eine Punktzu-Punkt-Verbindung anbietet ein O, auf das über RMI zugegriffen werden soll, muß von dieser Klasse erben die Schnittstelle, die das O für R-Zugriff anbieten möchte, muß über IF-definition beschrieben werden diese ist dann Grundlage für Erzeugung von Stubs und Skeletons nur hier definierte Methoden stehen auf R-Rechner zur Verfügung das IF muß IF java.rmi.Remote erweitern 481352650 Seite 2 von 8 Netzwerk Programmierung FH Merseburg SS 2002 java.rmi.RemoteObject java.rmi.Remote java.rmi.UnicastRemoteObject MyServerInterface extends implements MyServer Grundprinzip entfernter Methodenaufruf Server muß seine Dienste für R-Zugriff über IF zur Verfügung stellen import java.rmi.*; public interface RemoteBall extends Remote { public void hit() throws java.rmi.RemoteException; } Serverobjekt muß diese Schnittstelle implementieren von RemoteObject bzw. UnicastRemoteObject erben import java.rmi.*; import java.rmi.server.*; public class BallR extends UnicastRemoteObject implements RemoteBall { public BallR() throws RemoteException { super(); //Initialisierung UnicastRemoteObject } public void hit(){ System.out.println geschlagen"); } ("Class BallR: Der Ball wurde public static void main(String args[]) { try { BallR ballr = new BallR(); // Wenn Ball erzeugt wurde, kann dieser bei der Registry // angemeldet werden: Naming.rebind("BallR", ballr); } catch (Exception e) { e.printStackTrace(); } } } danach müssen Stub und Skeleton erzeugt werden: rmic BallR // Parameter = class-Datei BallR_Stub.class für Clientseite BallR_Skel.class für Serverseite 481352650 Seite 3 von 8 Netzwerk Programmierung beim Nachladen der Stub Datei über das Netz muss auf SecurityManager eingerichtet werden: FH Merseburg SS 2002 Clientseite ein System.setSecurityManager(new RMISecurityManager()); Bevor Server gestartet werden kann, muß rmiregistry gestartet werden, die für den Namensdienst und die Annahme von Anfragen verantwortlich ist Clientsicht C hat es einfacher, muß aber auch modifiziert werden um Zusätzliche Ausnahmen aufzufangen initale Verbindung zu einem R-Objekt herzustellen aber keine Interfaces implementieren um den Server finden zu können, wendet er sich an den Namensdienst auf dem Rechner des Servers und fragt mittels des Namens nach dem gewünschten Dienst import java.rmi.*; public class SchlaegerR { public BallR ballr; public void play (RemoteBall ballr) { try { ballr.hit(); }catch (RemoteException e) { System.out.println(e); } } public static void main (String args[]) { SchlaegerR batr = new SchlaegerR(); try { System.setSecurityManager(new RMISecurityManager()); RemoteBall remoteBall = (RemoteBall) Naming.lookup( "rmi://localhost/BallR"); schlaegerr.play(remoteBall); }catch (Exception e) { System.out.println(e); } } } Aufruf java SchlaegerR führt auf Serverrechner zu der Ausschrift: Class BallR: Der Ball wurde geschlagen. **** Ende des Beispiels Ball **** Komplexeres Beispiel: Chat System ChatServer ChatClient Im Socket-Beispiel wurden lediglich Strings ausgetauscht, die durch das vereinbarte Protokoll als Chat-Beiträge interpretiert wurden ChatServer stellt Dienste über IF zur Verfügung 481352650 Seite 4 von 8 Netzwerk Programmierung FH Merseburg SS 2002 meldet seine Dienste beim lokalen Namensdienst an und wartet auf Anrufe Client wendet sich an diesen Namensdienst auf der Servermaschine Baut eine Referenz zum Serverobjekt auf und benutzt die Schnittstelle als wäre sie ein lokales Objekt public interface IChatServer extends java.rmi.Remote { public void login(String name, IChatClient newClient) throws java.rmi.RemoteException; public void logout(String name) throws java.rmi.RemoteException; public void send(Message message) throws java.rmi.RemoteException; } auch Objekte werden übergeben send() Methode Message-Objekt (enthält Namen des Chatclients und Text) public class Message implements java.io.Serializable { public String name; public String text; public Message(String name, String text) { this.name = name; this.text = text; } } auch bei login() ChatClient-Objekt wird übergeben ChatClient beschreibt sich in diesem übergebenen Objekt selbst Server benötigt aber nicht nur die Beschreibung des Clients sondern auch Referenz auf ihn um ihm Nachrichten zukommen zu lassen diese sollen über RMI-Methodenaufrufe ausgetauscht werden Konsequenz: ChatClient-Objekt muß ebenfalls als Remote gekennzeichnet werden public interface IChatClient extends java.rmi.Remote { public void receiveEnter(String name) throws java.rmi.RemoteException; public void receiveExit(String name) throws java.rmi.RemoteException; public void receiveMessage(Message message) throws java.rmi.RemoteException; } Normale Objekte wie String oder Message werden, wenn sie als Parameter bei einem Methodenaufruf übergeben werden, als Kopie übergeben, Objekte die das IF Remote implementieren (wie IChatClient) werden als entfernte Referenz übergeben Server kann daraufhin entfernte Methoden im ChatClient-Object aufrufen (recieveEnter() für neue Chatmitglieder und recieveMessage() für neue Nachrichten) und ihm Nachrichten zukommen lassen Beispiel der Objektübergabe Message: Vorteil der OO vs Socket bei Änderung der Datenstruktur muß nicht gesamtes Auswertungsprotokoll wie bei Sockets geändert werden 481352650 Seite 5 von 8 Netzwerk Programmierung FH Merseburg SS 2002 Nachdem die Schnittstellen definiert sind, kann man sie implementieren: import java.rmi.*; import java.rmi.server.*; public class IChatClient{ ChatClient ChatFrame gui; String name; IChatServer server; String serverUrl; extends UnicastRemoteObject implements // GUI wie bei Socketbeispiel public ChatClient(String name, String url) throws RemoteException{ this.name = name; serverUrl = url; // GUI erzeugen und Events behandeln: // Nach Texteingaben wird sendTextToChat() aufgerufen, // bei Schließen des Fensters wird disconnect() aufgerufen. gui = new ChatFrame("Chat mit RMI"); gui.input.addKeyListener (new EnterListener(this,gui)); gui.addWindowListener(new ExitListener(this)); connect(); } private void connect() { try { server = (IChatServer) java.rmi.Naming.lookup ("rmi://"+serverUrl+ "/ChatServer"); server.login(name, this); } catch (Exception e) { e.printStackTrace();} } protected void sendTextToChat(String text) { try { server.send(new Message(name,text)); } catch (RemoteException e) {e.printStackTrace();} } protected void disconnect() { try { server.logout(name); } catch (Exception e) {e.printStackTrace();} } // im GUI wird neuer Chatteilnehmer ausgegegben public void receiveEnter(String name) { gui.output.append(name+" entered \n"); } // im GUI wird Verlassen eines Teilnehmers angezeigt public void receiveExit(String name) { gui.output.append(name+" left \n"); } // im GUI wird neue Message ausgegegben public void receiveMessage(Message message) { gui.output.append(message.name+": "+message.text+"\n"); } public static void main(String[] args) { if (args.length==2) { try { System.setSecurityManager(new RMISecurityManager()); new ChatClient(args[0],args[1]); } catch (RemoteException e) { 481352650 Seite 6 von 8 Netzwerk Programmierung e.printStackTrace(); } } else { System.out.println("Aufruf: <server>"); } FH Merseburg SS 2002 java ChatClient <name> } } Serverimplementierung Server hält Kontakt zu mehreren Clients alle eingehenden Nachrichten muss er an diese verteilen Clientverwaltung über Hash-Tabelle wobei die Namen als Schlüssel fungieren Aus dieser Tabelle wird eine Enumeration (Aufzählung) erzeugt, die der Reihe nach abgearbeitet wird und jeweils einen Client ausgibt, dem die Nachricht geschickt wird Server hält dabei die entfernten Referenzen, so daß der Aufruf über RMI abgewickelt wird import java.rmi.*; import java.rmi.server.*; import java.util.*; public class ChatServer extends UnicastRemoteObject implements IChatServer { Hashtable chatters = new Hashtable(); public ChatServer() throws RemoteException { } public synchronized void login(String name, IChatClient newClient) throws RemoteException { Enumeration enum = chatters.elements(); chatters.put(name, newClient); // login allen Clients bekannt geben while (enum.hasMoreElements()) { ((IChatClient) enum.nextElement()).receiveEnter(name); } System.out.println("Neuer Client "+name+" eingelogged"); } public synchronized void logout(String name) throws RemoteException { chatters.remove(name); Enumeration enum = chatters.elements(); // logout allen Clients bekannt geben while (enum.hasMoreElements()) { ((IChatClient)enum.nextElement()).receiveExit(name); } } public synchronized void send(Message message) throws RemoteException { Enumeration enum = chatters.elements(); while (enum.hasMoreElements()) { ((IChatClient)enum.nextElement()).receiveMessage(message); } } public static void main(String[] args) { 481352650 Seite 7 von 8 Netzwerk Programmierung FH Merseburg SS 2002 try { ChatServer server = new ChatServer(); Naming.rebind("ChatServer", server); } catch (Exception ex) {ex.printStackTrace();} }} *** 481352650 Seite 8 von 8