Lo scopo di questo tutorial è quello di creare un'asta con un server ed un certo numero di client. Il server si occuperà di gestire l'asta mentre i client rappresenteranno i partecipanti all'asta. Il server espone un certo numero di metodi che verranno invocati dai client per fare le offerte. I metodi esposti verranno usati da remoto dai client tramite il meccanismo della Remote Method Invocation.
Ovviamente questo meccanismo consente ai partecipanti all'asta di fare solo le offerte: se volessimo essere tenuti aggiornati del prezzo corrente occorrebbe interrogare di continuo il server richiamando da remoto un metodo (ad esempio getCurrendBid()). Ovviamente questo genere di chiamata è molto dispendiosa sia dal punto di vista computazionale che dalle risorse di rete sprecate poichè facciamo una chiamata ad un metodo remoto. La soluzione a quest'ultimo problema è di usare il meccanismo della callback. Ogni client infatti una volta collegatosi ad un server remoto, può eseguire una registrazione presso il server per ricevere le notifiche su un determinato evento. Il server a sua volta, al verificarsi dell'evento, invocherà un certo metodo remoto (che il client espone) in modo da notificare ai sottoscriventi che un certo evento si è verificato. Questa tecnica che può essere vista come una sorta di RMI mutuo permette al server di comunicare degli eventi con una sola chiamata ad un metodo. Come detto in precedenza il tutorial si divide in due parti: nella prima si farà uso di RMI e nella seconda si introdurrà il meccanismo della callback.
Il server sarà il componente che espone dei metodi remoti che saranno richiamabili dai client. Il primo passo consiste quindi nel definire un'interfaccia in cui elencheremo i metodi esposti. Creiamo quindi un file AuctionInterface.java
import java.rmi.Remote; import java.rmi.*;
public interface AuctionInterface extends Remote { //se usiamo RMI dobbiamo estendere l'oggetto Remote //tutti i metodi remoti devono 'lanciare' l'eccezione RemoteException public void setBid(int bid) throws RemoteException; //offerta public int getCurrentBid() throws RemoteException; //ottengo il prezzo public String getProduct() throws RemoteException; //ottengo il nome del prodotto }
Successivamente, occorre scrivere l'implementazione di tale interfaccia. Creiamo quindi un file Auctioneer.java.
public class Auctioneer extends UnicastRemoteObject implements AuctionInterface { private int currentBid; //variabile che contiene il prezzo corrente private String product; //variabile che contiene il nome del prodotto corrente
public Auctioneer(String p, int b) throws RemoteException { super(); currentBid = b; //imposto il prezzo... product = p; //...ed il nome }
public int getCurrentBid() throws RemoteException { //per recuperare il prezzo corrente System.out.println("Richiesta prezzo ..."); return currentBid; }
public String getProduct() throws RemoteException { //per ricavare il nome del prodotto System.out.println("Richiesta nome Prodotto ..."); return product; }
public void setBid(int b) throws RemoteException { //per fare un'offerta' System.out.println("Nuova Offerta ..."); if (b > currentBid) { currentBid = b; System.out.println("Offerta Accettata..."); } else System.out.println("Offerta Rifiutata..."); }
public static void main(String [] args) { //da riga di comando passo NomeProdotto e PrezzoIniziale if(args.length == 0) { System.out.println("USAGE: java "); System.exit(0); } String name = null; try { //questo sarà l'indirizzo del mio server name = "rmi://" + InetAddress.getLocalHost().getHostName() + "/BidServer"; System.out.println("Nome RMI: " + name); } catch (Exception ue) {}
Auctioneer thisOne=null;
try { thisOne = new Auctioneer(args[0], Integer.parseInt(args[1])); //creo un nuovo server 'thisOne' che associo all'indirizzo 'name' sull'RMI Registry' Naming.rebind(name, thisOne); } catch (Exception e) { e.printStackTrace(); }
}
}
Successivamente definiamo il comportamento del Client. Il codice è molto semplice in quanto il client deve semplicemente leggere da tastiera un numero e richiamare i metodi remoti del server per reperire prezzo corrente e nome prodotto ed eventualmente fare la nuova offerta. Creiamo quindi un file Auction.java.
public class Auction { private int currentBid=0; //prezzo corrente private String product=""; //nome prodotto
AuctionInterface auctioneer;
public Auction() { }
public static void main(String [] args) { if(args.length == 0) { //da riga di comando passo l'indirizzo del server System.out.println("USAGE: java Auction "); System.exit(0); } String serverName = "//" + args[0] + "/BidServer"; //indirizzo del Server Auction auc = new Auction(); try { auc.auctioneer = (AuctionInterface) Naming.lookup(serverName); //cerco il server sul registry auc.currentBid = auc.auctioneer.getCurrentBid(); //chiamo il suo metodo remoto per conoscere il prezzo corrente auc.product = auc.auctioneer.getProduct(); //...ed il nome dell'oggetto in vendita System.out.println("Current Price for " + auc.product + " : " + auc.auctioneer.getCurrentBid()); } catch (NotBoundException ex) { System.out.println("The server is not available"); ex.printStackTrace(); } catch(Exception e) { e.printStackTrace(); }
boolean flag=true; //flag che servirà ad uscirà dal while
BufferedReader myInput = new BufferedReader (new InputStreamReader (System.in)); String str = new String(); //input da tastiera... int myBid = 0; while (flag) { try { System.out.print("New Bid: "); str = myInput.readLine(); //leggo da tastiera la nuova offerta } catch (IOException e) { System.out.println ("Error: " + e); } try { if (str=="") flag=false; //se la stringa è vuota esco impostando flag a false... else { myBid = Integer.parseInt(str); //ricavo l'intero dall'input auc.currentBid = auc.auctioneer.getCurrentBid(); //aggiorno il prezzo if (myBid>auc.currentBid) auc.auctioneer.setBid(myBid); //se la mia offerta è maggiore di quella corrente la propongo else System.out.println("Propose Higher Bid"); //altrimenti ti dico che è troppo bassa :) } auc.currentBid = auc.auctioneer.getCurrentBid(); //aggiorno di nuovo e comunque il prezzo } catch (RemoteException ex) { ex.printStackTrace(); } System.out.println("Current Bid is : " + auc.currentBid); //e ti dico a quanto è arrivata l'offerta //cioè conosco il prezzo dell'oggetto solo dopo aver fatto un'offerta... } } }
Per compilare ed eseguire questo esempio dobbiamo procedere come segue(da un terminale):
compilare il server: javac Auctioneer.java
compilare il client: javac Auction.java
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, 4 e 5 questo -classpath ./ (per Linux) , -classpath .\ (per Windows)
per il deployement dell'applicazione ricordo che per il server avremo bisogno dei file Auctioneer.class ed AuctionInterface.class, mentre per il client avremo bisogno di Auction.class e AuctionInterface.class.