9. JAVA RMI
9.1. Introduzione
Abbiamo visto come creare applicazioni di rete utilizzando strumenti di comunicazione chiamati socket. In un'applicazione client/server basata su questi strumenti, il collegamento tra il client e il server è il protocollo di comunicazione che hanno adottato per comunicare. Le due applicazioni possono essere scritte in linguaggi diversi: Java, ad esempio, per il client, Perl per il server, o qualsiasi altra combinazione. Abbiamo infatti due applicazioni distinte collegate da un protocollo di comunicazione noto a entrambe. Inoltre, l'accesso alla rete tramite socket non è trasparente per un'applicazione Java: essa deve utilizzare la classe Socket, una classe creata appositamente per gestire questi strumenti di comunicazione noti come socket.
Java RMI (Remote Method Invocation) consente di creare applicazioni di rete con le seguenti caratteristiche:
- Le applicazioni client/server sono applicazioni Java su entrambe le estremità della comunicazione
- Il client può utilizzare gli oggetti situati sul server come se fossero locali
- Il livello di rete diventa trasparente: le applicazioni non devono preoccuparsi di come le informazioni vengono trasportate da un punto all'altro.
L'ultimo punto è un fattore di portabilità: se il livello di rete di un'applicazione RMI dovesse cambiare, l'applicazione stessa non dovrebbe essere riscritta. Sono le classi RMI del linguaggio Java che dovrebbero essere adattate al nuovo livello di rete.
Il principio della comunicazione RMI è il seguente:
- Un'applicazione Java standard viene scritta sulla macchina A. Essa fungerà da server. A tal fine, alcuni dei suoi oggetti saranno "pubblicati" sulla macchina A, dove l'applicazione è in esecuzione, e diventeranno quindi servizi.
- Un'applicazione Java classica viene scritta sulla macchina B. Essa fungerà da client. Avrà accesso agli oggetti/servizi pubblicati sulla macchina A; ovvero, tramite un riferimento remoto, sarà in grado di manipolarli come se fossero locali. Per farlo, dovrà conoscere la struttura dell'oggetto remoto a cui desidera accedere (metodi e proprietà).
9.2. Impariamo con un esempio
La teoria alla base dell'interfaccia RMI non è semplice. Per chiarire meglio il concetto, esamineremo passo dopo passo il processo di scrittura di un'applicazione client/server utilizzando il pacchetto RMI di Java. Useremo un'applicazione che si trova in molti libri sull'RMI: il client chiama un singolo metodo di un oggetto remoto, che poi restituisce una stringa. Qui presentiamo una leggera variazione: il server ripete ciò che il client gli invia. Abbiamo già presentato un'applicazione di questo tipo in questo libro, una che si basa sui socket.
9.2.1. L'applicazione server
9.2.1.1. Passo 1: L'interfaccia oggetto/server
Un oggetto remoto è un'istanza di classe che deve implementare l'interfaccia Remote definita nel pacchetto java.rmi. I metodi dell'oggetto che saranno accessibili in remoto sono quelli dichiarati in un'interfaccia derivata dall'interfaccia Remote:
import java.rmi.*;
// remote interface p
ublic interface interEcho extends Remote{
public String echo(String msg) throws java.rmi.RemoteException
; }
Qui, dichiariamo un'interfaccia interEcho che dichiara un metodo echo come accessibile da remoto. Questo metodo può generare un'eccezione della classe RemoteException, che comprende tutti gli errori relativi alla rete.
9.2.1.2. Passaggio 2: Scrittura dell'oggetto server
Nel passaggio successivo, definiamo la classe che implementa l'interfaccia remota precedente. Questa classe deve derivare dalla classe UnicastRemoteObject, che fornisce metodi che consentono l'invocazione di metodi remoti.
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// class implementing remote echo
public class srvEcho extends UnicastRemoteObject implements interEcho{
// manufacturer
public srvEcho() throws RemoteException{
super();
}// manufacturer end
// method performing the echo
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// fine echo
}// end of class
Nella classe precedente, troviamo:
- il metodo che esegue l'echo
- un costruttore che non fa altro che chiamare il costruttore della classe padre. È lì per dichiarare che può lanciare una RemoteException.
Creeremo un'istanza di questa classe con un metodo main. Affinché un oggetto/servizio sia accessibile dall'esterno, deve essere creato e registrato nella directory degli oggetti accessibili esternamente. Un client che desideri accedere a un oggetto remoto procede come segue:
- contatta il servizio di directory sulla macchina in cui si trova l'oggetto desiderato. Questo servizio di directory opera su una porta che il client deve conoscere (1099 per impostazione predefinita). Il client richiede alla directory un riferimento a un oggetto/servizio, fornendone il nome. Se questo nome corrisponde a un oggetto/servizio presente nella directory, la directory restituisce un riferimento al client, attraverso il quale il client può comunicare con l'oggetto/servizio remoto.
- Da quel momento in poi, il client può utilizzare questo oggetto remoto come se fosse locale
Tornando al nostro server, dobbiamo creare un oggetto di tipo srvEcho e registrarlo nella directory degli oggetti accessibili dall'esterno. Questa registrazione viene effettuata utilizzando il metodo rebind della classe Naming:
dove
name: il nome che verrà associato all'oggetto remoto
obj: l'oggetto remoto
La nostra classe srvEcho diventa quindi la seguente:
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// class implementing remote echo
public class srvEcho extends UnicastRemoteObject implements interEcho{
// manufacturer
public srvEcho() throws RemoteException{
super();
}// manufacturer end
// method performing the echo
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// fine echo
// service creation
public static void main (String arg[]){
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// hand
}// end of class
Leggendo il programma precedente, sembra che si arresti immediatamente dopo la creazione e la registrazione del servizio echo. Non è così. Poiché la classe srvEcho deriva dalla classe UnicastRemoteObject, l'oggetto creato viene eseguito a tempo indeterminato: ascolta le richieste dei client su una porta anonima, ovvero una scelta dal sistema in base alle circostanze. La creazione del servizio è asincrona: nell'esempio, il metodo main crea il servizio e continua l'esecuzione; visualizzerà "Echo server ready".
9.2.1.3. Passaggio 3: Compilazione dell'applicazione server
A questo punto, possiamo compilare il nostro server. Compiliamo il file interEcho.java dall'interfaccia interEcho e il file srvEcho.java dalla classe srvEcho. Otteniamo i corrispondenti file .class: interEcho.class e srvEcho.class.
9.2.1.4. Passaggio 4: Scrittura del client
Scriviamo un client che accetta l'URL del server echo come parametro e
- legge una riga digitata sulla tastiera
- la invia al server echo
- visualizza la risposta che invia
- torna al punto 1 e si ferma quando la riga digitata è “end”.
Il risultato è il seguente client:
import java.rmi.*;
import java.io.*
; public class cltEch
o { public static void main(String a
rg[]){ // syntax : cltEcho URLService
// argument verification
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// client-server dialogue
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
// open keyboard flow
in=new BufferedReader(new InputStreamReader(System.in));
// service location
serveur=(interEcho) Naming.lookup(urlService);
// loop for reading msg to be sent to echo server
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// send msg to server and receive response
reponse=serveur.echo(msg);
// follow-up
System.out.println("Réponse serveur : " + reponse);
// next msg
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// it's over
System.exit(0);
// error management
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// hand
}// class
Non c'è nulla di particolarmente speciale in questo client, a parte l'istruzione che recupera un riferimento al server:
Ricordiamo che il nostro servizio echo è stato registrato nella directory dei servizi della macchina su cui risiede utilizzando l'istruzione:
Naming.rebind("srvEcho",serveurEcho);
Anche il client utilizza quindi un metodo della classe Naming per ottenere un riferimento al server che desidera utilizzare. Il metodo di ricerca utilizzato accetta come parametro l'URL del servizio richiesto. Questo URL ha la forma di un URL standard:
dove
rmi: opzionale - protocollo RMI
macchina: nome o indirizzo IP della macchina su cui è in esecuzione il server echo - opzionale, il valore predefinito è localhost.
port: porta di ascolto del servizio directory di questa macchina — opzionale, il valore predefinito è 1099
service_name: nome con cui è stato registrato il servizio richiesto (srvEcho nel nostro esempio)
Ciò che viene restituito è un'istanza dell'interfaccia remota interEcho. Supponendo che il client e il server non si trovino sulla stessa macchina, durante la compilazione del client cltEcho.java, il file interEcho.class — risultato della compilazione dell'interfaccia remota interEcho — deve essere presente nella stessa directory; in caso contrario, si verificherà un errore di compilazione sulle righe che fanno riferimento a questa interfaccia.
9.2.1.5. Passaggio 5: Generazione dei file .class necessari per l'applicazione client-server
Per distinguere chiaramente i componenti lato server da quelli lato client, collocheremo il server nella directory echo\server e il client nella directory echo\client.
La directory del server contiene i seguenti file sorgente:
E:\data\java\RMI\echo\serveur>dir *.java
INTERE~1 JAV 158 09/03/99 15:06 interEcho.java
SRVECH~1 JAV 759 09/03/99 15:07 srvEcho.java
Dopo aver compilato questi due file sorgente, otteniamo i seguenti file .class:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
Nella directory client troviamo il seguente file sorgente:
oltre al file interEcho.class generato durante la compilazione del server:
Dopo aver compilato il file sorgente, otteniamo i seguenti file .class:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
Se proviamo a eseguire il client cltEcho, otteniamo il seguente errore:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Erreur : java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Se si tenta di eseguire il server srvEcho, viene visualizzato il seguente errore:
E:\data\java\RMI\echo\serveur>j:\jdk12\bin\java srvEcho
Erreur java.rmi.StubNotFoundException: Stub class not found: srvEcho_Stub; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
In entrambi i casi, la Java Virtual Machine indica che non è riuscita a trovare la classe srvEcho_stub. In effetti, non abbiamo mai sentito parlare di questa classe prima d'ora. Nel client, il server è stato individuato utilizzando la seguente istruzione:
Qui, urlservice è la stringa rmi://localhost/srvEcho con
RMI: protocollo RMI
Localhost: la macchina su cui è in esecuzione il server — in questo caso, la stessa macchina del client. La sintassi è normalmente macchina:porta. Se non viene specificata alcuna porta, viene utilizzata per impostazione predefinita la porta 1099. Il servizio di directory del server è in ascolto su questa porta.
srvEcho: questo è il nome del servizio specifico richiesto
Non sono stati segnalati errori durante la compilazione. Era semplicemente necessario che il file interEcho.class per l'interfaccia remota fosse disponibile.
In fase di esecuzione, la macchina virtuale richiede la presenza di un file srvEcho_stub.class se il servizio richiesto è il servizio srvEcho; in generale, per un servizio X è richiesto un file X_stub.class. Questo file è necessario solo in fase di esecuzione, non durante la compilazione del client. Lo stesso vale per il server. Ma cos'è esattamente questo file?
Sul server è presente la classe srvEcho.class, che è il nostro oggetto/servizio remoto. Il client, anche se non ha bisogno di questa classe, necessita comunque di una sorta di immagine della stessa per poter comunicare con essa. Infatti, il client non invia le sue richieste direttamente all’oggetto remoto: le invia alla sua immagine locale srvEcho_stub.class situata sulla stessa macchina su cui si trova lui stesso. Questa immagine locale, srvEcho_stub.class, comunica con un'immagine simile (srvEcho_stub.class) situata sul server. Questa immagine viene creata dal file .class del server utilizzando uno strumento Java chiamato rmic. Su Windows, il comando:
genererà, dal file srvEcho.class, altri due file .class:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~2 CLA 3 264 09/03/99 16:57 srvEcho_Stub.class
SRVECH~3 CLA 1 736 09/03/99 16:57 srvEcho_Skel.class
Qui abbiamo il file srvEcho_stub.class di cui sia il client che il server hanno bisogno in fase di esecuzione. C'è anche un file srvEcho_Skel.class, il cui scopo è attualmente sconosciuto. Copiamo il file srvEcho_stub.class nelle directory del client e del server ed eliminiamo il file srvEcho_Skel.class. Ora abbiamo i seguenti file:
sul lato server:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
sul lato client:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
9.2.1.6. Passaggio 6: Esecuzione dell'applicazione echo client-server
Siamo pronti per eseguire la nostra applicazione client-server. Inizialmente, il client e il server gireranno sulla stessa macchina. Per prima cosa, dobbiamo avviare la nostra applicazione server. Ricordiamo che questo:
- crea il servizio
- e lo registra nella directory dei servizi del computer su cui è in esecuzione il server echo
Quest'ultimo passaggio richiede un servizio di registro. Questo viene avviato con il comando:
rmiregistry è il servizio di registro. Qui viene avviato in background in una finestra del Prompt dei comandi di Windows utilizzando il comando start. Con il registro attivo, possiamo creare il servizio echo e registrarlo nel registro dei servizi. Anche in questo caso, viene avviato in background utilizzando un comando start:
Il server echo viene eseguito in una nuova finestra DOS e visualizza l'output richiesto:
Non resta che avviare e testare il nostro client:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
9.2.1.7. Il client e il server su due macchine diverse
Nell'esempio precedente, il client e il server si trovavano sulla stessa macchina. Ora li collochiamo su macchine diverse:
- il server su una macchina Windows
- il client su una macchina Linux
Il server viene avviato come prima sul computer Windows. Sul computer Linux, i file .class del client sono stati trasferiti:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 9
drwxr-xr-x 2 serge admin 1024 Mar 10 10:02 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
Il client viene avviato:
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Erreur : java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling call header; nested exception is:
java.rmi.UnmarshalException: skeleton class not found but required for client version
Quindi abbiamo un errore: la Java Virtual Machine richiede apparentemente il file srvEcho_skel.class, che è stato generato dall'utilità rmic ma non era stato utilizzato fino ad ora. Lo ricreiamo e lo trasferiamo anche sulla macchina Linux:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 10:17 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 10:17 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
Otteniamo lo stesso errore di prima... Quindi ci pensiamo su e rileggiamo la documentazione RMI. Alla fine concludiamo che forse il server stesso ha bisogno del famoso file srvEcho_Skel.class. Quindi riavviamo il server sulla macchina Windows con entrambi i file srvEcho_Stub.class e srvEcho_Skel.class presenti:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~2 CLA 3 264 10/03/99 9:05 srvEcho_Stub.class
SRVECH~3 CLA 1 736 10/03/99 9:05 srvEcho_Skel.class
E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho
Quindi, sulla macchina Linux, testiamo nuovamente il client e questa volta funziona:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Possiamo quindi concludere che, sul lato server, devono essere presenti entrambi i file srvEcho_Stub.class e srvEcho_Skel.class. Sul lato client, finora è stato necessario solo il file srvEcho_Stub.class. Si era rivelato essenziale quando il client e il server si trovavano sulla stessa macchina Windows. Su Linux, lo rimuoveremo per vedere cosa succede...
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:28)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
Abbiamo riscontrato un errore interessante che sembra indicare che la Java Virtual Machine abbia tentato di caricare la famigerata classe stub, ma non ci sia riuscita a causa dell'assenza di un "security manager". Ricordiamo di aver letto qualcosa al riguardo nella documentazione. Ci immergiamo nuovamente nell'argomento... e scopriamo che il server deve creare e installare un security manager per garantire ai client che richiedono il caricamento delle classi che queste siano sicure. Senza questo security manager, il caricamento delle classi è impossibile. Questo sembra quadrare: il nostro client Linux ha richiesto al server la classe srvEcho_stub.class di cui aveva bisogno, e il server ha rifiutato, affermando che non era stato installato alcun security manager. Quindi modifichiamo il codice della funzione principale del server come segue:
// service creation
public static void main (String arg[]){
// installation of a security manager Sy
stem.setSecurityManager(new RMISecurityManager());
// service launch and registration
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// hand
Compiliamo e generiamo i file srvEcho_stub.class e srvEcho_Skel.class utilizzando lo strumento rmic. Avviamo il servizio di directory (rmiregistry) e poi il server, e otteniamo un errore che prima non avevamo!
Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve) lors du lancement du serveur d’écho
Il gestore di sicurezza sembra essere stato troppo rigido. Rivediamo nuovamente la documentazione... Ci rendiamo conto che quando un gestore di sicurezza è attivo, è necessario specificare le autorizzazioni del programma al momento del suo avvio. Ciò si effettua con la seguente opzione:
<mark style="background-color: #ffff00">start j:\\jdk12\\bin\\java -Djava.security.policy=mypolicy srvEcho</mark>
dove
java.security.policy è una parola chiave
mypolicy è un file di testo che definisce le autorizzazioni del programma. In questo caso, è il seguente:
Qui il programma dispone di autorizzazioni complete.
Ricominciamo da capo. Vai alla directory del server ed esegui le seguenti operazioni:
- avvia il servizio di directory: start j:\jdk12\bin\rmiregistry
- Avvia il server: start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
E questa volta, il server echo (ma non ancora il client) si avvia correttamente. Ora puoi provare quanto segue:
- arresta il server echo, quindi il servizio di directory
- Riavvia il servizio di directory mentre ti trovi in una directory diversa da quella del server
- tornare alla directory del server avviare il server echo: verrà visualizzato il seguente errore:
Erreur java.rmi.ServerException: RemoteException occurred in server thread; nes
ted exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
Possiamo concludere che la directory da cui viene avviato il servizio di directory è importante. In questo caso, Java non è riuscito a trovare il file srvEcho_stub.class perché il servizio di directory non è stato avviato dalla directory del server. Al momento dell'avvio del server, è possibile specificare la directory in cui si trovano le classi richieste dal server:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Il comando è su una singola riga. La parola chiave <mark style="background-color: #ffff00">java.rmi.server.codebase</mark> viene utilizzata per specificare l'URL della directory contenente le classi richieste dal server. In questo caso, l'URL specifica il protocollo file — che è il protocollo per l'accesso ai file locali — e la directory contenente i file .class del server. Pertanto, se si procede come segue:
- arrestare il servizio di directory
- riavviare il servizio di directory da una directory diversa da quella del server
- nella directory del server, avviare il server con il comando (una singola riga):
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Il server ora funziona correttamente. Possiamo ora passare al client. Proviamolo:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
Otteniamo lo stesso errore che indica l'assenza di un gestore di sicurezza. Pensiamo di aver commesso un errore e che sia il client a dover creare il proprio gestore di sicurezza. Lasciamo il server con il suo gestore di sicurezza, ma ne creiamo uno anche per il client. La funzione principale del client cltEcho.java diventa quindi:
public static void main(String arg[]){
// syntax : cltEcho machine po
rt // machine: machine where the echo server
operates // port: port where the service directory operates on the echo servic
e machine // argument ve
rification if(ar
g.length!=1){ System.err.println("Syntaxe : pg u
rl_service_rmi"
)
; System.exit(1); } // installation
of a security manager System.setSecurityManager(n
ew RMISecurityManager());
// client-server dia
logue String urlServi
ce=arg[0]; Buf
feredReader in=null;
String msg=null; S
tring reponse=null; interEcho serveur=null; try{
....
} catch (Exception e){
....
}// try
}// hand
Procediamo quindi come segue:
- Ricompilare cltEcho.java
- trasferire i file .class sulla macchina Linux
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
- Avvia il client
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
java.io.FileNotFoundException: /e:/data/java/rmi/echo/serveur/srvEcho_Stub.class
at java.io.FileInputStream.<init>(FileInputStream.java)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:150)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:170)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:119)
at sun.applet.AppletClassLoader.findClass(AppletClassLoader.java:496)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:199)
at sun.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:159)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:97)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
File not found when looking for: srvEcho_Stub
Erreur : java.rmi.UnmarshalException: Return value class not found; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Nonostante le apparenze, stiamo facendo progressi: l'errore non è più lo stesso. Possiamo vedere che il client è stato in grado di richiedere srvEcho_Stub.class al server, ma il server non è riuscito a trovarlo. Pertanto, il client deve disporre di un gestore di sicurezza se vuole essere in grado di richiedere classi al server.
Se guardiamo l'errore precedente, vediamo che il file srvEcho_Stub.class è stato cercato nella directory e:/data/java/rmi/echo/server/ e non è stato trovato. Eppure è proprio lì che si trova. Se osserviamo più da vicino l'elenco dei metodi coinvolti nell'errore, troviamo questo: sun.net.www.protocol.file.FileURLConnection.getInputStream. Il client sembra aver aperto una connessione utilizzando un oggetto FileURLConnection. Sospettiamo che tutto ciò sia correlato al modo in cui abbiamo avviato il nostro server:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Il messaggio di errore sembra riferirsi al valore della parola chiave java.rmi.server.codebase. Quando consultiamo nuovamente la documentazione, vediamo che il valore di questa parola chiave, negli esempi forniti, è sempre: http://.., ovvero il protocollo utilizzato è HTTP. Non è chiaro come il client richieda e ottenga le proprie classi dal server. Forse le richiede utilizzando l'URL specificato dalla parola chiave *java.rmi.server.codebase*, che viene impostata all'avvio del server. Decidiamo quindi di avviare il server utilizzando il seguente nuovo comando:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Il protocollo ora è HTTP. Dobbiamo spostare i file .class in una posizione accessibile al server HTTP sul computer in cui saranno memorizzate le classi. Nel nostro esempio, il server è in esecuzione su un computer Windows con un server HTTP Microsoft PWS. La directory principale di questo server è d:\Inetpub\wwwroot. Pertanto, procediamo come segue:
- Creare la directory d:\Inetpub\wwwroot\rmi\echo
- Inserire lì i file .class del server e il file mypolicy
- Avviare il server web se non è già in esecuzione
- Riavviare il servizio di registro (rmiregistry)
- Riavviare il server con il comando
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
- Sul computer Linux, avvia il client:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1622 Mar 10 14:37 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Uff! Funziona. Il client ha recuperato con successo il file srvEcho_Stub.class.
Tutto questo ci ha dato qualche idea e ci chiediamo se il client in esecuzione sulla macchina Windows del server funzionerebbe anche senza il file srvEcho_Stub.class. Passiamo alla directory del client, eliminiamo il file srvEcho_Stub.class se presente e avviamo il client allo stesso modo di Linux:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 622 10/03/99 14:12 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : nouveau message
Réponse serveur : [nouveau message]
Message : fin
9.2.1.8. Riepilogo
Lato server Windows:
- Il server dispone di un gestore di sicurezza
- ed è stato avviato con le seguenti opzioni: start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Lato client (Linux o Windows)
- Il client dispone di un gestore di sicurezza
- Su Linux, è stato avviato con `java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho`
- Su Windows, è stato avviato con j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
9.2.1.9. Server Echo su Linux, client su Windows e Linux
Ora sposteremo il server su una macchina Linux e testeremo i client Linux e Windows. La procedura è la seguente:
- Sposta i file .class del server sul computer Linux
shiva[serge]:/home/admin/serge/WWW/rmi/echo/serveur#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 16:15 .
drwxr-xr-x 3 serge admin 1024 Mar 10 16:09 ..
-rw-r--r-- 1 serge admin 256 Mar 10 16:09 interEcho.class
-rw-r--r-- 1 serge admin 1245 Mar 10 16:09 srvEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 16:09 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 16:09 srvEcho_Stub.class
- Poiché srvEcho_Stub.class verrà richiesto dai client, la directory scelta per le classi del server è accessibile al server HTTP della macchina Linux. In questo caso, l'URL di questa directory è http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur
- il servizio di registro viene avviato in background: /usr/local/bin/jdk/rmiregistry &
- Il server viene avviato in background: /usr/local/bin/jdk/bin/java
-Djava.rmi.server.codebase=http://shiva.istia.univ-angers.fr/~serge/rmi/echo/server/
srvEcho &
Possiamo testare i client. Iniziamo dal client Windows.
- Accedere alla directory del client sul computer Windows
- Avvia il client con il comando:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://shiva.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : fin
Test del client Linux:
shiva[serge]:/home/admin/serge/java/rmi/echo/client#
$ java cltEcho srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Si noti che per il client Linux in esecuzione sulla stessa macchina del server echo, non è stato necessario specificare una macchina nell'URL del servizio richiesto.
9.3. Secondo esempio: server SQL su una macchina Windows
9.3.1. Il problema
Nel capitolo dedicato a JDBC abbiamo visto come gestire i database relazionali. Negli esempi forniti, le applicazioni e il database utilizzato si trovavano sullo stesso computer Windows. Qui proponiamo di scrivere un server RMI su un computer Windows che consenta ai client remoti di accedere ai database ODBC pubblici presenti sul computer che ospita il server.

Il client RMI potrebbe eseguire tre operazioni:
- connettersi al database di sua scelta
- inviare query SQL
- chiudere la connessione
Il server esegue le query SQL del client e invia i risultati al client. Questa è la sua funzione principale, motivo per cui lo chiameremo server SQL.
Applichiamo i diversi passaggi visti in precedenza con il server echo.
9.3.2. Fase 1: L'interfaccia remota
L'interfaccia remota è l'interfaccia che elenca i metodi del server RMI a cui potranno accedere i client RMI. Useremo la seguente interfaccia:
import java.rmi.*;
// remote interface p
ublic interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp
) throws java.rmi.RemoteExcept
ion; public String[] executeSQL(String requete, String separa
teur) throws java.rmi.RemoteE
xception; public Str
ing close() throws java.rmi.Re
moteException; }
I ruoli dei vari metodi sono i seguenti:
Connect: il client si connette a un database remoto, fornendo il driver, l'URL JDBC, nonché il proprio ID e la password per accedere al database. Il server restituisce una stringa che indica il risultato della connessione:
executeSQL: il client richiede l'esecuzione di una query SQL sul database a cui è connesso. Specifica il carattere che deve separare i campi nei risultati restituiti. Il server restituisce un array di stringhe:
per una query di aggiornamento del database, dove n è il numero di righe aggiornate
se la query ha generato un errore
se la query non ha restituito risultati
se la query ha restituito risultati. Le righe restituite dal server sono i risultati della query.
close: il client chiude la connessione al database remoto. Il server restituisce una stringa che indica il risultato di questa chiusura:
9.3.3. Fase 2: Scrittura del server
Di seguito è riportato il codice sorgente Java per il server SQL. Per comprenderlo è necessario avere familiarità con la gestione dei database JDBC e con la creazione di server RMI. I commenti presenti nel programma dovrebbero facilitare la comprensione.
// imported packages
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;
// class srvSQL
public class srvSQL extends UnicastRemoteObject implements interSQL{
// global class data
private Connection DB;
// ------------- manufacturer
public srvSQL() throws RemoteException{
super();
}
// --------------- connect
public String connect(String pilote, String url, String id,
String mdp) throws RemoteException{
// connection to url database via driver
// identification with identity id and password mdp
String resultat=null; // result of the method
try{
// loading the driver
Class.forName(pilote);
// connection request
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion réussie";
} catch (Exception e){
// error
resultat="500 Echec de la connexion (" + e + ")";
}
// end
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur)
throws RemoteException{
// executes a SQL query on the DB database
// and puts the results in an array of strings
// data required to execute the request
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// create query container
S=DB.createStatement();
// request execution
if (! S.execute(requete)){
// update request
// returns the number of lines updated
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// it was a query request
// retrieve results
RS=S.getResultSet();
// number of Resultset fields
int nbChamps=RS.getMetaData().getColumnCount();
// we exploit them
while(RS.next()){
// create results line
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// add to results vector
resultats.addElement(ligne);
}// while
// end of results analysis
// free up resources
RS.close();
S.close();
// we return the results
int nbLignes=resultats.size();
if (nbLignes==0){
lignes=new String[1];
lignes[0]="501 Pas de résultats";
} else {
lignes=new String[resultats.size()];
for(int i=0;i<lignes.length;i++)
lignes[i]=(String) resultats.elementAt(i);
}//if
return lignes;
} catch (Exception e){
// error
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- close
public String close() throws RemoteException {
// closes database connection
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// return result
return resultat;
}
// ----------- hand
public static void main (String[] args){
// security manager
System.setSecurityManager(new RMISecurityManager());
// service launch
srvSQL serveurSQL=null;
try{
// creation
serveurSQL=new srvSQL();
// registration
Naming.rebind("srvSQL",serveurSQL);
// follow-up
System.out.println("Serveur SQL prêt");
} catch (Exception e){
// error
System.err.println("Erreur lors du lancement du serveur SQL ("+ e +")");
}// try-catch
}// hand
}// class
9.3.4. Scrivere il client RMI
Il client del server RMI viene chiamato con i seguenti parametri:
directoryServiceURL: URL RMI del servizio di directory che ha registrato il server SQL
driver: driver che il server SQL deve utilizzare per gestire il database
urlBase: URL JDBC del database da gestire
id: ID client o null se non c'è un ID
password: password del client o null se non è presente alcuna password
separator: carattere che il server SQL deve utilizzare per separare i campi nelle righe di risultato di una query
Ecco un esempio di possibili parametri:
con qui:
Nome RMI del server SQL
il driver standard per i database con interfaccia ODBC
per utilizzare un database di articoli elencato nei database ODBC pubblici del computer Windows
nessuna identità
nessuna password
i campi nei risultati saranno separati da una virgola
Una volta avviato con i parametri sopra indicati, il client segue questi passaggi:
- Si connette al server RMI srvSQL, che è un server RMI sulla stessa macchina del client
- richiede una connessione al database degli articoli
- Viene richiesto all’utente di digitare una query SQL
- la invia al server SQL
- Visualizza sullo schermo i risultati restituiti dal server
- chiede nuovamente all'utente di digitare una query SQL sulla tastiera. Si fermerà quando la query sarà terminata.
Di seguito è riportato il codice client Java. I commenti dovrebbero essere sufficienti per comprenderlo.
import java.rmi.*;
import java.io.*;
public class cltSQL {
// global class data
private static String syntaxe =
"syntaxe : cltSQL urlServiceAnnuaire pilote urlBase id mdp separateur";
private static BufferedReader in=null;
private static interSQL serveurSQL=null;
public static void main(String arg[]){
// syntax : cltSQL urlServiceAnnuaire separator driver url id mdp
// urlServiceAnnuaire : url of the directory of services RMI to contact
// driver: driver to be used for the database to be processed
// urlBase: jdbc url of the database to be used
// id: user identity
// mdp: password
// separator: string separating fields in query results
// check number of arguments
if(arg.length!=6)
erreur(syntaxe,1);
// init database connection parameters
String urlService=arg[0];
String pilote=arg[1];
String urlBase=arg[2];
String id, mdp, separateur;
if(arg[3].equals("null")) id=""; else id=arg[3];
if(arg[4].equals("null")) mdp=""; else mdp=arg[4];
if(arg[5].equals("null")) separateur=" "; else separateur=arg[5];
// installation of a security manager
System.setSecurityManager(new RMISecurityManager());
// client-server dialogue
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// open keyboard flow
in=new BufferedReader(new InputStreamReader(System.in));
// follow-up
System.out.println("--> Connexion au serveur RMI en cours...");
// service location
serveurSQL=(interSQL) Naming.lookup(urlService);
// follow-up
System.out.println("--> Connexion à la base de données en cours");
// initial database connection request
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// follow-up
System.out.println("<-- "+reponse);
// response analysis
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// loop for reading requests to be sent to the server SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// send request to server and receive response
lignes=serveurSQL.executeSQL(requete,separateur);
// follow-up
afficheLignes(lignes);
// following request
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// while
// follow-up
System.out.println("--> Fermeture de la connexion à la base de données distante");
// close the connection
reponse=serveurSQL.close();
// follow-up
System.out.println("<-- " + reponse);
// end
System.exit(0);
// error management
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// try
}// hand
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ error
private static void erreur(String msg, int exitCode){
// error msg display
System.err.println(msg);
// possible release of resources
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// we leave
System.exit(exitCode);
}// error
}// class
9.3.5. Fase 3: Creazione dei file .class
- Il server viene compilato
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac interSQL.java
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac srvSQL.java
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
- I file Stub e Skel vengono creati
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\rmic srvSQL
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
SRVSQL~2 CLA 4 491 12/03/99 17:56 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 12/03/99 17:56 srvSQL_Skel.class
- Spostare i file interSQL.class, srvSQL_Stub.class e srvSQL_Skel.class nella directory client
E:\data\java\RMI\sql\client>dir
CLTSQL~1 JAV 3 486 11/03/99 11:39 cltSQL.java
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
- Compilazione del client
E:\data\java\RMI\sql\client>j:\jdk12\bin\javac cltSQL.java
E:\data\java\RMI\sql\client>dir *.class
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
CLTSQL~1 CLA 2 839 12/03/99 18:00 cltSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
9.3.6. Fase 4: Test con server e client sulla stessa macchina Windows
- Il servizio di directory viene avviato in una directory diversa da quella del server e del client
- Inserire il seguente file mypolicy nelle directory del client e del server
- Avvia il server
E:\data\java\RMI\sql\serveur>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/sql/serveur/ srvSQL
- Avvia il client
E:\data\java\RMI\sql\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltSQL srvSQL
sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connection to server RMI in progress...
--> Current database connection
<-- 200 Successful connection
--> Requête : select nom, stock_actu from articles order by stock_actu desc
<-- 101 vélo.31
<-- 101 test3.13
<-- 101 water skis,13
<-- 101 canoeing,13
<-- 101 panther,11
<-- 101 leopard,11
<-- 101 sperm whale,10
<-- 101 rifle,10
<-- 101 arc,10
--> Query: update articles set stock_actu=stock_actu-1 where stock_actu<=11
<-- 100 5
--> Requête : select nom,stock_actu from articles order by stock_actu asc
<-- 101 sperm whale,9
<-- 101 rifle,9
<-- 101 arc.9
<-- 101 panther,10
<-- 101 leopard,10
<-- 101 test3.13
<-- 101 water skis,13
<-- 101 canoeing,13
<-- 101 vélo.31
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base
9.3.7. Passaggio 5: Test con un server su un computer Windows e un client su un computer Linux
- Se necessario, arrestare il server e il servizio di directory
- Trasferire i file .class del client su un computer Linux
shiva[serge]:/home/admin/serge/java/rmi/sql/client#
$ dir *.class
-rw-r--r-- 1 serge admin 2839 Mar 11 14:37 cltSQL.class
-rw-r--r-- 1 serge admin 451 Mar 11 14:37 interSQL.class
- I file del server sono collocati in una directory accessibile al server HTTP del computer Windows
D:\Inetpub\wwwroot\rmi\sql>dir
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 3 238 11/03/99 13:19 srvSQL.class
SRVSQL~2 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
MYPOLICY 81 08/06/98 15:01 mypolicy
- Riavvia il servizio di directory
- Riavvia il server con parametri diversi da quelli utilizzati nel test precedente
D:\Inetpub\wwwroot\rmi\sql>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/sql/ srvSQL
- Avvia il client sul computer Linux
/usr/local/bin/jdk/bin/java cltSQL rmi://tahe.istia.univ-angers.fr/srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Requête : select nom,stock_actu,stock_mini from articles order by nom
<-- 101 arc,9,8
<-- 101 sperm whale,9,6
<-- 101 canoeing,13.7
<-- 101 test3,13,9
<-- 101 rifle,9,8
<-- 101 leopard,10.7
<-- 101 panther,10.7
<-- 101 water skis,13.8
<-- 101 bicycle,31,8
--> Query: update articles set stock_actu=stock_mini where stock_mini<=7
<-- 100 4
--> Requête : select nom,stock_actu,stock_mini from articles order by nom
<-- 101 arc,9,8
<-- 101 sperm whale,6,6
<-- 101 canoeing,7,7
<-- 101 test3,13,9
<-- 101 rifle,9,8
<-- 101 leopard,7,7
<-- 101 panther,7,7
<-- 101 water skis,13.8
<-- 101 bicycle,31,8
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base
9.3.8. Conclusione
Si tratta di un'applicazione interessante in quanto consente l'accesso a un database da qualsiasi workstation della rete. Avremmo potuto benissimo scriverla in modo tradizionale utilizzando i socket, che è in realtà ciò che viene richiesto in un esercizio nel capitolo sui database. Se dovessimo scrivere questa applicazione in modo tradizionale:
- avremmo un client e un server che potrebbero essere scritti in linguaggi diversi
- il client e il server comunicherebbero scambiandosi righe di testo e avrebbero un dialogo che potrebbe assomigliare a questo:
dove i primi due parametri specificano dove trovare il server, e i quattro successivi indicano i parametri di connessione per il database da utilizzare
Il server potrebbe rispondere con qualcosa del tipo:
per chiedere al server di eseguire una query SQL sul database connesso al client. separator è il carattere utilizzato per separare i campi nelle righe di risposta.
Il server potrebbe rispondere con qualcosa del tipo
per una query di aggiornamento del database, dove n è il numero di righe aggiornate
se la query ha generato un errore
se la query non ha restituito risultati
se la query ha restituito risultati. Le righe restituite dal server sono i risultati della query.
per chiudere la connessione al database remoto. Il server potrebbe restituire una stringa che indica il risultato di questa chiusura:
Qui vediamo che se siamo in grado di costruire un'applicazione tradizionale utilizzando un protocollo del tipo sopra descritto, possiamo dedurre una possibile struttura per il server RMI. Nel protocollo abbiamo un messaggio dal client al server del tipo:
Possiamo avere un metodo all'interno del server RMI
e questo metodo, accessibile al client, deve quindi far parte dell'interfaccia pubblicata del server.
Per concludere, si noti che il nostro server attualmente gestisce un solo client: non può gestire più client così com'è scritto al momento. Infatti, se un client si connette al database B1, il server crea un oggetto Connection con DB=DB1. Se un secondo client richiede una connessione al database B2, il server la stabilisce con Connection DB=DB2, interrompendo così la connessione del primo client al database B1.
9.4. Esercizi
9.4.1. Esercizio 1
Estendi il precedente server SQL in modo che possa gestire più client.
9.4.2. Esercizio 2
Scrivere l'applet Java per l'e-commerce presentata negli esercizi del capitolo JDBC in modo che funzioni con il server RMI dell'esercizio precedente.