9. JAVA RMI
9.1. Inleiding
We hebben gezien hoe netwerktoepassingen kunnen worden gemaakt met behulp van de communicatietools genaamd sockets. In een client/server-toepassing die op deze tools is gebaseerd, is de verbinding tussen de client en de server het communicatieprotocol dat zij hebben gekozen om met elkaar te communiceren. De twee applicaties kunnen in verschillende talen zijn geschreven: bijvoorbeeld Java voor de client, Perl voor de server of een andere combinatie. Er zijn inderdaad twee afzonderlijke applicaties die met elkaar zijn verbonden via een communicatieprotocol dat beide kennen. Bovendien is de toegang tot het netwerk via sockets niet transparant voor een Java-applicatie: deze moet gebruikmaken van de klasse Socket, een klasse die speciaal is gemaakt om deze communicatietools, de sockets, te beheren.
Met JAVA en RMI (Remote Method Invocation) kunnen netwerktoepassingen met de volgende kenmerken worden gemaakt:
- Client/server-toepassingen zijn Java-toepassingen aan beide uiteinden van de communicatie
- De client kan objecten op de server gebruiken alsof ze lokaal zijn
- De netwerklaag wordt transparant: applicaties hoeven zich geen zorgen te maken over de manier waarop informatie van het ene punt naar het andere wordt getransporteerd.
Dit laatste punt draagt bij aan de draagbaarheid: mocht de netwerklaag van een RMI-toepassing veranderen, dan hoeft de toepassing zelf niet opnieuw te worden geschreven. Alleen de RMI-klassen in de programmeertaal Java moeten worden aangepast aan de nieuwe netwerklaag.
Het principe van een RMI-communicatie is als volgt:
- Een klassieke Java-applicatie wordt geschreven op machine A. Deze fungeert als server. Hiervoor worden bepaalde objecten ‘gepubliceerd’ op machine A, waarop de applicatie draait, en worden ze zo diensten.
- Er wordt een klassieke Java-applicatie geschreven op machine B. Deze zal de rol van client vervullen. De applicatie krijgt toegang tot de objecten/services die op machine A zijn gepubliceerd, dat wil zeggen dat ze deze via een externe verwijzing kan manipuleren alsof ze lokaal zijn. Hiervoor moet de applicatie de structuur kennen van het externe object waartoe ze toegang wil hebben (methoden en eigenschappen).
9.2. Laten we dit aan de hand van een voorbeeld bekijken
De theorie achter de interface RMI is niet eenvoudig. Om er meer duidelijkheid over te krijgen, gaan we stap voor stap het schrijven van een client/server-toepassing volgen met behulp van het Java-pakket RMI. We nemen een toepassing die in veel boeken over RMI te vinden is: de client roept één enkele methode van een object op afstand aan, dat vervolgens een tekenreeks terugstuurt. We presenteren hier een kleine variant: de server geeft door wat de client hem stuurt. We hebben in dit boek al een dergelijke applicatie besproken, die echter op sockets was gebaseerd.
9.2.1. De servertoepassing
9.2.1.1. Stap 1: de interface van het object/de server
Een extern object is een klasse-instantie die de interface Remote moet implementeren, zoals gedefinieerd in het pakket java.rmi. De methoden van het object die op afstand toegankelijk zijn, zijn de methoden die zijn gedeclareerd in een interface die is afgeleid van de interface Remote:
import java.rmi.*;
// de interface op afstand
public interface interEcho extends Remote{
public String echo(String msg) throws java.rmi.RemoteException;
}
Hier wordt dus een interface interEcho gedefinieerd, waarin de methode echo als op afstand toegankelijk wordt aangemerkt. Deze methode kan een uitzondering van de klasse RemoteException genereren, een klasse die alle netwerkgerelateerde fouten omvat.
9.2.1.2. Stap 2: het serverobject schrijven
In de volgende stap definiëren we de klasse die de voorgaande interface op afstand implementeert. Deze klasse moet zijn afgeleid van de klasse UnicastRemoteObject, een klasse die beschikt over methoden waarmee methoden op afstand kunnen worden aangeroepen.
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// klasse die de externe echo implementeert
public class srvEcho extends UnicastRemoteObject implements interEcho{
// constructormethode
public srvEcho() throws RemoteException{
super();
}// einde constructor
// methode die de echo uitvoert
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// einde echo
}// einde klasse
In de vorige klasse vinden we:
- de methode die de echo weergeeft
- een constructor die niets anders doet dan de constructor van de bovenliggende klasse aanroepen. Deze is er om aan te geven dat er een uitzondering van het type RemoteException kan worden gegenereerd.
We gaan een instantie van deze klasse aanmaken met een methode main. Om een object/service van buitenaf toegankelijk te maken, moet het worden aangemaakt en geregistreerd in de directory van extern toegankelijke objecten. Een client die toegang wil krijgen tot een object op afstand, gaat namelijk als volgt te werk:
- hij wendt zich tot de directorydienst van de machine waarop het gewenste object zich bevindt. Deze directorydienst draait op een poort die de klant moet kennen (standaard 1099). De client vraagt de directory om een verwijzing naar een object of dienst waarvan hij de naam opgeeft. Als deze naam overeenkomt met die van een object of dienst in de directory, stuurt de directory de client een verwijzing terug waarmee de client met het object of de dienst op afstand kan communiceren.
- Vanaf dat moment kan de client dit object op afstand gebruiken alsof het lokaal is
Terugkomend op onze server moeten we een object van het type srvEcho aanmaken en dit registreren in de directory van objecten die van buitenaf toegankelijk zijn. Deze registratie gebeurt met de methode rebind van de klasse Naming:
met
naam: de naam die aan het externe object wordt gekoppeld
obj: het externe object
Onze klasse srvEcho ziet er dus als volgt uit:
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// klasse die de echo op afstand implementeert
public class srvEcho extends UnicastRemoteObject implements interEcho{
// constructor
public srvEcho() throws RemoteException{
super();
}// einde constructor
// methode die de echo uitvoert
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// einde echo
// aanmaken van de service
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
}// einde klasse
Als we het vorige programma lezen, krijgen we de indruk dat het onmiddellijk zal stoppen nadat de echo-service is aangemaakt en opgeslagen. Dat is niet het geval. Omdat de klasse srvEcho is afgeleid van de klasse UnicastRemoteObject, blijft het aangemaakte object oneindig lang draaien: het luistert naar verzoeken van clients op een anonieme poort, dat wil zeggen een poort die door het systeem wordt gekozen op basis van de omstandigheden. Het aanmaken van de service verloopt asynchroon: in het voorbeeld maakt de methode main de service aan en gaat vervolgens verder met de uitvoering; er wordt inderdaad „Echoserver gereed“ weergegeven.
9.2.1.3. Stap 3: compileren van de servertoepassing
Op dit moment kunnen we onze server compileren. We compileren het bestand interEcho.java van de interface interEcho en het bestand srvEcho.java van de klasse srvEcho. We krijgen de bijbehorende .class-bestanden: interEcho.class en srvEcho.class.
9.2.1.4. Stap 4: de client schrijven
We schrijven een client waaraan we het bestand URL van de echo-server als parameter doorgeven en die
- een via het toetsenbord ingevoerde regel leest
- deze naar de echo-server stuurt
- het antwoord weergeeft dat de server terugstuurt
- terugkeert naar stap 1 en stopt wanneer de ingevoerde regel „einde” is.
Dit levert de volgende client op:
import java.rmi.*;
import java.io.*;
public class cltEcho {
public static void main(String arg[]){
// syntaxis: cltEcho URLService
// controle van de argumenten
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// client-server-dialoog
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
// toetsenbordstream openen
in=new BufferedReader(new InputStreamReader(System.in));
// lokalisatie van de dienst
serveur=(interEcho) Naming.lookup(urlService);
// lus voor het lezen van berichten die naar de echo-server moeten worden verzonden
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// verzending van het bericht naar de server en ontvangst van het antwoord
reponse=serveur.echo(msg);
// opvolging
System.out.println("Réponse serveur : " + reponse);
// volgend bericht
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// klaar
System.exit(0);
// foutafhandeling
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// main
}// klasse
Er is niets bijzonders aan deze client, behalve de instructie die een referentie van de server opvraagt:
We herinneren ons dat onze echo-service is geregistreerd in de servicelijst van de machine waarop deze draait, met de instructie:
Naming.rebind("srvEcho",serveurEcho);
De client gebruikt dus ook een methode van de klasse Naming om een referentie op te vragen van de server die hij wil gebruiken. De gebruikte lookup-methode accepteert als parameter de URL van de aangevraagde dienst. Deze heeft de vorm van een klassieke URL:
met
rmi: optioneel – RMI-protocol
machine: naam of adres IP van de machine waarop de echo-server draait – optioneel, standaard localhost.
port: luisterpoort van de directorydienst op deze machine – optioneel, standaard 1099
nom_service: naam waaronder de gevraagde dienst is geregistreerd (srvEcho in ons voorbeeld)
Wat wordt opgehaald, is een instantie van de externe interface interEcho. Als we aannemen dat de client en de server zich niet op dezelfde machine bevinden, moet bij het compileren van de client cltEcho.java moet in dezelfde map het bestand interEcho.class aanwezig zijn, het resultaat van de compilatie van de externe interface interEcho; anders treedt er een compilatiefout op bij de regels die naar deze interface verwijzen.
9.2.1.5. Stap 5: het genereren van de .class-bestanden die nodig zijn voor de client-servertoepassing
Om goed te begrijpen wat aan de serverzijde en wat aan de clientzijde gebeurt, plaatsen we de server in de map echo\serveur en de client in de map echo\client.
De map van de server bevat de volgende bronbestanden:
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
Na het compileren van deze twee bronbestanden ontstaan de volgende .class-bestanden:
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
In de map van de klant bevindt zich het volgende bronbestand:
evenals het bestand interEcho.class dat tijdens de compilatie van de server is gegenereerd:
Na het compileren van het bronbestand zijn de volgende .class-bestanden beschikbaar:
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
Als we proberen de client cltEcho uit te voeren, krijgen we de volgende foutmelding:
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
Als men probeert de server srvEcho te starten, krijgt men de volgende foutmelding:
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 beide gevallen geeft de Java-virtuele machine aan dat de klasse srvEcho_stub niet is gevonden. We hebben inderdaad nog nooit van deze klasse gehoord. In de client is de server gelokaliseerd met de volgende instructie:
Hier is urlservice de tekenreeks rmi://localhost/srvEcho met
Rmi: het RMI-protocol
Localhost: de machine waarop de server draait – in dit geval dezelfde machine als waarop de client draait. De syntaxis is normaal gesproken machine:poort. Als de poort ontbreekt, wordt standaard poort 1099 gebruikt. Op deze poort luistert de directorydienst van de server.
srvEcho: dit is de naam van de specifieke service die wordt aangevraagd
Tijdens het compileren werden er geen fouten gemeld. Het bestand interEcho.class van de externe interface moest gewoon beschikbaar zijn.
Tijdens de uitvoering vraagt de virtuele machine om de aanwezigheid van een bestand srvEcho_stub.class als de aangevraagde dienst de dienst srvEcho is, en in het algemeen een bestand X_stub.class voor een dienst X. Dit bestand is alleen nodig bij de uitvoering, niet bij het compileren van de client. Hetzelfde geldt voor de server. Wat is dit bestand dan precies?
Op de server bevindt zich de klasse srvEcho.class, wat ons object/onze externe service is. De client heeft, ook al heeft hij deze klasse niet nodig, toch een soort afspiegeling ervan nodig om ermee te kunnen communiceren. In feite richt de client zijn verzoeken niet rechtstreeks aan het externe object, maar aan zijn lokale afspiegeling srvEcho_stub.class, die zich op dezelfde machine bevindt als hijzelf. Deze lokale afspiegeling srvEcho_stub.class communiceert met een soortgelijke afspiegeling (srvEcho_stub.class) die zich ditmaal op de server bevindt. Dit beeld wordt aangemaakt op basis van het bestand .class op de server met behulp van een Java-tool genaamd rmic. Onder Windows is de opdracht:
zal op basis van het bestand srvEcho.class twee andere bestanden .class genereren:
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
Hier bevindt zich inderdaad het bestand srvEcho_stub.class dat de client en de server nodig hebben voor de uitvoering. Er is ook een bestand srvEcho_Skel.class waarvan de functie op dit moment onbekend is. We maken een kopie van het bestand srvEcho_stub.class in de map van zowel de client als de server en verwijderen het bestand srvEcho_Skel.class. We hebben dus de volgende bestanden:
aan de serverzijde:
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
aan de clientzijde:
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. Stap 6: De client-server-echo-applicatie uitvoeren
We zijn klaar om onze client-server-applicatie uit te voeren. In eerste instantie zullen de client en de server op dezelfde machine draaien. We moeten eerst onze serverapplicatie starten. We herinneren ons dat deze:
- de service aanmaakt
- deze registreert in de servicelijst van de machine waarop de echo-server draait
Voor dit laatste punt is een directorydienst nodig. Deze wordt gestart met het commando:
rmiregistry is de directorydienst. Deze wordt hier op de achtergrond gestart in een Windows-DOS-venster met het commando start. Zodra de directory actief is, kunnen we de echo-service aanmaken en deze registreren in de servicelijst. Ook hier wordt deze op de achtergrond gestart met het commando start:
De echo-server draait in een nieuw venster DOS en geeft weer wat er van hem gevraagd werd:
Nu hoeven we alleen nog maar onze client te starten en te testen:
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. De client en de server op twee verschillende machines
In het vorige voorbeeld stonden de client en de server op dezelfde computer. We plaatsen ze nu op verschillende computers:
- de server op een Windows-computer
- de client op een Linux-computer
De server wordt net als eerder op de Windows-computer gestart. Op de Linux-computer zijn de .class-bestanden van de client overgezet:
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
De client is gestart:
$ 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
Er is dus een fout opgetreden: de Java-virtuele machine vraagt blijkbaar om het bestand srvEcho_skel.class, dat was aangemaakt door het hulpprogramma rmic, maar dat tot nu toe nog niet was gebruikt. We maken het opnieuw aan en zetten het ook over naar de Linux-machine:
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
We krijgen dezelfde foutmelding als eerder... Dus denken we even na en lezen we de documentatie over RMI nog eens door. Uiteindelijk komen we tot de conclusie dat het misschien de server zelf is die het genoemde bestand srvEcho_Skel.class nodig heeft. We starten vervolgens op de Windows-machine de server opnieuw op, met de twee bestanden srvEcho_Stub.class en srvEcho_Skel.class aanwezig, evenals :
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
Vervolgens testen we de client opnieuw op de Linux-machine en deze keer werkt het:
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
Hieruit volgt dus dat aan de serverzijde de twee bestanden srvEcho_Stub.class en srvEcho_Skel.class aanwezig moeten zijn. Aan de clientzijde was tot nu toe alleen het bestand srvEcho_Stub.class nodig. Dit bleek onmisbaar toen de client en de server op dezelfde Windows-machine stonden. Onder Linux verwijderen we het om te zien wat er gebeurt...
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
We hebben hier een interessante foutmelding die lijkt aan te geven dat de Java-virtuele machine de bekende klasse stub wilde laden, maar dat dit mislukte bij gebrek aan een 'security manager'. We herinneren ons dat we hierover iets hebben gelezen in de documentatie. We duiken er nog eens in… en ontdekken dat de server een ‘security manager’ moet aanmaken en installeren die aan clients die het laden van klassen aanvragen, garandeert dat deze klassen veilig zijn. Zonder deze ‘security manager’ is het laden van klassen onmogelijk. Dat lijkt te kloppen: onze Linux-client heeft de klasse srvEcho_stub.class, die hij nodig heeft, bij de server opgevraagd en deze heeft dit geweigerd met de mededeling dat er geen security manager was geïnstalleerd. We passen de code van de functie main op de server daarom als volgt aan:
// opzetten van de dienst
public static void main (String arg[]){
// installatie van een beveiligingsbeheerder
System.setSecurityManager(new RMISecurityManager());
// de service starten en registreren
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 ");
}
}// hoofdpagina
We compileren en genereren de bestanden srvEcho_stub.class en srvEcho_Skel.class met de tool rmic. We starten de directory-service (rmiregistry) en vervolgens de server, waarna we een foutmelding krijgen die we voorheen niet hadden!
Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve) lors du lancement du serveur d’écho
De beveiligingsmanager lijkt te streng te zijn geweest. We lezen de documentatie nog eens door... We komen erachter dat wanneer een beveiligingsmanager actief is, bij het starten van een programma de rechten moeten worden gespecificeerd. Dit doe je met de volgende optie:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
waarbij
java.security.policy een trefwoord is
mypolicy is een tekstbestand waarin de rechten van het programma zijn gedefinieerd. In dit geval is dat het volgende:
Het programma heeft hier alle rechten.
We beginnen opnieuw. We gaan naar de map van de server en voeren achtereenvolgens het volgende uit:
- de directorydienst starten: start j:\jdk12\bin\rmiregistry
- de server starten: start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
En deze keer start de echo-server (de client nog niet) correct op. Nu kun je het volgende experiment uitvoeren:
- stop de echo-server en vervolgens de directory-service
- start de directory-service opnieuw op vanuit een andere map dan die van de server
- ga terug naar de map van de server start de echo-server – je krijgt de volgende foutmelding:
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
Hieruit kunnen we afleiden dat de map van waaruit de directory-service wordt gestart, van belang is. In dit geval heeft Java de klasse srvEcho_stub.class niet gevonden omdat de directory-service niet vanuit de servermap is gestart. Bij het opstarten van de server kan worden aangegeven in welke map de klassen staan die de server nodig heeft:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Het commando staat op één regel. Het trefwoord java.rmi.server.codebase wordt gebruikt om de URL van de map aan te geven die de klassen bevat die de server nodig heeft. Hier specificeert deze URL het file-protocol, dat het protocol is voor toegang tot lokale bestanden, en de map met de .class-bestanden van de server. Als we dus als volgt te werk gaan:
- de directorydienst stoppen
- de directorydienst opnieuw starten vanuit een andere map dan die van de server
- in de map op de server de directorydienst starten met het commando (één regel):
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
De server is nu correct gestart. We kunnen dus verdergaan met de client. We testen deze:
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
We krijgen dezelfde foutmelding die aangeeft dat er geen beveiligingsbeheerder is. We denken dat we ons misschien vergist hebben en dat de client zelf zijn beveiligingsbeheerder moet aanmaken. We laten de server met zijn beveiligingsbeheerder staan, maar maken er ook een aan voor de client. De main-functie van de client cltEcho.java wordt dan:
public static void main(String arg[]){
// syntaxis: cltEcho machinepoort
// machine: machine waarop de echo-server draait
// poort: poort waarop de dienstenlijst op de machine van de echo-service draait
// controle van de argumenten
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// installatie van een beveiligingsbeheerder
System.setSecurityManager(new RMISecurityManager());
// client-servercommunicatie
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
....
} catch (Exception e){
....
}// probeer
}// main
Vervolgens gaan we als volgt te werk:
- cltEcho.java wordt opnieuw gecompileerd
- de .class-bestanden worden overgezet naar de Linux-machine
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
- we starten de 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
Ondanks de schijn boeken we vooruitgang: de foutmelding is niet meer dezelfde. We zien dat de client de klasse srvEcho_Stub.class bij de server heeft kunnen opvragen, maar dat deze deze niet heeft gevonden. De client moet dus over een beveiligingsmanager beschikken om klassen bij de server te kunnen opvragen.
Als we naar de vorige foutmelding kijken, zien we dat er in de map e:/data/java/rmi/echo/server/ naar het bestand srvEcho_Stub.class is gezocht en dat het niet is gevonden. Toch staat het daar wel degelijk. Als we de lijst met methoden die bij de fout betrokken zijn nader bekijken, vinden we deze: sun.net.www.protocol.file.FileURLConnection.getInputStream. De client lijkt een stream te hebben geopend met een object van het type FileURLConnection. We vermoeden dat dit alles te maken heeft met de manier waarop we onze server hebben gestart:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
De foutmelding lijkt te verwijzen naar de waarde van het trefwoord java.rmi.server.codebase. Als we de documentatie nog eens bekijken, zien we dat de waarde van dit sleutelwoord in de gegeven voorbeelden altijd is: http://.., c.a.d. Het gebruikte protocol is http. Het is niet duidelijk hoe de client zijn klassen bij de server opvraagt en ontvangt. Misschien vraagt hij ze aan met de URL van het trefwoord java.rmi.server.codebase, URL, dat bij het opstarten van de server is opgegeven. We besluiten daarom de server te starten met de volgende nieuwe opdracht:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Het protocol is nu http. We moeten de bestanden .class verplaatsen naar een locatie die toegankelijk is voor de http-server van de machine waarop de klassen zullen worden opgeslagen. In ons voorbeeld draait de server op een Windows-machine met een http-server van Microsoft. De root van deze server is d:\Inetpub\wwwroot. We gaan dus als volgt te werk:
- we maken de map d:\Inetpub\wwwroot\rmi\echo aan
- we plaatsen daarin de bestanden .class van de server en het bestand mypolicy
- start de webserver, als dat nog niet is gebeurd
- start de directorydienst (rmiregistry) opnieuw op
- start de server opnieuw met het commando
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
- op de Linux-machine start men de 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
Oef! Het werkt. De client heeft het beruchte bestand srvEcho_Stub.class inderdaad weten op te halen.
Dit alles heeft ons op ideeën gebracht, en we vragen ons af of de client die op de Windows-server draait, ook zou werken zonder het bestand srvEcho_Stub.class. We gaan naar de map van de client, verwijderen het bestand srvEcho_Stub.class als dat daar staat en starten de client op dezelfde manier op als onder 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é
Windows-serverzijde:
- de server heeft een beveiligingsbeheerder
- de server is gestart met de volgende opties: start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Aan de Linux- of Windows-clientzijde
- heeft de client een beveiligingsmanager
- op Linux is deze gestart met java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
- op Windows is deze gestart met j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
9.2.1.9. Echo-server op Linux, clients op Windows en Linux
We verplaatsen de server nu naar een Linux-machine en testen Linux- en Windows-clients. De te volgen procedure is als volgt:
- we verplaatsen de bestanden .class van de server naar de Linux-machine
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
- Omdat de klasse srvEcho_Stub.class door klanten zal worden opgevraagd, is de map die voor de serverklassen is gekozen een map die toegankelijk is voor de HTTP-server van de Linux-machine. In dit geval is de URL in deze map http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur
- de directorydienst wordt op de achtergrond gestart: /usr/local/bin/jdk/rmiregistry &
- de server wordt op de achtergrond gestart: /usr/local/bin/jdk/bin/java
-Djava.rmi.server.codebase=http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur/
srvEcho &
We kunnen de clients nu testen. Eerst de Windows-client.
- We gaan naar de map van de client op de Windows-machine
- we starten de client met het commando:
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
We testen de Linux-client:
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
Let op: voor de Linux-client die op dezelfde machine draait als de echo-server, hoefde er geen machine te worden opgegeven in de URL van de aangevraagde dienst.
9.3. Tweede voorbeeld: SQL-server op een Windows-machine
9.3.1. Het probleem
In hoofdstuk JDBC hebben we gezien hoe relationele databases worden beheerd. In de gepresenteerde voorbeelden stonden de applicaties en de gebruikte database op dezelfde Windows-computer. Hier stellen we voor om een RMI-server op een Windows-computer te schrijven, waarmee externe clients gebruik kunnen maken van de openbare ODBC-databases van de computer waarop de server draait.

De RMI-client zou drie bewerkingen kunnen uitvoeren:
- verbinding maken met een database naar keuze
- SQL-verzoeken verzenden
- de verbinding verbreken
De server voert de SQL-verzoeken van de client uit en stuurt de resultaten terug. Dit is zijn belangrijkste taak en daarom noemen we hem een SQL-server.
We passen de verschillende stappen toe die we eerder bij de echo-server hebben gezien.
9.3.2. Stap 1: de externe interface
De externe interface is de interface die de methoden van de RMI-server weergeeft die toegankelijk zullen zijn voor RMI-clients. We gebruiken de volgende interface:
import java.rmi.*;
// de externe interface
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;
}
De verschillende methoden hebben de volgende functies:
Connect: de client maakt verbinding met een externe database waarvan hij de pilote, de url JDBC, zijn gebruikersnaam id en zijn wachtwoord mdp opgeeft om toegang te krijgen tot deze database. De server retourneert een tekenreeks die het resultaat van de verbinding aangeeft:
executeSQL: de client vraagt om het uitvoeren van een query SQL op de database waarmee hij is verbonden. Hij geeft het teken aan dat de velden moet scheiden in de resultaten die naar hem worden teruggestuurd. De server stuurt een array met tekenreeksen terug:
voor een verzoek tot het bijwerken van de database, waarbij n het aantal bijgewerkte rijen is
als de aanvraag een fout heeft gegenereerd
als de aanvraag geen resultaat heeft opgeleverd
als de query resultaten heeft opgeleverd. De regels die de server op deze manier terugstuurt, zijn de resultaten van de query.
close: de client verbreekt de verbinding met de externe database. De server stuurt een tekenreeks terug die het resultaat van deze verbreking aangeeft:
9.3.3. Stap 2: Servercode
Hieronder volgt de Java-broncode van de server SQL. Om deze te begrijpen, moet men bekend zijn met het beheer van databases via JDBC en met de opbouw van servers via RMI. De opmerkingen in het programma zouden het begrip ervan moeten vergemakkelijken.
// geïmporteerde pakketten
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;
// klasse srvSQL
public class srvSQL extends UnicastRemoteObject implements interSQL{
// globale gegevens van de klasse
private Connection DB;
// ------------- constructor
public srvSQL() throws RemoteException{
super();
}
// --------------- verbinding
public String connect(String pilote, String url, String id,
String mdp) throws RemoteException{
// verbinding met de database-URL via de driver
// authenticatie met gebruikersnaam id en wachtwoord mdp
String resultat=null; // resultaat van de methode
try{
// het laden van de driver
Class.forName(pilote);
// verbindingsverzoek
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion réussie";
} catch (Exception e){
// fout
resultat="500 Echec de la connexion (" + e + ")";
}
// einde
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur)
throws RemoteException{
// voert een query SQL uit op de database DB
// en plaatst de resultaten in een array van strings
// gegevens die nodig zijn voor de uitvoering van de query
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// aanmaken van de querycontainer
S=DB.createStatement();
// uitvoering van de query
if (! S.execute(requete)){
// updatequery
// het aantal bijgewerkte rijen wordt teruggestuurd
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// dit was een opvraagquery
// de resultaten worden opgehaald
RS=S.getResultSet();
// aantal velden in de Resultset
int nbChamps=RS.getMetaData().getColumnCount();
// deze worden verwerkt
while(RS.next()){
// de resultatenrij wordt aangemaakt
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// toevoegen aan de resultatenvector
resultats.addElement(ligne);
}// while
// einde van de verwerking van de resultaten
// de bronnen worden vrijgegeven
RS.close();
S.close();
// de resultaten worden teruggestuurd
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){
// fout
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- sluiten
public String close() throws RemoteException {
// sluit de verbinding met de database
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// het resultaat teruggeven
return resultat;
}
// ----------- main
public static void main (String[] args){
// beveiligingsbeheerder
System.setSecurityManager(new RMISecurityManager());
// start van de service
srvSQL serveurSQL=null;
try{
// aanmaken
serveurSQL=new srvSQL();
// registratie
Naming.rebind("srvSQL",serveurSQL);
// opvolging
System.out.println("Serveur SQL prêt");
} catch (Exception e){
// fout
System.err.println("Erreur lors du lancement du serveur SQL ("+ e +")");
}// try-catch
}// main
}// klasse
9.3.4. Het schrijven van de client RMI
De client van de server RMI wordt aangeroepen met de volgende parameters:
urlserviceAnnuaire: url RMI van de directorydienst die de server SQL heeft geregistreerd
driver: driver die de server SQL moet gebruiken om de database te beheren
urlBase: URL JDBC van de te beheren database
id: identiteit van de klant of null indien geen identiteit
mdp: wachtwoord van de klant of null indien geen wachtwoord
scheidingsteken: teken dat de server SQL moet gebruiken om de velden in de resultatenregels van een query van elkaar te scheiden
Hier volgt een voorbeeld van mogelijke parameters:
met hier:
RMI-naam van de server SQL
de standaarddriver voor databases met ODBC-interface
om een artikeldatabase te gebruiken die is opgenomen in de lijst met openbare databases ODBC van de Windows-machine
geen identiteit
geen wachtwoord
de velden in de resultaten worden gescheiden door een komma
Zodra de client met de bovenstaande instellingen is gestart, doorloopt hij de volgende stappen:
- hij maakt verbinding met de server RMI srvSQL, dus een server RMI op dezelfde machine als de client
- hij vraagt om verbinding met de artikel-database
- hij vraagt de gebruiker om een query SQL via het toetsenbord in te voeren
- hij stuurt deze naar de server SQL
- het toont de door de server teruggestuurde resultaten op het scherm
- het vraagt de gebruiker opnieuw om een zoekopdracht SQL via het toetsenbord in te voeren. Het stopt wanneer de zoekopdracht is voltooid.
Hieronder volgt de Java-code van de client. De opmerkingen zouden voldoende moeten zijn om deze te begrijpen.
import java.rmi.*;
import java.io.*;
public class cltSQL {
// globale gegevens van de klasse
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[]){
// syntaxis: cltSQL urlServiceAnnuaire scheidingsteken driver url id wachtwoord
// urlServiceAnnuaire: URL van de te contacteren dienstenlijst RMI
// driver: driver die moet worden gebruikt voor de te gebruiken database
// urlBase: JDBC-URL van de te gebruiken database
// id: gebruikers-ID
// mdp: het wachtwoord
// scheidingsteken: teken dat de velden in de resultaten van een query van elkaar scheidt
// controle van het aantal argumenten
if(arg.length!=6)
erreur(syntaxe,1);
// initieer parameters voor de verbinding met de 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];
// installatie van een beveiligingsmanager
System.setSecurityManager(new RMISecurityManager());
// client-servercommunicatie
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// toetsenbordstream openen
in=new BufferedReader(new InputStreamReader(System.in));
// monitoring
System.out.println("--> Connexion au serveur RMI en cours...");
// lokalisatie van de dienst
serveurSQL=(interSQL) Naming.lookup(urlService);
// opvolging
System.out.println("--> Connexion à la base de données en cours");
// eerste verbindingsverzoek met de database
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// opvolging
System.out.println("<-- "+reponse);
// analyse van het antwoord
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// lus voor het lezen van de verzoeken die naar de server SQL moeten worden verzonden
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// verzending van de verzoek naar de server en ontvangst van het antwoord
lignes=serveurSQL.executeSQL(requete,separateur);
// opvolging
afficheLignes(lignes);
// volgend verzoek
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// terwijl
// gevolgd door
System.out.println("--> Fermeture de la connexion à la base de données distante");
// de verbinding wordt verbroken
reponse=serveurSQL.close();
// vervolg
System.out.println("<-- " + reponse);
// einde
System.exit(0);
// foutafhandeling
} 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
// ------------ fout
private static void erreur(String msg, int exitCode){
// foutmelding weergeven
System.err.println(msg);
// eventuele vrijgave van bronnen
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// afsluiten
System.exit(exitCode);
}// fout
}// klasse
9.3.5. Stap 3: het aanmaken van de .class-bestanden
- de server is gecompileerd
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
- de bestanden Stub en Skel worden aangemaakt
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
- de bestanden interSQL.class, srvSQL_Stub.class en srvSQL_Skel.class worden overgebracht naar de map van de klant
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
- de client wordt gecompileerd
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. Stap 4: Testen met server en client op dezelfde Windows-machine
- de directory-service wordt gestart in een andere map dan die van de server en de client
- het volgende mypolicy-bestand wordt in de mappen van zowel de client als de server geplaatst
- start de 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
- de client wordt gestart
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
--> Query: select naam, stock_actu from artikelen order by stock_actu desc
<-- 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 artikelen set stock_actu=actuele_voorraad-1 where stock_actu<=11
<-- 100 5
--> Query: select naam,stock_actu from artikelen 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. Stap 5: Testen met een server op een Windows-computer en een client op een Linux-computer
- eventueel worden de server en de directory-service gestopt
- de .class-bestanden van de client worden overgezet naar een Linux-computer
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
- De bestanden van de server worden in een map geplaatst die toegankelijk is voor de HTTP-server van de Windows-machine
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
- de telefoongidsdienst wordt hervat
- de server wordt opnieuw opgestart met andere instellingen dan die welke bij de vorige test werden gebruikt
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
- de client wordt op de Linux-machine gestart
/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 naam,stock_actu,stock_mini from artikelen order by naam
<-- 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 artikelen set stock_actu=stock_mini where stock_mini<=7
<-- 100 4
--> Query: select naam,stock_actu,stock_mini uit artikelen, gesorteerd op naam
<-- 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. Conclusie
Dit is een interessante toepassing omdat deze toegang biedt tot een database vanaf elke willekeurige werkplek in het netwerk. We hadden deze ook op de traditionele manier kunnen schrijven met sockets, wat overigens wordt gevraagd in een oefening in het hoofdstuk over databases. Als we deze toepassing op de traditionele manier zouden schrijven:
- zouden we een client en een server hebben die in verschillende talen geschreven zouden kunnen zijn
- zouden de client en de server communiceren door tekstregels uit te wisselen en zouden ze een dialoog voeren die er ongeveer zo uit zou kunnen zien:
waarbij de eerste twee parameters aangeven waar de server te vinden is, en de vier volgende de verbindingsparameters voor de te gebruiken database aangeven
De server zou kunnen antwoorden met iets in de trant van:
om de server te vragen een query SQL uit te voeren op de database waarmee de client is verbonden. 'separator' is het scheidingsteken tussen de velden in de regels van het antwoord.
De server zou bijvoorbeeld het volgende kunnen antwoorden
bij een verzoek om de database bij te werken, waarbij n het aantal bijgewerkte rijen is
als het verzoek een fout heeft gegenereerd
als de zoekopdracht geen resultaten heeft opgeleverd
als de zoekopdracht resultaten heeft opgeleverd. De regels die de server op deze manier terugstuurt, zijn de resultaten van de zoekopdracht.
om de verbinding met de externe database te verbreken. De server kan een tekenreeks terugsturen die het resultaat van deze verbreking aangeeft:
Hieruit blijkt dat als men in staat is een traditionele applicatie te bouwen met een protocol zoals hierboven beschreven, men daaruit een mogelijke structuur van de server RMI kan afleiden. Waar in het protocol een zin van de client naar de server voorkomt van het type:
zou er binnen de server RMI een methode kunnen zijn
en deze methode, die toegankelijk is voor de client, moet dus deel uitmaken van de gepubliceerde interface van de server.
Tot slot merken we op dat onze server momenteel slechts één client ondersteunt: in de huidige vorm kan hij er niet meerdere aan. Als een client namelijk verbinding maakt met een database B1, wordt door de server een object Connection DB=DB1 aangemaakt. Als een tweede client een verbinding aanvraagt met een database B2, noteert de server dit als Connection DB=DB2, waardoor de verbinding van de eerste client met de database B1 wordt verbroken.
9.4. Oefeningen
9.4.1. Oefening 1
Breid de vorige server SQL uit, zodat deze meerdere clients kan beheren.
9.4.2. Oefening 2
Schrijf de Java-applet voor e-commerce die in de oefeningen van hoofdstuk JDBC wordt behandeld, zodat deze werkt met de server RMI uit de vorige oefening.