Remote-Objekte

Werbung
RMI Remote Method Invocation
JOHANNES KEPLER UNIVERSITY LINZ
Research and teaching network
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
2
Motivation
Methodenaufruf bei einer VM
 Objekte innerhalb einer JVM
Methodenaufruf über VM
Grenzen hinweg
 Objekte sind auf mehreren JVMs
(Rechnern) verteilt
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
3
Probleme bei Remote-Objekten
Objektreferenzen:
 Was ist eine Remote-Referenz?
 Wie finde ich ein Objekt?
 Wie spreche ich Objekte an?
Methodenaufruf:
 Wie wird die Sprungadresse für die Methode ermittelt?
Parameterübergabe und Rückgabewerte:
 Wie werden Parameterwerte und Rückgabewerte verschickt?
Objekterzeugung:
 Wie kann der Client ein Remote-Objekt erzeugen?
Garbage Collection:
 Wann kann ein Objekt am Server frei gegeben werden?
Laden der Klassen:
 Von wo wird die Klasse für ein vom Client benötigtes Objekt geladen?
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
4
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
5
Proxy-Pattern
request()
Client
Interface:
 definiert eine allgemeine Schnittstelle, die Client sieht und Server implementieren muss
Implementierung für den Server
 hat Interface entsprechend zu implementieren
Proxy für den Client
 wird vom Client angesprochen
 implementiert die Schnittstelle, d.h. stellt sich dar wie ein wirkliches Objekt
 delegiert alle Requests an die wirkliche Objektimplementierung
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
6
Proxy-Pattern bei Remote-Objekten
Proxy

Stub stellt das Proxy-Objekt auf der Seite des Client dar

Proxy leitet die Anforderungen über die VM-Grenzen an Server weiter

Skeleton empfängt die Requests und baut eigentlichen Request an den Server auf

Server bedient den Request

Ergebnis wird an Skeleton zurückgegeben

Ergebnis wird über Netzwerk an Stub weitergeleitet

Stub gibt das Ergebnis an Client zurück
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
7
Kommunikationsarchitektur
Client und Server
Stubs und Skeletons
Remote Reference Layer
Netzwerkverbindung
Netzwerkverbindung
 zwischen JVMs immer über Network
Layer des Host OS
Pratikum SWE 2
JVM
JVM
© Institut für Systemsoftware, Johannes Kepler Universität Linz
JVM
JVM
8
Kommunikationsarchitektur: Aufgaben der Schichten
Client und Server
 sind die eigentlichen Anwendungsobjekte
 kommunizieren über virtuelle Kommunikation
Stubs und Skeletons
 Stub (Achtung: ab Java 1.5 nicht mehr nötig; mittels Dynamic-Proxies gelöst)
 ist das Proxy-Objekt auf der Client-Seite und stellt sich wie Server-Objekt dar
 leitet Requests des Client an die unteren RMI-Services weiter
 Skeleton (Achtung: ab Java 1.2 nicht mehr nötig!!!)
 empfängt die Requests von unteren RMI-Services und
 setzt diese für Requests an Server-Objekt um
Remote Reference Layer
 verwaltet die Remote-Referenzen (Klasse RemoteRef) der Server-Objekte
 RemoteRef bietet Methode invoke für Methodenaufrufe abzusetzen (wird von Stubs
verwendet)
 dient zur Registrierung von Remote-Objekts
Netzwerkverbindung




eigentliche Netzwerkverbindung über TCP/IP
stream-basierte Verbindung
standardmäßig auf Port 1099
Kommunikationsprotokoll oberhalb TCP/IP (nicht standardisiert)
 Java Remote Method Protocol (JRMP)
 IIOP
 und weitere
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
9
Objektregistrierung und Objektsuche
RMI ermöglicht
 Registrierung von Remote-Objekten durch Server mit einer RMI-URL
 Aufsuchen von Remote-Objekten durch Client
Dazu dienen
 RMI-Registrierungsprogramm rmiregistry
 RMI-Service Klassen Naming, Registry, LocateRegistry
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
10
Objektregistrierung und Objektsuche: Vorgehen
LocateRegistry: Zugriff auf Registry Service
Registry: bind oder rebind ein Server-Objekt registriert.

Es muss dabei ein eindeutiger Name für das Server-Objekt vergeben werden.
try {
Bank bank = new BankImpl();
Registry reg = LocateRegistry.createRegistry(1099);
reg.bind("Bank", bank);
}
...
default port 1099;
andere möglich
Das Server-Objekt wird dabei unter der RMI-URL mit
rmi://<HostComputer>:1099/Bank
eingetragen.
Damit hat ein Client eine eindeutige URL, um auf ein Server-Objekt zuzugreifen;
// Client
try {
Registry reg = LocateRegistry.getRegistry("<Hostcomputer>", 1099);
Bank bank = (Bank) reg.lookup("Bank");
...
}
...
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
11
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
13
Realisierung eines Remote-Objekts
Interfaces und Klassen
Interface Spezifikation für Remote-Objekt

muss java.rmi.Remote erweitern

definiert die sichtbaren Methoden des Remote-Objekts

jede Methode muss eine RemoteException werfen
Server-Objekt

muss Interface implementieren

muss an das RMI System exportiert werden
Stub-Objekt (kann ab Java 1.5 durch DynamicProxy ersetzt werden)

wird mit rmic erzeugt

implementiert Interface
Skeleton-Objekt (nur bei Java 1.1 verwendet)

wird mit rmic erzeugt

erweitert Basisklasse java.rmi.server.Skeleton
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
ab Java 1.5
auch mittels
Dynamic-Proxy
14
Beispiel Bank-Server
Im folgenden Beispiel wird anhand eines einfachen Bank-Servers die
Realisierung eines Client-Server-Programms veranschaulicht
Folgende Schritte sind dabei durchzuführen:
 Erstellen
 Definition eines Interfaces für das Remote-Objekt
 Implementierung des Interfaces für den Server
 Generierung von Stub- und Skeleton-Klassen mit rmic (ab 1.5 nicht mehr nötig)
 Implementierung des Server-Programms, welches das Server-Objekt anlegt und beim
RMI registriert
 Realisierung eines Client-Programms, welches auf das Server-Objekt zugreift und
dieses verwendet
 Starten
 Starten des rmiregistry (ab 1.5 nicht mehr nötig)
 Starten des Server-Programms
 Starten des Client-Programms
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
15
Beispiel Bank-Server: Interface Bank
Interface Bank definiert Methoden für Zugriffe auf Konten
package bank.common;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Bank extends Remote {
public static final int PORT = 1099;
public long getBalance(int account)
throws RemoteException;
RemoteExceptions für alle
Remote-Methods notwendig!
public void deposit(int account, long amount)
throws RemoteException;
public void transfer(int from, int to, long amount)
throws RemoteException;
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
16
Beispiel Bank-Server: BankImpl
Klasse BankImpl stellt eine entsprechende Implementierung von Bank bereit
package bank.server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import bank.common.Bank;
Constructor mit
RemoteException!
public class BankImpl extends UnicastRemoteObject implements Bank {
private long[] accounts = new long[100];
protected BankImpl() throws RemoteException { super(); }
public synchronized long getBalance(int account) throws RemoteException {
return accounts[account];
}
public synchronized void deposit(int account, long amount)
throws RemoteException {
accounts[account] += amount;
}
public synchronized void transfer(int from, int to, long amount)
throws RemoteException {
accounts[from] -= amount;
accounts[to] += amount;
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
17
Beispiel Bank-Server: Stub und Skeleton
Stub- und Skeleton-Klassen müssen nicht
geschrieben werden
Sie können durch das Programm
rmic
automatisch generiert werden
rmic wird mit ServerImplementierungsklasse aufgerufen
Microsoft Windows XP
rmic <options> <class names>
where <options> includes:
-keep Do not delete intermediate generated source files
-g Generate debugging info
-depend Recompile out-of-date files recursively
-nowarn Generate no warnings
-verbose Output messages about what the compiler is doing
-classpath <path> Specify where to find input source and
class files
-d <directory> Specify where to place generated class
files
-J<runtime flag> Pass argument to the java interpreter
-v1.1 Create stubs/skeletons for JDK 1.1 stub protocol
version
-vcompat (default) Create stubs/skeletons compatible with
both JDK 1.1 and Java 2 stub protocol versions
-v1.2 Create stubs for Java 2 stub protocol version only
> rmic –keep BankImpl
public final class BankImpl_Stub
extends java.rmi.server.RemoteStub
implements Bank, java.rmi.Remote
{
...
public final class BankImpl_Skel
implements java.rmi.server.Skeleton
{
...
}
}
Achtung: kann ab Java 1.5 gänzlich entfallen!
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
18
Beispiel Bank-Server: Server-Programm
Server-Programm hat folgende Aufgaben:
 erzeugen des Server-Objekts
 exportieren des Server-Objekts an das RMI
 Registrieren des Server-Objekts unter einem RMI-URL mit Registry
package bank.server;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import bank.common.Bank;
public class BankServer {
public BankServer() {
try {
Bank bank = new BankImpl();
Registry reg = LocateRegistry.createRegistry(Bank.PORT);
reg.bind("Bank", bank);
} catch (Exception e) {
System.out.println("Trouble: " + e);
}
}
public static void main(String args[]) {
new BankServer();
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
19
Beispiel Bank-Server: Client-Programm
Client-Programm wird:
 sich über den Registry mit dem URL das Remote-Objekt holen
 das Remote-Objekt verwenden (dabei direkt den Stub aufrufen)
 eine Reihe von Exceptions abfangen
package bank.client;
import java.rmi.*;
import bank.common.Bank;
public class BankClient {
Name des Servers,
z.B. "localhost"
public static void main(String[] args) {
try {
Registry reg = LocateRegistry.getRegistry("…Server Adr…", Bank.PORT);
Bank bank = (Bank) reg.lookup("Bank");
bank.deposit(1, 10000);
bank.deposit(2, 20000);
bank.transfer(1, 2, 3000);
System.out.println(bank.getBalance(1));
System.out.println(bank.getBalance(2));
} catch (Exception re) {
…
}
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
20
Exportieren von Server-Objekten
Server-Objekte müssen bei ihrer Erzeugung an das RMI exportiert (bekannt
gemacht) werden
Dies kann auf zwei Arten passieren
 Ableiten von Klasse UnicastRemoteObject (wie im letzten Beispiel)
 mit static-Methode exportObject von Klasse UnicastRemoteObject
static RemoteStub exportObject(Remote obj) throws RemoteException
static Remote exportObject(Remote obj, int port) throws RemoteException
Port 0 bedeutet, dass Port vom
System gewählt wird.
public class BankImpl implements Bank { ... }
public class BankServer {
public BankServer() {
try {
Bank bank = new BankImpl();
Registry reg = LocateRegistry.createRegistry(PORT);
RemoteStub bstub = UnicastRemoteObject.export(bank);
reg.bind("Bank", bstub);
} catch (Exception e) {
System.out.println("Trouble: " + e);
}
...
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
21
rmiregistry und Naming (alte Version)
Starten des RMIRegistry-Programms
Microsoft Windows XP
> rmiregistry
Server:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class BankServer {
public BankServer() {
try {
Bank bank = new BankImpl();
Naming.rebind("rmi://localhost:1099/„Bank", bank);
...
Client:
public class BankClient {
public static void main(String[] args) {
try {
Bank bank = (Bank) Naming.lookup(
"rmi://localhost:1099/"Bank");
...
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
22
RMI
Exkurs: Dynamic Proxy
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
24
Exkurs: Dynamic Proxy
package java.lang.reflect
dynamisch erzeugter Proxy implementiert Liste von Interfaces
wird durch statischer Methode Proxy.newProxyInstance erzeugt
public class Proxy {
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException;
Implementierte
Interfaces
InvocationHandler
…
}
Anwendung:
Foo f = (Foo)Proxy.newProxyInstance(null, new Class[] {Foo.class}, handler);
Erzeugtes Proxy-Objekt implementiert Interfaces, ruft aber InvocationHandler auf
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
}
InvocationHandler soll Indirektion realisieren
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
Reflection API
25
Beispiel Dynamic Proxy: TraceHandler (1/2)
TraceHandler realisiert InvocationHandler
 zuerst Trace-Ausgabe
 dann Aufruf der eigentlichen Methode
class TraceHandler implements InvocationHandler {
private Object target;
private PrintStream traceLog; public TraceHandler(Object target, PrintStream traceLog) { this.target = target;
this.traceLog = traceLog; }
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { traceLog.print(target + "." + m.getName() + "(");
if (args != null) {
Ausgabe der
for (int i = 0; i < args.length; i++) { Traceinfo
traceLog.print(args[i]);
if (i < args.length ‐ 1)
traceLog.print(", ");
}
}
traceLog.println(")");
Aufruf der eigentlichen Methode
return m.invoke(target, args);
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
26
Beispiel Dynamic Proxy: TraceHandler (2/2)
Test mit Proxy für Integer-Werte
mit Trace der compareTo-Methode des Comparable-Interfaces
public class ProxyTest {
}
public static void main(String[] args) { Object[] elements = new Object[1000];
// fill elements with proxies for the integers 1 ... 1000 { Comparable.class }
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
Class[] interfaces = value.getClass().getInterfaces();
InvocationHandler handler = new TraceHandler(value, System.out);
Object proxy = Proxy.newProxyInstance(null, interfaces, handler);
elements[i] = proxy;
}
500.compareTo(547)
750.compareTo(547)
Integer key = 547; 625.compareTo(547)
// search for the key
562.compareTo(547)
int result = Arrays.binarySearch(elements, key);
531.compareTo(547)
546.compareTo(547)
// print match if found
554.compareTo(547)
if (result >= 0) 550.compareTo(547)
System.out.println(elements[result]);
548.compareTo(547)
}
547.compareTo(547)
..
547
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
27
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
28
Parameterübergabe und Rückgabewerte
Bei RMI sind folgende Arten der Parameterübergabe und Rückgabewerte
zu unterscheiden:
 Basisdatentypen (int, double, boolean, etc.)
 Werte werden über das Netzwerk übertragen
 Serialisierbare Objekte (implementieren Interface Serializable)
 Objekte werden serialisiert über das Netzwerk übertragen
und auf der anderen Seite eine Kopie aufgebaut
 Remote-Objekte (implementieren Interface Remote)
 Es wird auf der empfangenden Seite ein Stub für das Objekt aufgebaut
 alle anderen (nicht serialisierbar, nicht Remote)
 diese können nicht als Parameter übergeben werden
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
29
Basisdatentypen
 Auf Senderseite werden Werte „gemarshaled“ und über das
Netzwerk gesendet
 Auf der Empfängerseite werden die Werte wieder entpackt und an
Client geliefert
client
server_skeleton
server_stub
getValue()
getValue()
4711
4711
JVM 1
Pratikum SWE 2
server
4711
JVM 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
30
Serialisierbare Objekte
 Inhalt des Objekts wird über den Java-Serialisierungsmechanismus
serialisiert und über das Netzwerk übertragen
 auf der Empfängerseite wird ein neues Objekt mit gleichem Inhalt
aufgebaut
 Problem, wenn Klasse des serialisierten Objekts auf Empfängerseite
nicht bekannt ist!
 siehe Nachladen von Code
client
server_skeleton
server_stub
server
getList()
getList()
ArrayList (id = 37)
ArrayList (id = 14)
ArrrayList
<1, 2, 3, …>
JVM 1
Pratikum SWE 2
JVM 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
31
Remote-Objekte
 Es wird kein Wert übergeben sondern
 über das RMI-System wird eine Remote-Referenz auf das ServerObjekt übermittelt
 auf der Clientseite wird dafür ein Stub erzeugt und eine Referenz
auf den Stub dem Empfänger übergeben
client
server_skeleton
server_stub
server
getRemoteMember()
getRemoteMember()
create()
remote
remote
remote_stub
remote_stub
Remote-Reference to
remote
JVM 1
Pratikum SWE 2
JVM 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
32
Beispiel: Car und CarStore
Remote-Interfaces Car und CarStore
public interface Car extends Remote {
public int getNr() throws RemoteException;
}
public interface CarStore extends Remote {
public Car getCar(int nr) throws RemoteException;
public void returnCar(Car car) throws RemoteException;
}
Implementierungen CarImpl und CarStoreImpl
public class CarImpl extends UnicastRemoteObject implements Car {
private final int nr;
protected CarImpl(int nr) throws RemoteException {
this.nr = nr;
}
public int getNr() throws RemoteException { return nr; }
}
public class CarStoreImpl extends UnicastRemoteObject implements CarStore {
private final Car[] cars = new Car[3];
public CarStoreImpl() throws RemoteException {...}
public synchronized Car getCar(int nr) throws RemoteException {
return cars[nr];
}
public synchronized void returnCar(Car car) throws RemoteException { ... }
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
33
Beispiel: Car und CarStore
CarStoreServer Applikation
public class CarStoreServer {
private static CarStore store;
public static void main(String[] args) throws Exception {
store = new CarStoreImpl();
store.cars[0] = new CarImpl(0);
store.cars[1] = new CarImpl(1);
store.cars[2] = new CarImpl(2);
Registry reg = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
reg.bind("CarStore", store);
}
CarStoreClient
public class CarStoreClient {
public static void main(String[] args) {
try {
Registry reg = LocateRegistry.getRegistry(Registry.REGISTRY_PORT);
CarStore cs = (CarStore) reg.lookup("CarStore");
Car c1a = cs.getCar(1);
...
Remote-Objekt
} catch (Exception e) { ... }
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
34
== und equals bei Remote-Referenzen (1/2)
== bei Remote-Referenzen
 bei jedem Remote-Zugriff werden neue Stub-Objekte angelegt
 Objekte sind nicht ==
public class CarStoreClient {
public static void main(String[] args) {
try {
...
Car c1a = cs.getCar(1);
Car c1b = cs.getCar(1);
false !!!
if (c1a == c1b) ...
equals
• Stub implementiert equals entsprechend
 Objekte sind equals, wenn selbes Server-Objekt
public class CarStoreClient {
public static void main(String[] args) {
try {
...
Car c1a = cs.getCar(1);
Car c1b = cs.getCar(1);
true !!!
if (c1a.equals(c1b)) ...
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
35
== und equals bei Remote-Referenzen (2/2)
Car implementiert equals
public class CarImpl extends UnicastRemoteObject implements Car {
...
public synchronized boolean equals(Object obj) {
if (obj instanceof CarImpl) {
return nr == ((CarImpl) obj).nr;
}
return false;
}
}
Server CarStore erzeugt immer neue CarImpl-Objekte
public class CarStoreImpl extends UnicastRemoteObject implements CarStore{
...
public synchronized Car getCar(int nr) throws RemoteException {
return new CarImpl(nr);
}
}
neue Car-Objekt mit new erzeugt
Diese Objekte werden am Client nicht als equals erkannt
public class Client {
public static void main(String[] args) throws RemoteException … {
Registry reg = LocateRegistry.getRegistry(2222);
CarStore cs = (CarStore) reg.lookup("CarStore");
Car c1a = cs.getCar(1);
Car c1b = cs.getCar(1);
if (c1a.equals(c1b)) …
Pratikum SWE 2
false !!!
© Institut für Systemsoftware, Johannes Kepler Universität Linz
36
Remote-Referenz als Parameter
Remote-Referenz als Parameter von Remote-Methode
 Client ruft Remote-Methode mit Remote-Referenz auf
 am Server wird ein Stub für das Remote-Stub am Client erzeugt
 Verbindung zum ursprünglichen Remote-Objekt am Server nicht vorhanden!!
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
37
Beispiel: Car und CarStore
Client ruft returnCar mit Remote-Referenz auf
public class Client {
public static void main(String[] args) throws RemoteException … {
Registry reg = LocateRegistry.getRegistry(2222);
CarStore cs = (CarStore) reg.lookup("CarStore");
Car c1a = cs.getCar(1);
cs.returnCar(c1a);
Remote-Stub !!
Am Server wird Stub aufgebaut
public class CarStoreImpl extends UnicastRemoteObject implements CarStore {
public synchronized Car getCar(int nr) throws RemoteException {
return cars[nr];
}
public v synchronized oid returnCar(Car car) throws RemoteException {
if (car.equals(cars[car.getNr()])) ...
}
}
false !!
Keine Remote-Stubs als Parameter übergeben !
Besser mit IDs, etc. arbeiten !
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
38
Erweitertes Beispiel BankManager
BankManager ist ein Remote-Objekt, das Zugriff auf Kunden
(Customer) und Konten (Accounts) bietet
Sowohl Account als auch Customer sind Remote-Objekte
BankManager bietet den Einstiegspunkte und wird als einziges Objekt
registiert
Vom BankManager holt sich das Client-Programm die weiteren RemoteObjekte
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
39
Beispiel BankManager: Remote Interfaces
public interface Account extends Remote {
public
public
public
public
public
int getId() throws RemoteException;
Customer getCustomer() throws RemoteException;
long getBalance() throws RemoteException;
long withdraw(long amount) throws RemoteException;
void deposit(long diff) throws RemoteException;
}
public interface Customer extends Remote {
public String getName() throws RemoteException;
public Account[] getAccounts() throws RemoteException;
}
public interface BankManager extends Remote {
public Customer getCustomer(String name) throws RemoteException;
public Customer createCustomer(String name) throws RemoteException;
public Account getAccount(int accountNumber) throws RemoteException;
public Account createAccount(String customer) throws RemoteException;
public void transfer(int from, int to, long amount) throws RemoteException;
}
Remote-Objekte
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
40
Beispiel BankManager: BankManagerImpl
BankManager verwaltet Mengen von Kunden und Konten
BankManager erlaubt Zugriff auf Konto und Kunde
public class BankManagerImpl extends UnicastRemoteObject implements BankManager {
private static BankManagerImpl instance;
public synchronized static BankManager getInstance() {
if (instance == null) {
try { instance = new BankManagerImpl();
} catch (RemoteException e) { }
}
return instance;
}
private static int id = 0;
private final Map<Integer, Account> accounts;
private final Map<String, Customer> customers;
private BankManagerImpl() throws RemoteException {
accounts = new HashMap<Integer, Account>();
customers = new HashMap<String, Customer>();
}
@Override
public synchronized Customer getCustomer(String name) throws RemoteException {
return customers.get(name);
}
...
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
41
Beispiel BankManager: BankManagerImpl
...
@Override
public synchronized Customer createCustomer(String name) throws RemoteException {
CustomerImpl c = new CustomerImpl(name, this);
customers.put(name, c);
return customers.get(name);
}
@Override
public synchronized Account getAccount(int id) throws RemoteException {
return accounts.get(id);
}
@Override
public synchronized Account createAccount(String customer) throws RemoteException {
id++;
Customer customer = customers.get(customer);
Account a = new AccountImpl(id, this, customer);
accounts.put(id, a);
customer.accounts.add(a);
return a;
}
@Override
public synchronized void transfer(int from, int to, long amount) throws RemoteException {
Account aFrom = getAccount(from);
Account aTo = getAccount(to);
aFrom.withdraw(amount);
aTo.deposit(amount);
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
42
Beispiel BankManager: CustomerImpl
public class CustomerImpl extends UnicastRemoteObject implements Customer {
private final String name;
final List<Account> accounts;
protected CustomerImpl(String name) throws RemoteException {
super();
this.name = name;
this.accounts = new ArrayList<Account>();
}
@Override
public String getName() throws RemoteException {
return name;
}
@Override
public synchronized Account[] getAccounts() throws RemoteException {
return accounts.toArray(new Account[0]);
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
43
Beispiel BankManager: AccountImpl
public class AccountImpl extends UnicastRemoteObject implements Account {
private final int id;
private final String customer;
private long balance;
public AccountImpl(int id, String customer) throws RemoteException {
super();
this.id = id;
this.customer = customer;
balance = 0L;
listeners = new LinkedList<AccountListener>();
}
@Override
public int getId() throws RemoteException {
return id;
}
@Override
public Customer getCustomer() throws RemoteException {
return BankManager.getInstance().getCustomer(customer);
}
...
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
44
Beispiel BankManager: AccountImpl
...
@Override
public synchronized long getBalance() throws RemoteException {
return balance;
}
@Override
public long withdraw(long diff) throws RemoteException {
synchronized (this) {
balance = balance - diff;
}
fireAccountChangedEvent();
return diff;
}
@Override
public void deposit(long diff) throws RemoteException {
synchronized (this) {
balance = balance + diff;
}
fireAccountChangedEvent();
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
45
Beispiel BankManager: Server-Programm
Server-Programm registriert BankManager als einziges Objekt
public class BankServer {
private static BankManager bank;
private static void start() throws Exception {
bank = BankManagerImpl.getInstance();
Registry reg = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
reg.bind("Bank", bank);
System.out.println("Server started on port " + Registry.REGISTRY_PORT);
}
public static void main(String[] args) throws Exception {
start();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
do {
System.out.println("Press x for exit.");
} while (!"x".equals(in.readLine()));
System.exit(0);
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
46
Beipiel BankManager: Client-Programm
Client-Programm holt sich BankManager
greift damit auf Account-Objekte und Customer-Objekte zu
public class BankClient {
static final String serverIP = "localHost";
static final int serverPort = Registry.REGISTRY_PORT;
static private BankManager bank;
public static void main(String[] args) {
try {
Registry reg = LocateRegistry.getRegistry(serverIP, serverPort);
bank = (BankManager)reg.lookup("Bank");
Customer c1 = bank.createCustomer("Hans");
Customer c2 = bank.createCustomer("Franz");
Account a1 = bank.createAccount(c1);
Account a2 = bank.createAccount(c2);
Account a1 = c1.getAccounts()[0];
Account a2 = c2.getAccounts()[1];
a1.deposit(1000);
a2.deposit(2000);
bank.transfer(a1.getId(), a2.getId(), 100);
} catch (Exception exc) {
exc.printStackTrace();
}
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
47
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
48
Callbacks
In machen Fällen ist es notwendig, dass der Server dem Client eine Meldung
schickt.
 z.B. Benachrichtung über Änderungen
In diesem Fall sind die Rolle von Client und Server zu vertauschen
 am Client muss ein Remote-Objekt existieren
 eine Remote-Referenz wird dem Server übergeben
 am Server wird ein Stub für das Remote-Client-Objekt angelegt
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
49
Beispiel BankManager: Remote Change Notification
Remote-Listener-Interface für Client
public interface RemoteAccountListener extends Remote {
public void accountChanged(RemoteAccountEvent e) throws RemoteException;
}
Account wird um Remote-Methode addAccountListener erweitert
public interface Account extends Remote {
public void addRemoteAccountListener(RemoteAccountListener l)
throws RemoteException;
public void removeRemoteAccountListener(RemoteAccountListener l)
throws RemoteException;}
public class AccountImpl extends UnicastRemoteObject implements Account {
…
@Override
public void addAccountListener(AccountListener l) throws RemoteException {
synchronized (listeners) {
listeners.add(l);
}
}
@Override
public void removeAccountListener(AccountListener l) throws RemoteException
… }
private void fireAccountChanged() { … }
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
50
Beispiel BankManager: Remote Change Notification
Der Client muss RemoteAccountListener implementieren und anmelden
public class BankClient {
public BankClient(BankManager bm) {
super();
this.bm = bm;
// add client as account listener at the bank manager
Account a = bm.getAccount(1);
a.addRemoteAccountListener(accountWatcher);
…
Nicht vergessen: Abmelden am Ende!
(siehe Distributed Garbage Collection)
a.removeRemoteAccountListener(accoutWatcher);
}
private AccountWatcher accountWatcher = new AccountWatcher();
private static class AccountWatcher
extends UnicastRemoteObject implements AccountListener {
public AccountWatcher() throws RemoteException {
super();
}
public void accountChanged(AccountChangedEvent evt)
throws RemoteException {
…
}
};
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
51
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
52
RMI und Threads
Jeder Request wird in eigenem Thread ausgeführt
client 1
Server
client 2
Thread
request1()
request2()
request3()
Synchronisation notwendig
 Synchronisation der Zugriffe von mehreren Clients
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
53
Synchronisation
Z.B. durch Synchronisation der Remote Methods
public class BankManagerImpl extends UnicastRemoteObject implements BankManager {
...
@Override
public synchronized Customer getCustomer(String name) throws RemoteException {
return customers.get(name);
}
@Override
public synchronized Customer createCustomer(String name) throws RemoteException { … }
@Override
public synchronized Account getAccount(int id) throws RemoteException { … }
@Override
public synchronized Account createAccount(String c) throws RemoteException { … }
...
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
54
Probleme: Lang laufenden Methoden
Lang laufender synchronisierter Code blockiert alle ClientRequests
public class BankManagerImpl extends UnicastRemoteObject implements BankManager {
...
public synchronized void makeAnnualBalance() {
// long lasting activity
…
}
...
}
Lang laufender Prozess blockiert
alle Client-Requests
Lösung:
 lang-laufende Request durch den Client im eigenem Thread
asynchron ausführen !
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
55
Probleme: Reentrancy
Java Locks sind reentrant
d.h. ein Thread, der den Lock auf ein Objekt hat, kann Code der auf
gleiches Objekt gelockt ist trotzdem ausführen
Mit Lock auf BankManagerImpl
public class BankManagerImpl extends UnicastRemoteObject implements BankManager {
...
@Override
public synchronized void transfer(int from, int to, long amount) throws RemoteException {
Account aFrom = getAccount(from);
Mit Lock auf BankManagerImpl 
Account aTo = getAccount(to);
reentrant!
…
}
@Override
public synchronized Account getAccount(int id) throws RemoteException { … }
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
56
Probleme: Reentrancy
Werden Methoden aber Remote mit Zyklus über Client (Callback)
ausgeführt, gilt das nicht mehr, weil unterschiedliche Threads
Server
client 1
request1()
synchronized(this)
callback()
Blockiert synchron bis
Callback zurückkehrt
request2()
synchronized(this)
Deadlock, weil
unterschiedliche Threads
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
57
Probleme: Reentrancy
Beispiel
public class BankClient extends UnicastRemoteObject {
private static class AccountWatcher
extends UnicastRemoteObject implements AccountListener {
public void accountChanged(AccountChangedEvent evt)
throws RemoteException {
bankManager.getAccount()...
}
};
public class BankManagerImpl extends UnicastRemoteObject
Thread 1
...
@Override
public synchronized void transfer(int from, int to, long am
Account aFrom = getAccount(from);
Account aTo = getAccount(to);
aTo.fireAccountChanged();
}
@Override
public synchronized Account getAccount(int id) throws Remot
Thread 2
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
58
Probleme: Reentrancy
Lösung: Asynchrone Callbacks
Callbacks asynchron ausgeführt erlaubt, dass Server-Methode zu Ende läuft
public class AccountImpl extends UnicastRemoteObject implements Account {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
...
@Override
public void deposit(long diff) throws RemoteException {
synchronized (this) {
balance = balance + diff;
fireAccountChangedEvent();
Methode wartet damit nicht mehr,
}
}
dass Callbacks zurückkehren
private void fireAccountChanged() {
final AccountChangedEvent evt = new AccoutChangedEvent(…);
for (final AccountListener l : listeners) {
executor.submit(() -> {
l.accountChanged(evt);
});
Erzeugen und absetzen
}
}
von asynchronen Callbacks
…
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
59
Probleme: Reentrancy
Lösung: Asynchrone Callbacks
client
Server
request1()
synchronized(this)
callback()
request2()
Pratikum SWE 2
synchronized(this)
© Institut für Systemsoftware, Johannes Kepler Universität Linz
Asynchron,
nicht blockierend
nicht synchronized(this)
60
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
61
Distributed Garbage Collection
Garbage Collector gibt Objekte frei, wenn sie nicht mehr referenziert werden
Bei Remote-Objekten ist es dem Garbage Collector aber nicht möglich zu
entscheiden, ob Remote-Clients das Objekt noch referenzieren
Distributed Garbage Collector arbeitet mit:
 Reference Counting: Es werden die Zugriffe von Remote-Clients auf die Remote-Objekte
gezählt
 Lease Time: Greift ein Client eine bestimmte Zeit – der Lease Time – nicht auf das
Remote-Objekt zu, wird angenommen, dass der Client das Objekt nicht mehr benötigt.
Konsequenz: Beim Client ist es möglich, dass Remote-Objekte verschwinden
und er mit ungültigen Remote-Referenzen umgehen können muss.
Lease Time:
 System Property java.rmi.dgc.leaseValue
 Standardwert 10 min
 Einstellung als Option bei java: java –Djava.rmi.dgc.leaseValue=20000
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
62
RMI
Motivation
Grundarchitektur
Implementierung von Remote-Objekten
Parameterübergabe
Callbacks
RMI und Threads
Distributed Garbage Collection
Verteilung und Nachladen von Code
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
63
Verteilung und Nachladen von Klassen
Bei Zugriff auf Remote-Objekte ist es nötig, dass Code vom Server
nachgeladen wird
 für Stubs
 für Parameter und Rückgabewerte
Beispiel: Stub
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
64
Motivation Nachladen von Klassen
public interface BankManager extends Remote {
public Customer getCustomer(String customerName) throws RemoteException;
}
Customer
PrivateCustomer
BusinessCustomer
Es existieren Spezialisierungen
von Customer! Diese sind Client
eventuell nicht bekannt!
public class BankManagerImpl extends UnicastRemoteObject implements BankManager {
public synchronized Customer getCustomer(String customerName) throws RemoteException {
if (…) return privateClients.get(customerName);
else return businessClients.get(customerName);
}
Client: Müsste alle Spezialisierungen von Customer kennen
Konretes Objekt vom Typ PrivateCustomer_Stub oder BusinessCustomer_Stub
try {
Customer customer = account.getCustomer(name);
} catch (RemoteException remoteException) {
System.err.println(remoteException);
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
65
Nachladen von Code
Um Code von einer externen Site nachladen zu können, ist folgendes zu tun
 es muss beim Client ein RMISecurityManager installiert sein (sonst kann kein externer
Code geladen werden)
 Es müssen Permissions für den Client gesetzt werden:
 Socketverbindung zum Server für RMI über Port 1099
 Socketverbindung zum Nachladen von Code hier über Webserver, d.h. Port 80 (oder
8080)
Beispiel:
 Clientprogramm:
public class MyClient {
public static void main(String[] args) {
System.setSecurityManager(new RMISecurityManager());
System.setProperty("java.security.policy", "client.policy");
...
 Policy-Datei für Client:
grant {
permission java.net.SocketPermission “server-url:1024-65535“, “connect“;
permission java.net.SocketPermission “server-url:80“, “connect“;
permission java.net.SocketPermission “server-url:8080“, “connect“;
}
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
66
Verteilung des Klassencodes
Die Klassen sollen zur Verteilung auf 3 Teile aufgeteilt werden:
 server:
 alle Klassendateien, die für die Ausführung des Servers erforderlich sind
 alle Stub-Klassen
 download:
 alle Klassendateien, die dynamisch vom Client nachgeladen werden sollen
 inklusive alle abhängigen (z.B. Basisklassen und Interfaces)
 client:
 alle Klassendateien, die unmittelbar vom Client verwendet werden
 die Policy-Datei (z.B. client.policy)
Die 3 Teile werden dann folgend verteilt:
 server:
 auf dem Rechner des Servers
 download:
 bei Verwendung eines Webservers ins download-Verzeichnis des Webserver
 client:
 auf den Rechern des Client
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
67
Starten der verteilten Programme
Am Server
 Starten des RMIRegistry-Programms (oder Verwendung von Registry-Klasse)
Microsoft Windows XP
> rmiregistry
 Starten des Server-Programms mit Angabe des Download-Verzeichnisses als Codebase
> java –Djava.rmi.server.codebase=http://localhost/download/
MyServerApp
Am Client
 Starten des Client-Programms, hier zusätzlich unter Angabe einer Policy-Datei
> java –Djava.security.policy=client.policy MyClientApp
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
68
Literatur
Horstmann, Cornell, Core Java 2, Band 2 Expertenwissen, Markt und Technik,
2002: Kapitel 5
Krüger, Handbuch der Java-Programmierung, 3. Auflage, Addison-Wesley, 2003,
http://www.javabuch.de: Kapitel 46
Fundamentals of RMI, Short Course,
http://java.sun.com/developer/onlineTraining/rmi/
Pratikum SWE 2
© Institut für Systemsoftware, Johannes Kepler Universität Linz
69
Herunterladen