Dopo aver visto l'uso dell'RMI, adesso è giunto il momento di complicarci un pò la vita ed introdurre il meccanismo della callback. Se un client fa una nuova offerta, gli atri client non saranno a conoscenza di questo fatto e non potranno rilanciare.
Per implementare questa funzione il server dovrà disporre di un sistema per notificare a tutti client che il prezzo è cambiato. Per questo motivo i client esporranno un metodo richiamabile da remoto dal server quando il prezzo dell'oggetto cambia, in questo caso, il server agirà da client mentre i client agiranno da server. Affinchè il server sappia quali client avvisare, quest'ultimi durante la fase di connessione dovranno registrarsi sul server richiamando esplicitamente un metodo registerForNotify(). Alla chiamata di questo metodo il server conserverà da qualche parte (possibilmente un vettore) il riferimento al client in modo da avvisarlo nel caso di una nuova offerta. Supponendo di usare un vettore per memorizzare i riferimenti ai client che si sono registrati per le notifiche, al cambio del prezzo dell'oggetto il server ciclerà questo vettore e richiamerà da remoto su ogni client il metodo che espongono tramite l'interfaccia.
Continuando dal codice visto in precedenza, vedremo come modificarlo in modo da inserire la callback. Partiamo quindi dall'interfaccia del server.
Al file AuctionInterface.java, aggiungiamo il metodo:
public void registerForNotification(Notifiable n) throws RemoteException;
che verrà richiamato dai client quando vorranno ricevere delle notifiche dal server. L'oggetto di tipo Notifiable, si riferisce all'interfaccia del client: sarà esso stesso a registrarsi sul server tramite la propria interfaccia.
Continuiamo con l'implementazione del server, modifichiamo il file Auctioneer.java:
La prima cosa da fare è di aggiungere un vettore in cui memorizzare i riferimenti ai client, aggiungiamo quindi all'inizio della classe (dopo 'private String product;'):
private Vector clientList = null;
Il vettore deve anche essere istanziato, nel costruttore (public Auctioneer()) aggiungiamo dopo la riga super();
clientList = new Vector(); //creo l'istanza del vettore
Aggiungiamo poi il metodo per far registrare i client:
public void registerForNotification(Notifiable n) throws RemoteException { System.out.println("Un client ha richiesto la notifica..."); clientList.addElement(n); //aggiungo il riferimento 'n' al client nel vettore }
Faremo si, che il client esponga tramite la propria interfaccia (Notifiable) un metodo notify che verrà chiamato da remoto dal server per notificare un evento (una nuova offerta). Richiameremo il metodo remoto notify, quando verrà proposta ed accettata una nuova offerta, cioè all'interno del metodo setBid(). Aggiungiamo quindi all'internopublic class Auction di questo metodo (dopo la riga System.out.println("Offerta Accettata...");) il seguente codice:
for(Enumeration clients = clientList.elements(); clients.hasMoreElements();) { //ciclo sul Vettore Notifiable aClient = (Notifiable) clients.nextElement(); //prendo il riferimento al client aClient.notify(0); //richiamo il metodo remoto, lo 0 è inutile... System.out.println("Ho notificato l'evento ad un client"); }
Si sottolinea il fatto che al client si notifica l'evento e non il nuovo prezzo. Sarà, quindi, compito del client leggere il nuovo prezzo alla chiamata del metodo notify().
Passiamo quindi al client. Per prima cosa scrivere la sua interfaccia Notifiable. Creiamo un nuovo file Notifiable.java e scriviamo:
import java.rmi.*;
public interface Notifiable extends Remote { public void notify(int aNumber) throws RemoteException; //il metodo che il server può richiamare da remoto, l'intero serve ad evitare di sovrascrivere //il metodo notify() di object }
Passiamo quindi all'implentazione del client. Modifichiamo il file Auction.java come segue:
La classe Auction implementa l'interfaccia Notifiable, quindi modifichiamo la riga public class Auction come segue
public class Auction implements Notifiable
Aggiungiamo al costruttore (public Auction()) il seguente codice:
Questo codice serve ad esportare il/i metodi remoti del client sul registry, visto che si è già instaurata una connessione con il server. In questo modo il client non dovrà registrare esplicitamente i propri metodi remoti sul registry. Inseriamo il codice per registrarci sul server nel main, dopo la riga auc.auctioneer = (AuctionInterface) Naming.lookup(serverName); //cerco il server sul registry :
auc.auctioneer.registerForNotification(auc); //mi registro sul server
Per finire aggiungiamo il metodo notify:
public void notify(int aNumber) { //il prezzo è cambiato System.out.println("The Server notify a new bid...");
try { currentBid = auctioneer.getCurrentBid(); //leggo qual è il nuovo prezzo System.out.println("New Current Bid is " + currentBid + "\n"); //e lo stampo ... } catch (RemoteException ex) { ex.printStackTrace(); } }
Per compilare ed eseguire il nuovo esempio dobbiamo procedere come segue(da un terminale):
compilare il server: javac Auctioneer.java
compilare il client: javac Auction.java
creare lo stub del client: rmic Auction
eseguire il registry: rmiregistry
eseguire il server: java Auctioneer nome_prodotto prezzo_iniziale
eseguire il/i client (sullo stesso computer): java Auction localhost
Note:
dalla versione 1.5.0_06 della Jre non è necessario usare rmic per creare Stub e Skeleton del server.
nel caso in cui dovessimo avere errori del tipo (ClassNotFoundException) si può specificare il classpath. Basta aggiungere alla fine delle righe 1, 2, 5 e 6 questo -classpath ./ (per Linux) , -classpath .\ (per Windows)
per il deployement dell'applicazione ricordo che per il server avremo bisogno dei file Auctioneer.class, AuctionInterface.class, Auction_stub.class e Notifiable.class , mentre per il client avremo bisogno di Auction.class, AuctionInterface.class, Auction_stub.class e Notifiable.class.