Invio di notifiche ai client

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:

Untitled

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.

Untitled

Interfaccia del server

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;
}

Interfaccia del client

L’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).

Implementazione del server

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);
		}
	}
}