Come visto in precedenza, RMI permette l’invocazione reciproca di metodi tra due oggetti remoti “client” e “server” attraverso la tecnica del callback.
Un uso tipico di questa funzionalità è quello di permettere al server di inviare ai client delle notifiche (quando necessario, in base alla logica applicativa), mentre i client possono continuare normalmente a richiedere servizi al server.
Un class diagram che illustra un esempio di tale sitruzione è il seguente, del tutto analogo a quelli già mostrati negli esempi di callback:
Anche il sequence diagram che mostra un esempio di comportamento del sistema è uguale al solito, per quanto riguarda la parte di inizializzazione (nella quale il server e client acquisiscono l’uno il riferimento remoto dell’altro), ma la “novità” è ciò che accade dopo: periodicamente, in un ciclo infinito (nel caso di questo esempio), il server invia una notifica al client, con la quale comunica (a scopo illustrativo) il numero di richieste di servizio che ha ricevuto fino a quel momento.
Il codice che definisce l’interfaccia remota del server è:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Server extends Remote {
public String toUpperCase(String s) throws RemoteException;
public void addListener(RemoteListener l) throws RemoteException;
public void removeListener(RemoteListener l) throws RemoteException;
}
toUpperCase
corrispondente al servizio “vero e proprio” offerto dal server (qui è un semplice servizio che riceve una stringa e restituisce la stessa stringa con tutti i caratteri resi maiuscoli)addListener
permette a un client di registrarsi presso il server, inviando il proprio riferimento remoto, al fine di ricevere notifiche dal serverremoveListener
chiede al server di “dimenticare” il riferimento rermoto di un client, e quindi di non inviare più notifiche a quest’ultimoL’interfaccia remota del client prevede un singolo metodo, remoteEvent
, che il server chiama per inviare una notifica al client, tipicamente al fine di comunicare che è avvenuto un qualche evento:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RemoteListener extends Remote {
public void remoteEvent(Object param) throws RemoteException;
}
il parametro di remoteEvent
è di tipo Object
, cioè può contenere qualunque oggetto: perciò, questo singolo metodo potrebbe essere sufficiente per passare qualunque informazione al client (ma, normalmente, si preferisce usare più metodi specifici).
Il server mantiene una lista di riferimenti remoti ai client registrati (listeners
) e un conteggio del numero di chiamate del metodo toUpperCase
, il cui valore viene periodicamente inviato (mediante l’invocazione remota del metodo remoteEvent
) a tutti i client registrati:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;
public class ServerImpl extends UnicastRemoteObject implements Server {
private final List<RemoteListener> listeners = new ArrayList<>();
private int numCalls = 0;
public ServerImpl() throws RemoteException {}
public String toUpperCase(String s) {
synchronized (this) { numCalls++; }
return s.toUpperCase();
}
public synchronized void addListener(RemoteListener l) {
listeners.add(l);
}
public synchronized void removeListener(RemoteListener l) {
listeners.remove(l);
}
private synchronized void notifyListeners() {
Iterator<RemoteListener> iter = listeners.iterator();
while (iter.hasNext()) {
try {
RemoteListener l = iter.next();
l.remoteEvent(numCalls);
} catch (RemoteException e) {
// Il client si è probabilmente disconnesso
iter.remove();
}
}
}
public static void main(String[] args) throws InterruptedException, RemoteException {
ServerImpl server = new ServerImpl();
System.out.println("Registering...");
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("server", server);
System.out.println("Registered");
while (true) {
server.notifyListeners();
Thread.sleep(500);
}
}
}