Skip to content

9. JAVA RMI

9.1. Introduzione

Abbiamo visto come creare applicazioni di rete utilizzando gli strumenti di comunicazione denominati sockets. In un’applicazione client/server basata su questi strumenti, il collegamento tra il client e il server è costituito dal protocollo di comunicazione che essi hanno adottato per comunicare tra loro. Le due applicazioni possono essere scritte con linguaggi diversi: Java, ad esempio, per il client, Perl per il server o qualsiasi altra combinazione. Si tratta effettivamente di due applicazioni distinte collegate da un protocollo di comunicazione noto a entrambe. Inoltre, l’accesso alla rete tramite i socket non è trasparente per un’applicazione Java: essa deve utilizzare la classe Socket, creata appositamente per gestire questi strumenti di comunicazione che sono i sockets.

JAVA RMI (Remote Method Invocation) consente di creare applicazioni di rete con le seguenti caratteristiche:

  1. Le applicazioni client/server sono applicazioni Java alle due estremità della comunicazione
  2. Il client può utilizzare gli oggetti presenti sul server come se fossero locali
  3. 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. Saranno le classi RMI del linguaggio Java a dover essere adattate al nuovo livello di rete.

Il principio di una comunicazione RMI è il seguente:

  1. Un’applicazione Java classica viene scritta su una macchina A, che fungerà da server. A tal fine, alcuni dei suoi oggetti verranno «pubblicati» sulla macchina A su cui l’applicazione è in esecuzione, trasformandosi così in servizi.
  2. Un’applicazione Java classica viene sviluppata su una macchina B, che fungerà da client. Essa avrà accesso agli oggetti/servizi pubblicati sulla macchina A, ovvero, tramite un riferimento remoto, potrà manipolarli come se fossero locali. A tal fine, dovrà conoscere la struttura dell’oggetto remoto a cui intende accedere (metodi e proprietà).

9.2. Impariamo con un esempio

La teoria alla base dell’interfaccia RMI non è semplice. Per chiarire meglio il concetto, seguiremo passo dopo passo la scrittura di un’applicazione client/server che utilizza il pacchetto RMI di Java. Prendiamo un’applicazione che si trova in numerosi libri su RMI: il client chiama un unico metodo di un oggetto remoto che gli restituisce quindi una stringa di caratteri. Qui presentiamo una leggera variante: il server ripete ciò che gli invia il client. In questo libro abbiamo già presentato un’applicazione di questo tipo basata sui socket.

9.2.1. L’applicazione server

9.2.1.1. Fase 1: l’interfaccia dell’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 da remoto sono quelli dichiarati in un’interfaccia derivata dall’interfaccia Remote:

import java.rmi.*;

// l'interfaccia remota
public interface interEcho extends Remote{
    public String echo(String msg) throws java.rmi.RemoteException;
}

In questo caso, viene quindi dichiarata un’interfaccia interEcho che dichiara un metodo echo come accessibile da remoto. Questo metodo può generare un’eccezione della classe RemoteException, classe che raggruppa tutti gli errori relativi alla rete.

9.2.1.2. Fase 2: scrittura dell’oggetto server

Nella fase successiva, si definisce la classe che implementa l’interfaccia remota precedente. Questa classe deve derivare dalla classe UnicastRemoteObject, che dispone dei metodi che consentono l’invocazione di metodi da remoto.

import java.rmi.*;
import java.rmi.server.*;
import java.net.*;

// classe che implementa l'eco remoto
public class srvEcho extends UnicastRemoteObject implements interEcho{

    // costruttore
    public srvEcho() throws RemoteException{
        super();
    }// fine del costruttore

    // metodo che implementa l'eco
    public String echo(String msg) throws RemoteException{
        return  "["  + msg + "]";
    }// fine eco
}// fine classe

Nella classe precedente troviamo:

  1. il metodo che restituisce l’output
  2. un costruttore che non fa altro che chiamare il costruttore della classe madre. È presente per dichiarare che può generare un'eccezione di tipo 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 dall'esterno. Un client che desideri accedere a un oggetto remoto procede infatti nel modo seguente:

  1. si rivolge al servizio di directory del computer su cui si trova l’oggetto desiderato. Questo servizio di directory opera su una porta che il cliente deve conoscere (1099 per impostazione predefinita). Il cliente richiede alla directory un riferimento a un oggetto/servizio di cui fornisce il nome. Se tale nome corrisponde a quello di un oggetto/servizio presente nella directory, quest’ultima restituisce al cliente un riferimento tramite il quale il cliente potrà interagire con l’oggetto/servizio remoto.
  2. Da quel momento in poi, il client può utilizzare quell’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 con il metodo della classe rebind della classe Naming:

Naming.rebind(String nom, Remote obj)

con

nome: 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.*;

// classe che implementa l'eco remoto
public class srvEcho extends UnicastRemoteObject implements interEcho{

    // costruttore
    public srvEcho() throws RemoteException{
        super();
    }// fine costruttore

    // metodo che implementa l'echo
    public String echo(String msg) throws RemoteException{
        return  "["  + msg + "]";
    }// fine eco

    // creazione del servizio
    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 ");
        }
    }// main
}// fine classe

Leggendo il programma precedente, si ha l’impressione che si interrompa subito dopo aver creato e registrato il servizio di eco. Non è così. Poiché la classe srvEcho deriva dalla classe UnicastRemoteObject, l’oggetto creato viene eseguito all’infinito: ascolta le richieste dei client su una porta anonima, ovvero scelta dal sistema in base alle circostanze. La creazione del servizio è asincrona: nell’esempio, il metodo main crea il servizio e prosegue la propria esecuzione: visualizzerà correttamente «Server di eco pronto».

9.2.1.3. Fase 3: compilazione dell’applicazione server

A questo punto, possiamo compilare il nostro server. Compiliamo il file interEcho.java dell’interfaccia interEcho e il file srvEcho.java della classe srvEcho. Otteniamo i file .class corrispondenti: interEcho.class e srvEcho.class.

9.2.1.4. Fase 4: scrittura del client

Scriviamo un client a cui passiamo come parametro il file URL del server di eco e che

  1. legge una riga digitata sulla tastiera
  2. la invia al server di eco
  3. visualizza la risposta inviata da quest’ultimo
  4. torna al punto 1 e si arresta quando la riga digitata è «fine».

Si ottiene così il seguente client:

import java.rmi.*;
import java.io.*;

public class cltEcho {

    public static void main(String arg[]){
        // sintassi: cltEcho URLService

        // verifica degli argomenti
        if(arg.length!=1){
            System.err.println("Syntaxe : pg url_service_rmi");
            System.exit(1);
        }

        // dialogo client-server
        String urlService=arg[0];
        BufferedReader in=null;
        String msg=null;
        String reponse=null;
        interEcho serveur=null;

        try{
            // apertura del flusso da tastiera
            in=new BufferedReader(new InputStreamReader(System.in));
            // localizzazione del servizio
            serveur=(interEcho) Naming.lookup(urlService);                
            // ciclo di lettura dei messaggi da inviare al server di eco
            System.out.print("Message : ");
            msg=in.readLine().toLowerCase().trim();
            while(! msg.equals("fin")){
                // invio del messaggio al server e ricezione della risposta
                reponse=serveur.echo(msg);
                // monitoraggio
                System.out.println("Réponse serveur : " + reponse);
                // messaggio successivo
                System.out.print("Message : ");                
                msg=in.readLine().toLowerCase().trim();
            }// while
            // finito
            System.exit(0);
        // gestione degli errori        
        } catch (Exception e){
            System.err.println("Erreur : " + e);
            System.exit(2);
        }// try
    }// main
}// classe                

Non c’è nulla di particolarmente rilevante in questo client, se non l’istruzione che richiede un riferimento al server:

            serveur=(interEcho) Naming.lookup(urlService);

Ricordiamo che il nostro servizio echo è stato registrato nella directory dei servizi della macchina su cui si trova con l’istruzione:


            Naming.rebind("srvEcho",serveurEcho);

Anche il client utilizza quindi un metodo della classe Naming per ottenere un riferimento al server che intende utilizzare. Il metodo lookup utilizzato accetta come parametro l’URL del servizio richiesto. Questo ha la forma di un URL classico:

    rmi://macchina:porta/nom_service

con

rmi: facoltativo - protocollo RMI

machine: nome o indirizzo IP della macchina su cui opera il server di eco - facoltativo, per impostazione predefinita localhost.

port: porta di ascolto del servizio di directory di questa macchina - facoltativo, per impostazione predefinita 1099

nom_service: nome con cui è stato registrato il servizio richiesto (srvEcho nel nostro esempio)

Ciò che viene recuperato è un'istanza dell'interfaccia remota interEcho. Supponendo che il client e il server non si trovino sulla stessa macchina, quando si compila il client cltEcho.java, è necessario che nella stessa directory sia presente il file interEcho.class, risultato della compilazione dell’interfaccia remota interEcho; in caso contrario, si verificherà un errore di compilazione nelle righe che fanno riferimento a tale interfaccia.

9.2.1.5. Fase 5: generazione dei file .class necessari all’applicazione client-server

Per comprendere chiaramente cosa appartiene al lato server e cosa al lato client, si collocherà il server in una directory echo\serveur e il client in una 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 la compilazione di questi due file sorgente, si ottengono 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 del client si trova il seguente file sorgente:

E:\data\java\RMI\echo\client>dir *.java

CLTECH~1 JAV         1 427  09/03/99  16:08 cltEcho.java

oltre al file interEcho.class generato durante la compilazione del server:

E:\data\java\RMI\echo\client>dir *.class

INTERE~1 CLA           256  09/03/99  15:59 interEcho.class

Dopo la compilazione del file sorgente si ottengono 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 si tenta di eseguire il client cltEcho, viene visualizzato 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 avviare 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 macchina virtuale Java segnala di non aver trovato la classe srvEcho_stub. In effetti, non abbiamo mai sentito parlare di questa classe. Nel client, la localizzazione del server è stata effettuata con la seguente istruzione:

            serveur=(interEcho) Naming.lookup(urlService);                

In questo caso, urlservice è la stringa rmi://localhost/srvEcho con

Rmi: protocollo RMI

Localhost: macchina su cui opera il server – in questo caso la stessa macchina su cui si trova il client. La sintassi è normalmente macchina:porta. In assenza della porta, verrà utilizzata per impostazione predefinita la porta 1099. Su questa porta è in ascolto il servizio di directory del server.

srvEcho: è il nome del servizio specifico richiesto

Durante la compilazione non era stato segnalato alcun errore. Era semplicemente necessario che il file interEcho.class dell’interfaccia remota fosse disponibile.

All’esecuzione, la macchina virtuale richiede la presenza di un file srvEcho_stub.class se il servizio richiesto è il servizio srvEcho; in generale, un file X_stub.class per un servizio X. Questo file è necessario solo all’esecuzione, non alla compilazione del client. Lo stesso vale per il server. Che cos’è quindi questo file?

Sul server si trova la classe srvEcho.class, che rappresenta il nostro oggetto/servizio remoto. Il client, pur non avendo bisogno di questa classe, necessita comunque di una sorta di immagine della stessa per potersi comunicare con essa. Infatti, il client non indirizza direttamente le proprie richieste all’oggetto remoto: le indirizza 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 della stessa natura (srvEcho_stub.class) situata questa volta sul server. Tale immagine viene creata a partire dal file .class presente sul server tramite uno strumento Java denominato rmic. In Windows, il comando:

E:\data\java\RMI\echo\serveur>j:\jdk12\bin\rmic srvEcho

genererà, a partire 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

C'è effettivamente il file srvEcho_stub.class, necessario sia al client che al server per l'esecuzione. C'è anche un file srvEcho_Skel.class, di cui per ora non si conosce la funzione. Si crea una copia del file srvEcho_stub.class nella directory del client e del server e si elimina il file srvEcho_Skel.class. Si ottengono quindi 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

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. Fase 6: Esecuzione dell'applicazione client-server di eco

Siamo pronti per eseguire la nostra applicazione client-server. Inizialmente, il client e il server funzioneranno sulla stessa macchina. Per prima cosa, dobbiamo avviare la nostra applicazione server. Ricordiamo che questa:

  • crea il servizio
  • lo registra nella directory dei servizi del computer su cui opera il server echo

Quest’ultimo punto richiede la presenza di un servizio di directory. Questo viene avviato con il comando:

start j:\jdk12\bin\rmiregistry

rmiregistry è il servizio di directory. Qui viene avviato in background in una finestra DOS di Windows tramite il comando start. Una volta attivata la directory, è possibile creare il servizio di eco e registrarlo nella directory dei servizi. Anche in questo caso, viene avviato in background tramite il comando start:

E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho

Il server di eco viene eseguito in una nuova finestra DOS e visualizza quanto richiesto:

Serveur d’écho prêt

Non ci 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. Client e 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 un computer Windows
  • il client su un computer Linux

Il server viene avviato come in precedenza sul computer Windows. Sul computer Linux sono stati trasferiti i file .class del client:

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

Si verifica quindi un errore: la macchina virtuale Java richiede apparentemente il file srvEcho_skel.class, che era stato generato dall’utilità rmic ma che fino a quel momento non era stato utilizzato. 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

Si verifica lo stesso errore di prima... Quindi ci pensiamo su e rileggiamo la documentazione su RMI. Alla fine ci diciamo che forse è il server stesso ad aver bisogno del famoso file srvEcho_Skel.class. Riavviamo quindi, sul computer Windows, il server con i due 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, sul computer Linux, si prova 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

Ne consegue quindi che, sul lato server, devono essere presenti i due file srvEcho_Stub.class e srvEcho_Skel.class. Sul lato client, finora è stato necessario solo il file srvEcho_Stub.class. Si era rivelato indispensabile quando il client e il server si trovavano sulla stessa macchina Windows. Su Linux, lo rimuoviamo 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

C'è un errore interessante che sembra indicare che la macchina virtuale Java abbia tentato di caricare la famosa classe stub, ma che non ci sia riuscita in assenza di un «security manager». Ci viene in mente di aver letto qualcosa su questo argomento nella documentazione. Ci immergiamo nuovamente nella documentazione... e scopriamo che il server deve creare e installare un "security manager" che garantisca ai client che richiedono il caricamento delle classi che queste siano sicure. In assenza di tale "security manager", il caricamento delle classi è impossibile. Sembra quadrare: il nostro client Linux ha richiesto al server la classe srvEcho_stub.class di cui ha bisogno e il server ha rifiutato la richiesta, segnalando che non era stato installato alcun gestore di sicurezza. Modifichiamo quindi il codice della funzione main del server nel modo seguente:

    // creazione del servizio
    public static void main (String arg[]){

        // installazione di un gestore di sicurezza
        System.setSecurityManager(new RMISecurityManager());

        // avvio e registrazione del servizio
        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 ");
        }
    }// main

Si compila e si generano i file srvEcho_stub.class e srvEcho_Skel.class con lo strumento rmic. Si avvia il servizio di directory (rmiregistry), poi il server, e si verifica un errore che prima non si era mai verificato!

Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve)  lors du lancement du serveur d’écho

Sembra che il gestore di sicurezza sia stato troppo efficace. Rileggiamo la documentazione... Ci rendiamo conto che, quando un gestore di sicurezza è attivo, è necessario specificarne i diritti all’avvio di un programma. Ciò si effettua con la seguente opzione:

start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho

dove

java.security.policy è una parola chiave

mypolicy è un file di testo che definisce i permessi del programma. In questo caso, è il seguente:

grant {
    // Consenti tutto per ora
    permission java.security.AllPermission;
};

In questo caso il programma dispone di tutti i diritti.

Ricominciamo. Ci posizioniamo nella directory del server ed eseguiamo in sequenza:

  • avvio del servizio di directory: start j:\jdk12\bin\rmiregistry
  • avvio del 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 potete fare la seguente prova:

  • arrestare il server echo e poi il servizio di directory
  • riavviare il servizio di directory trovandosi in una directory diversa da quella del server
  • tornate alla directory del server avviate il server echo: otterrete 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

Ne consegue che la directory da cui viene avviato il servizio di directory è importante. In questo caso, Java non ha trovato la classe srvEcho_stub.class perché il servizio di directory non è stato avviato dalla directory del server. Al momento dell’avvio del server, è possibile specificare in quale directory si trovano le classi necessarie al server:

start j:\jdk12\bin\java 
-Djava.security.policy=mypolicy 
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/ 
srvEcho

Il comando va inserito su un’unica riga. La parola chiave java.rmi.server.codebase serve a indicare il percorso della directory contenente le classi necessarie al server. In questo caso, tale URL specifica il protocollo file, ovvero il protocollo di accesso ai file locali, e la directory contenente i file .class del server. Se quindi si procede come segue:

  • arresto del servizio di directory
  • riavviare il servizio di directory da una directory diversa da quella del server
  • nella directory del server, avviare il servizio con il comando (una sola riga):
 start j:\jdk12\bin\java 
-Djava.security.policy=mypolicy 
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/ 
srvEcho

Il server è stato avviato correttamente. Si può quindi passare al client. Lo si testa:

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

Si ottiene lo stesso errore che segnala l’assenza di un gestore di sicurezza. Si pensa che forse ci si sia sbagliati e che sia il client a dover creare il proprio gestore di sicurezza. Si lascia il server con il suo gestore di sicurezza, ma se ne crea uno anche per il client. La funzione main del client cltEcho.java diventa quindi:

public static void main(String arg[]){
        // sintassi: cltEcho porta della macchina
        // macchina: macchina su cui opera il server di eco
        // porta: porta su cui opera la directory dei servizi sulla macchina del servizio di eco

        // verifica degli argomenti
        if(arg.length!=1){
            System.err.println("Syntaxe : pg url_service_rmi");
            System.exit(1);
        }

        // installazione di un gestore di sicurezza
        System.setSecurityManager(new RMISecurityManager());

        // dialogo client-server
        String urlService=arg[0];
        BufferedReader in=null;
        String msg=null;
        String reponse=null;
        interEcho serveur=null;

        try{
            ....
        } catch (Exception e){
            ....
        }// prova
    }// main

Si procede quindi come segue:

  • si ricompila cltEcho.java
  • si trasferiscono i file .class sul computer 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
  • si 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, si stanno facendo progressi: l’errore non è più lo stesso. Si nota che il client è riuscito a richiedere al server la classe srvEcho_Stub.class, ma che quest’ultimo non l’ha trovata. Pertanto, il client deve disporre di un gestore di sicurezza se vuole poter richiedere classi al server.

Se si esamina l’errore precedente, si nota che il file srvEcho_Stub.class è stato cercato nella directory e:/data/java/rmi/echo/serveur/ e non è stato trovato. Eppure si trova proprio lì. Se si osserva più da vicino l’elenco dei metodi coinvolti nell’errore, si trova questo: sun.net.www.protocol.file.FileURLConnection.getInputStream. Il client sembra aver aperto un flusso con un oggetto di tipo FileURLConnection. Si deduce che tutto ciò sia correlato al modo in cui è stato 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 fare riferimento al valore della parola chiave java.rmi.server.codebase. Rileggendo la documentazione, si nota che il valore di questa chiave, negli esempi forniti, è sempre: http://.., c.a.d. Il protocollo utilizzato è http. Non è chiaro come il client richieda e ottenga le classi dal server. Forse le richiede utilizzando il valore URL della parola chiave java.rmi.server.codebase, URL specificata all’avvio del server. Si decide quindi di avviare il server con 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. È necessario spostare i file .class in una posizione accessibile al server http del computer su cui saranno memorizzate le classi. Nel nostro esempio, il server gira su una macchina Windows con un server HTTP PWS di Microsoft. La radice di questo server è d:\Inetpub\wwwroot. Si procede quindi nel modo seguente:

  • si crea la directory d:\Inetpub\wwwroot\rmi\echo
  • vi si collocano i file .class del server e il file mypolicy
  • si avvia il server Web, se non è già stato fatto
  • si riavvia il servizio di directory (rmiregistry)
  • si riavvia 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
  • sulla macchina Linux, si 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 è riuscito a recuperare il famoso file srvEcho_Stub.class.

Tutto questo ci ha dato qualche idea e ci chiediamo se anche il client che si trova sul server Windows funzionerebbe senza il file srvEcho_Stub.class. Passiamo nella 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. Résumé

Lato server Windows:

  • il server dispone di un gestore di sicurezza
  • è 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

Sul lato client Linux o Windows

  • il client dispone di un gestore di sicurezza
  • su Linux, è stato avviato tramite java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
  • su Windows, è stato avviato tramite 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 spostiamo il server su una macchina Linux e testiamo i client Linux e Windows. La procedura da seguire è la seguente:

  • si trasferiscono i file .class dal server alla macchina 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é la classe srvEcho_Stub.class verrà richiesta dai clienti, la directory scelta per le classi del server è una directory accessibile al server HTTP della macchina Linux. In questo caso, la classe URL si trova in questa directory: http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur
  • il servizio di directory 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/serveur/

srvEcho &

È possibile testare i client. Iniziamo con il client Windows.

  • Si accede alla directory del client sul computer Windows
  • si 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

Si esegue il 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

Da notare che per il client Linux che gira sulla stessa macchina del server echo, non è stato necessario specificare la macchina nel URL del servizio richiesto.

9.3. Secondo esempio: server SQL su macchina Windows

9.3.1. Il problema

Nel capitolo JDBC abbiamo visto come gestire i database relazionali. Negli esempi presentati, le applicazioni e il database utilizzato si trovavano sulla stessa macchina Windows. In questa sede ci proponiamo di scrivere un server RMI su una macchina Windows, che consentirebbe ai client remoti di utilizzare i database pubblici ODBC del computer su cui si trova il server.

Image

Il client RMI potrebbe eseguire 3 operazioni:

  • connettersi al database di sua scelta
  • inviare richieste SQL
  • chiudere la connessione

Il server esegue le richieste SQL del client e gli invia i risultati. Questo è il suo compito principale ed è per questo che 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. Utilizzeremo la seguente interfaccia:

import java.rmi.*;

// l'interfaccia remota
public interface interSQL extends Remote{
    public String connect(String pilote, String url, String id, String mdp)
        throws java.rmi.RemoteException;
    public String[] executeSQL(String requete, String separateur) 
        throws java.rmi.RemoteException;
    public String close()
        throws java.rmi.RemoteException;
}

Il ruolo dei diversi metodi è il seguente:

Connect: il client si connette a un database remoto di cui fornisce il pilote, l’url, l’JDBC, la propria identità id e la propria password mdp per accedere a tale database. Il server restituisce una stringa di caratteri che indica il risultato della connessione:

    200 - Connexion réussie
    500 - Echec de la connexion

executeSQL: il client richiede l’esecuzione di una query SQL sul database a cui è connesso. Indica il carattere che deve separare i campi nei risultati che gli vengono restituiti. Il server restituisce un array di stringhe:

    100 n

per una richiesta di aggiornamento del database, dove n è il numero di righe aggiornate

    500 msg d’erreur

se la richiesta ha generato un errore

    501 Pas de résultats

se la richiesta non ha generato alcun risultato

    101 ligne1
    101 ligne2
    101 ...

se la query ha generato dei risultati. Le righe così restituite dal server sono i risultati della query.

close: il client chiude la connessione con il database remoto. Il server restituisce una stringa che indica il risultato di tale chiusura:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

9.3.3. Fase 2: Codice del server

Di seguito è riportato il codice sorgente Java del server SQL. Per comprenderlo è necessario aver assimilato la gestione dei database JDBC e la creazione dei server RMI. I commenti del programma dovrebbero facilitarne la comprensione.

// pacchetti importati
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;

// classe srvSQL
public class srvSQL extends UnicastRemoteObject implements interSQL{

    // dati globali della classe
    private Connection DB;

    // ------------- costruttore
    public srvSQL() throws RemoteException{
        super();
    }

    // --------------- connessione
    public String connect(String pilote, String url, String id,
        String mdp) throws RemoteException{

        // connessione all'URL della base tramite il driver
        // identificazione con ID e password

        String resultat=null;            // risultato del metodo
        try{
            // caricamento del driver
            Class.forName(pilote);
            // richiesta di connessione
            DB=DriverManager.getConnection(url,id,mdp);
            // ok
            resultat="200 Connexion réussie";
        } catch (Exception e){
            // errore
            resultat="500 Echec de la connexion (" + e + ")";
        }
        // fine
        return resultat;
    }            

    // ------------- executeSQL
    public String[] executeSQL(String requete, String separateur)
        throws RemoteException{

        // esegue una query SQL sul database DB
        // e inserisce i risultati in un array di stringhe

        // dati necessari per l'esecuzione della query
        Statement S=null;
        ResultSet RS=null;
        String[] lignes=null;
        Vector resultats=new Vector();
        String ligne=null;

        try{
            // creazione del contenitore della query
            S=DB.createStatement();
            // esecuzione della query
            if (! S.execute(requete)){
                // richiesta di aggiornamento
                // viene restituito il numero di righe aggiornate
                lignes=new String[1];
                lignes[0]="100 "+S.getUpdateCount();
                return lignes;
            }
            // si trattava di una query di interrogazione
            // si recuperano i risultati
            RS=S.getResultSet();
            // numero di campi del Resultset
            int nbChamps=RS.getMetaData().getColumnCount();
            // vengono elaborati
            while(RS.next()){
                // creazione della riga dei risultati
                ligne="101 ";
                for (int i=1;i<nbChamps;i++)
                    ligne+=RS.getString(i)+separateur;
                ligne+=RS.getString(nbChamps);
                // aggiunta al vettore dei risultati
                resultats.addElement(ligne);
            }// while
            // fine dell'elaborazione dei risultati
            // si liberano le risorse
            RS.close();
            S.close();
            // restituzione dei risultati
            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){
            // errore
            lignes=new String[1];
            lignes[0]="500 " + e;
            return lignes;
        }// try-catch
    }// executeSQL

    // --------------- chiudi
    public String close() throws RemoteException {
        // chiude la connessione al database
        String resultat=null;
        try{
            DB.close();
            resultat="200 Base fermée";
        } catch (Exception e){
            resultat="500 Erreur à la fermeture de la base ("+e+")";
        }
        // restituisce il risultato
        return resultat;
    }

    // ----------- main
    public static void main (String[] args){

        // gestore della sicurezza
        System.setSecurityManager(new RMISecurityManager());

        // avvio del servizio
        srvSQL serveurSQL=null;
        try{
            // creazione
            serveurSQL=new srvSQL();
            // registrazione
            Naming.rebind("srvSQL",serveurSQL);
            // monitoraggio
            System.out.println("Serveur SQL prêt");
        } catch (Exception e){
            // errore
            System.err.println("Erreur lors du lancement du serveur SQL ("+ e +")");
        }// try-catch
    }// main

}// classe

9.3.4. Scrittura del client RMI

Il client del server RMI viene richiamato con i seguenti parametri:

    urlserviceAnnuaire pilote urlBase id mdp separateur

urlserviceAnnuaire: 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: identificativo del cliente o null se non presente

mdp: password del cliente o null se non è presente una password

separatore: carattere che il server SQL deve utilizzare per separare i campi delle righe dei risultati di una query

Ecco un esempio di parametri possibili:

    srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

dove:

urlserviceAnnuaire    srvSQL

nome rmi del server SQL

pilote     sun.jdbc.odbc.JdbcOdbcDriver

il driver standard per i database con interfaccia ODBC

urlBase    jdbc:odbc:articles

per utilizzare un database articoli dichiarato nell'elenco dei database pubblici ODBC del computer Windows

id    null

nessuna identità

mdp    null

nessuna password

separateur    , 

i campi dei risultati saranno separati da una virgola

Una volta avviato con i parametri precedenti, il client segue i seguenti passaggi:

  • si connette al server RMI srvSQL, ovvero un server RMI sulla stessa macchina del client
  • richiede la connessione al database articoli
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • chiede all’utente di digitare una query SQL sulla tastiera
  • la invia al server SQL
executeSQL(requete, ’’,’’);
  • visualizza sullo schermo i risultati restituiti dal server
  • chiede nuovamente all’utente di digitare una richiesta SQL sulla tastiera. Si interromperà quando la richiesta sarà terminata.

Di seguito è riportato il codice Java del client. I commenti dovrebbero essere sufficienti per comprenderlo.

import java.rmi.*;
import java.io.*;

public class cltSQL {

    // dati globali della classe
    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[]){
        // sintassi: cltSQL urlServiceAnnuaire separatore driver URL ID password
        // urlServiceAnnuaire: URL dell'elenco dei servizi RMI da contattare
        // driver: driver da utilizzare per il database da gestire
        // urlBase: URL JDBC del database da utilizzare
        // id: identificativo dell'utente
        // mdp: la sua password
        // separatore: stringa che separa i campi nei risultati di una query

        // verifica del numero di argomenti
        if(arg.length!=6)
            erreur(syntaxe,1);

        // inizializzazione dei parametri di connessione al database
        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];        

        // installazione di un gestore di sicurezza
        System.setSecurityManager(new RMISecurityManager());

        // dialogo client-server
        String requete=null;
        String reponse=null;
        String[] lignes=null;
        String codeErreur=null;

        try{
            // apertura del flusso da tastiera
            in=new BufferedReader(new InputStreamReader(System.in));
            // monitoraggio
            System.out.println("--> Connexion au serveur RMI en cours...");
            // localizzazione del servizio
            serveurSQL=(interSQL) Naming.lookup(urlService);
            // monitoraggio
            System.out.println("--> Connexion à la base de données en cours");
            // richiesta di connessione iniziale al database
            reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
            // monitoraggio
            System.out.println("<-- "+reponse);
            // analisi della risposta
            codeErreur=reponse.substring(0,3);
            if(codeErreur.equals("500")) 
                erreur("Abandon sur erreur de connexion à la base",3);
            // ciclo di lettura delle richieste da inviare al server SQL
            System.out.print("--> Requête : ");
            requete=in.readLine().toLowerCase().trim();
            while(! requete.equals("fin")){
                // invio della richiesta al server e ricezione della risposta
                lignes=serveurSQL.executeSQL(requete,separateur);
                // monitoraggio
                afficheLignes(lignes);
                // richiesta successiva
                System.out.print("--> Requête : ");                
                requete=in.readLine().toLowerCase().trim();
            }// mentre
            // monitoraggio
            System.out.println("--> Fermeture de la connexion à la base de données distante");
            // si chiude la connessione
            reponse=serveurSQL.close();
            // seguito
            System.out.println("<-- " + reponse);
            // fine
            System.exit(0);
        // gestione degli errori        
        } catch (Exception e){
            erreur("Abandon sur erreur : " + e,2);
        }// try
    }// main

    // ----------- AfficheLignes
    private static void afficheLignes(String[] lignes){
        for (int i=0;i<lignes.length;i++)
            System.out.println("<-- " + lignes[i]);
    }// afficheLignes

    // ------------ errore
    private static void erreur(String msg, int exitCode){
        // visualizzazione messaggio di errore
        System.err.println(msg);
        // eventuale liberazione delle risorse
        try{
            in.close();
            serveurSQL.close();
        } catch(Exception e){}
        // uscita
        System.exit(exitCode);
    }// errore

}// classe    

9.3.5. Fase 3: creazione dei file .class

  • il server è stato 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
  • vengono creati i file Stub e Skel
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
  • si trasferiscono i file interSQL.class, srvSQL_Stub.class, srvSQL_Skel.class nella directory del cliente
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
  • si compila il 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
F:\>start j:\jdk12\bin\rmiregistry
  • Si inserisce il seguente file mypolicy nelle directory del client e del server
grant {
    // Consenti tutto per ora
    permission java.security.AllPermission;
};
  • si 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
  • si 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 ,

--> Connexion au serveur RMI en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Richiesta: select nome, stock_actu da articoli ordinati per stock_actu in ordine decrescente
<-- 101 vélo,31
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 panthère,11
<-- 101 léopard,11
<-- 101 cachalot,10
<-- 101 fusil,10
<-- 101 arc,10
--> Query: update articoli set stock_actu=stock_actu-1 where stock_actu<=11
<-- 100 5
--> Query: select nome, stock_actu from articoli order by stock_actu asc
<-- 101 cachalot,9
<-- 101 fusil,9
<-- 101 arc,9
<-- 101 panthère,10
<-- 101 léopard,10
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 vélo,31
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

9.3.7. Fase 5: Test con server su macchina Windows e client su macchina Linux

  • Se necessario, si arrestano il server e il servizio di directory
  • si trasferiscono 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 vengono inseriti 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
  • riprendiamo il servizio di elenco telefonico
F:\>start j:\jdk12\bin\rmiregistry
  • si 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
  • si avvia il client sulla macchina Linux
/usr/local/bin/jdk/bin/java cltSQL rmi://tahe.istia.univ-angers.fr/srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

--> Query: select nome,stock_actu,stock_mini from articoli order by nome
<-- 101 arc,9,8
<-- 101 cachalot,9,6
<-- 101 canoé,13,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,10,7
<-- 101 panthère,10,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Query: update articles set stock_actu=stock_mini where stock_mini<=7
<-- 100 4
--> Query: select nome, stock_actu, stock_mini da articoli ordinati per nome
<-- 101 arc,9,8
<-- 101 cachalot,6,6
<-- 101 canoé,7,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,7,7
<-- 101 panthère,7,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

9.3.8. Conclusione

Si tratta di un'applicazione interessante in quanto consente l'accesso a un database da qualsiasi postazione della rete. Si sarebbe potuto benissimo scriverla in modo tradizionale utilizzando i socket, come del resto richiesto in un esercizio del capitolo dedicato ai database. Se si scrivesse questa applicazione in modo tradizionale:

  • avremmo un client e un server che potrebbero essere scritti con linguaggi diversi
  • il client e il server comunicherebbero tramite lo scambio di righe di testo e il loro dialogo potrebbe essere del tipo:
client : connect machine port pilote urlBase id mdp

dove i primi due parametri specificano dove trovare il server, mentre i quattro successivi indicano i parametri di connessione al database da utilizzare

Il server potrebbe rispondere con qualcosa del tipo:

200 - Connexion réussie
500 - Echec de la connexion
client : executeSQL requete, separateur

per richiedere al server di eseguire una query SQL sul database collegato al client. Il separatore è il carattere che separa i campi nelle righe della risposta.

Il server potrebbe rispondere con qualcosa del tipo

    100 n

per una richiesta di aggiornamento del database, dove n è il numero di righe aggiornate

    500 msg d’erreur

se la richiesta ha generato un errore

    501 Pas de résultats

se la ricerca non ha prodotto alcun risultato

    101 ligne1
    101 ligne2
    101 ...

se la query ha generato dei risultati. Le righe così restituite dal server sono i risultati della query.

client : close

per chiudere la connessione con il database remoto. Il server potrebbe restituire una stringa che indica il risultato di tale chiusura:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

Si nota qui che, se si è in grado di realizzare un’applicazione tradizionale con un protocollo del tipo precedente, è possibile dedurne una possibile struttura del server RMI. Laddove nel protocollo si ha una frase dal client al server del tipo:

    commande param1 param2 ... paramq

all’interno del server RMI si potrà avere un metodo

    String commande(param1,param2,..., paramq)

e questo metodo, accessibile al client, dovrà quindi far parte dell’interfaccia pubblicata del server.

Per concludere, notiamo che il nostro server attualmente gestisce un solo client: non può gestirne più di uno, così come è scritto al momento. Infatti, se un client si connette a un database B1, il server crea un oggetto Connection DB=DB1. Se un secondo client richiede una connessione a un database B2, il server lo registra come Connection DB=DB2, interrompendo così la connessione del primo client al database B1.

9.4. Esercizi

9.4.1. Esercizio 1

Estendere il server SQL precedente in modo che gestisca più clienti.

9.4.2. Esercizio 2

Scrivere l’applet Java di e-commerce presentata negli esercizi del capitolo JDBC in modo che funzioni con il server RMI dell’esercizio precedente.