ESWE1P1 Opdracht 4: Distributed programmeren met Java en RMI.

© John Visser, Arjan Besjis en Harry Broeders.

RMI.

In deze practicum opdracht maak je gebruik van Java Remote Method Invocation, dit is een technologie waarmee een cliënt een remote object op een server kan benaderen. De methoden van dit serverobject kunnen dus vanuit een andere JVM (Java Virtuele Machine) worden aangeroepen. Om de communicatie tussen de server en de client werkend te krijgen wordt gebruik gemaakt van stub en skeleton objecten. De werking van RMI is behandeld bij ESWE1C2. Meer informatie over RMI kun je vinden op:

In de onderstaande figuur worden de stappen getoond, die uitgevoerd moeten worden om een RMI client- en serverclass te maken. Hierbij wordt gebuik gemaakt van de Java omgeving die je ook bij opdracht 3 hebt gebruikt.

We zullen aan het einde van deze opdracht ook het producer/consumer programma uit opgave 3 met behulp van RMI over verschillende machines verdelen.

RMI Stap voor stap.

Met behulp van het volgende stappenplan kun je methods van een object op de ene (server) machine aanroepen vanaf een andere (client) machine.

  1. De remote interface definiëren.
    De serverclass moet zijn services via een remote interface definiëren. Dit gebeurt door de java.rmi.Remote interface uit te breiden. Elke method in een remote interface moet een java.rmi.RemoteException kunnen gooien. Als voorbeeld gebruiken we een distributed applicatie waarbij een client een nummer ophaalt van de server. De remote interface kan dan als volgt gedefinieerd worden:
    import java.rmi.*;
    
    public interface RemoteInt extends Remote {
       public int haalnr() throws RemoteException;
    }
    
  2. De remote interface implementeren.
    Je moet vanzelfsprekend een Java serverclass maken die de remote interface implementeert. Deze class moet je afleiden van java.rmi.UnicastRemoteObject. In de constructor wordt het gecreëerde object op de server (localhost) gekoppeld aan de naam die meegegeven wordt bij het aanroepen van de constructor. Dit gebeurt in de aanroep: Naming.rebind(s, this);
    import java.rmi.*;
    import java.rmi.server.*;
    import java.net.*;
    
    public class RemoteImp extends UnicastRemoteObject implements RemoteInt {
       public RemoteImp(String naam, int a) throws RemoteException, MalformedURLException {
          Naming.rebind("rmi://localhost/"+naam, this);
          nr=a;
       }
       public int haalnr() throws RemoteException {
          return nr;
       }
       private int nr;
    }
    
  3. De serverclass compileren.
    Als je de bin directory van de j2sdk in je PATH environment variabele opneemt kun je de java tools eenvoudig vanuit een DOS box starten.
    cd vul-hier-je-werkdirectory-in
    set PATH=c:\program files\java\jdk-vul-hier-de-versie-in\bin\;"%PATH%"
    javac RemoteImp.java
    
  4. Run de stub compiler.
    RMI levert een stubcompiler, rmic genoemd. Je moet rmic gebruiken om de client-stub en het server-skeleton voor de remote class te genereren. De stub wordt in de client applicatie gebruikt als stand-ins (proxy) voor het remote object. De stub verzendt remote aanroepen naar de server. Het skeleton1 ontvangt de aanroep, leest de parameters en roept vervolgens het door jou gedefinieerde remote object aan. Je runt rmic door de volgende opdracht te typen:
    rmic RemoteImp
    

    De rmic stubcompiler gebruikt dezelfde argumenten als javac. Zo kun je bijvoorbeeld met het opdrachtregelargument -classpath de locatie van je classes aangeven. De gegenereerde stub en skeleton classes worden in de actieve directory geplaatst, tenzij je met de optie -d een andere locatie opgeeft.

    1 In Java 1.1 is een aparte skeleton nodig. Vanaf Java 1.2 (ook wel Java 2 genoemd) is een aparte skeleton niet meer nodig. Bij Java 2 wordt aan de serverkant ook (net zoals aan de clientkant) gebruik gemaakt van de stub. Aan de serverkant wordt de benodigde skeleton code dan dynamisch (tijdens run-time) aangemaakt zodra deze code nodig is. Deze skeleton code wordt afgeleid van de stub. In Java 2 is het ook mogelijk om aan de client kant te verwijzing naar de stub die op de server staat (net zoals bij een applet). De client code zal dan zelf de stub van de server ophalen. Details kun je vinden op: http://developer.java.sun.com/developer/onlineTraining/rmi/RMI.html. Als je het commando rmic -v1.2 RemoteImp gebruikt wordt alleen de stub gegenereerd.

  5. Start de RMI-registry op de server.
    RMI definieert interfaces voor een niet-permanente naamgevingservice, registry genoemd. RMI heeft een remote objectimplementatie van deze registry. Je kunt er serverobjecten mee ophalen en registreren en daarbij eenvoudige namen gebruiken. Elk serverproces kan zijn eigen registry ondersteunen of je kunt een enkele standalone registry hebben die alle virtuele machines in een servernode ondersteund. Je start een registry-object onder windows door de volgende opdracht te typen:
    start rmiregistry
    

    start is een DOS commando waarmee een applicatie in een nieuw DOS venster wordt gestart. Je moet het registry starten in het directory waar de sceleton staat (<= Java 1.1) of de stub staat (>=1.2).

    De registry-database is leeg als de server wordt gestart. Je moet de registry zelf vullen met de remote objecten die je maakt. Het rmiregistry programma moet blijven draaien.

  6. Start de serverobjecten.
    Je moet de serverclass laden en vervolgens een remote object maken. We gebruiken hiervoor de class Server.
    public class Server {
       public static void main(String args[]) {
          int a=Integer.parseInt(args[0]); 
          try {
             RemoteImp nt=new RemoteImp("remobj", a);
             System.out.println("Remote object geinstalleerd.");
          } 
          catch (Exception e) {
             System.out.println(e.getMessage());
          }
       }
    }
    

    Je kunt nu als volgt (in een nieuwe DOS box) een remote object aanmaken met de waarde 123:

    javac Server.java
    start java.exe Server 123
    

    Dit programma moet ook blijven draaien.

    
      
  7. Registreer de remote objecten bij de registry.
    Je moet het remote object bij de RMI -registry registreren, zodat de cliënt dit object kan bereiken. Je gebruikt de method bind of rebind van de class java.rmi.Naming om een naam aan een remote object te verbinden. Deze naam wordt opgeslagen in de RMI registry. Dit gebeurt in de constructor van RemoteImp. Het remote object kan nu door de client worden aangeroepen.
    
      
  8. Schrijf de clientcode.
    De client bestaat voor het grootste gedeelte uit gebruikelijke Java code. Je moet de class java.rmi.Naming gebruiken om een remote object te lokaliseren. Je roept vervolgens via een stub die als proxy voor het remote object dient de methoden ervan aan. Deze stub is door rmic gegenereerd.
    import java.rmi.*;
    
    public class Client {
       public static void main(String args[]) {
          try {
             RemoteInt obj =(RemoteInt)Naming.lookup("rmi://"+args[0]+"/remobj");
             System.out.println("nr = "+obj.haalnr());
          }
          catch (Exception e) { 
             System.out.println(e.getMessage());   
          }
       }
    }
    
  9. Compileer de clientcode.
    Compileer de client:
    javac Client.java
    
  10. Start de client.
    RMI heeft ook securiry hooks die het mogelijk maken dat de client stub-classes op verzoek vanaf de server worden geladen. Je kunt de client als volgt ook op dezelfde machine als de server starten.
    java Client vul-hier-het-ip-adres-van-de-server-in
    

Opdracht 4a.

Haal de files Client.java, Server.java, RemoteImp.java en RemoteInt.java op.

Compileer de java files en maak de benodigde stub's aan. Ga hierbij volgens het bovengenoemde stappenplan te werk.

Run de server en de client eerst op 1 machine (je kunt dan localhost of 127.0.0.1 als ip adres van de server gebruiken. Laat daarna de server en de client op verschillende machines lopen. Verwijder alle overbodige files van de server- en de clientmachine!

Opdracht 4b.

Het boundedbuffer programma uit opgave 3 kunnen we met behulp van RMI over meerdere machines verdelen.

Definieer een remote class RemoteBoundedBuffer die de volgende interface implementeerd:

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface BoundedBuffer extends Remote {
	public void put(char c) throws InterruptedException, RemoteException;
	public char get() throws InterruptedException, RemoteException;
}

De class RemoteBoundedBuffer kan als volgt gedefinieerd worden:

public class RemoteBoundedBuffer extends UnicastRemoteObject implements BoundedBuffer {
// ... rest nog invullen ...
}

Schrijf ook een main functie waarin twee objecten van het type RemoteBoundedBuffer worden geregistreerd bij het RMI registry.

Schrijf een class Producer die via RMI karakters naar een RemoteBoundedBuffer kan sturen. Schrijf ook een class Consumer die via RMI karakters uit en RemoteBoundedBuffer kan lezen. Start verschillende Consumer objecten en Producer objecten op verschillende machines. Werkt de synchronisatie nog steeds goed?