18 RMI - objekter over netværk

Indhold:



Kapitlet forudsættes ikke i resten af bogen.

Forudsætter kapitel 11, Interfaces, kapitel 17, Serialisering og kendskab til netværk.



RMI (Remote Method Invocation) er en måde at arbejde med objekter der eksisterer i en anden Java virtuel maskine (ofte på en anden fysisk maskine), som om de var lokale objekter.


18.1 Principper

Herunder er tegnet, hvad der sker, når en klient på maskine A laver et kald til et serverobjekt (da: værts-objekt), der er i maskine B.


Serverobjektet findes slet ikke på maskine A, i stedet er der en såkaldt RMI-stub, der repræsenterer det. Når der sker et kald til RMI-stubben på maskine A, sørger den for at transportere kaldet og alle parametre til maskine B, hvor serverobjektet bliver kaldt, som om det var et lokalt kald. Serverobjektets svar bliver transporteret tilbage til RMI-stubben, der returnerer det til klienten.

Denne proces foregår helt automatisk og er usynlig for klienten såvel som serverobjektet.


RMI benytter serialisering til at transportere parametre og returværdi mellem maskinerne, så man skal huske, at alle objekter, der sendes over netværket, skal implementere Serializable-interfacet, og at variabler, der ikke skal overføres, skal mærkes med nøgleordet transient.


Overordnet set foregår det på den måde, at der defineres et interface til de metoder på serverobjektet, der skal være tilgængelige for klienten.


18.2 Konkret

Konkret kunne man tænke sig, at serveren havde et konto-objekt, hvor man kan overføre penge, spørge om saldo og få bevægelserne på kontoen. Så ville man definere et Konto-interface (her kaldt KontoI):


import java.util.Vector;

public interface KontoI extends java.rmi.Remote
{
  public void overførsel(int kroner) throws java.rmi.RemoteException;
  public int saldo()                 throws java.rmi.RemoteException;
  public Vector bevægelser()         throws java.rmi.RemoteException;
}


Interfacet skal arve fra interfacet java.rmi.Remote, og alle metoder skal kunne kaste undtagelsen java.rmi.RemoteException.


Interfacet bliver brugt på både klientsiden og serversiden.

18.2.1 På serversiden

På serversiden skal vi implementere Konto-interfacet og programmere den funktionalitet der skjuler sig bag det, i et serverobjekt, som normalt ender på Impl (for at signalere, at det er implementationen af interfacet). Serverobjektet skal arve fra UnicastRemoteObject.


import java.util.Vector;
import java.rmi.server.UnicastRemoteObject;

public class KontoImpl extends UnicastRemoteObject implements KontoI
{
  public int saldo;
  public Vector bevægelser;

  public KontoImpl() throws java.rmi.RemoteException 
  {
    // man starter med 100 kroner
    saldo = 100;
    bevægelser = new Vector();
  }

  public void overførsel(int kroner) throws java.rmi.RemoteException
  {
    saldo = saldo + kroner;
    String s = "Overførsel på "+kroner+" kr. Ny saldo er "+saldo+" kr.";
    bevægelser.addElement(s);
    System.out.println(s);
  }

  public int saldo()                 throws java.rmi.RemoteException
  {
    System.out.println("Der spørges om saldoen. Den er "+saldo+" kr.");
    return saldo;
  }

  public Vector bevægelser()         throws java.rmi.RemoteException
  {
    System.out.println("Der spørges på alle bevægelser.");
    return bevægelser;
  }
}


Nu skal vi oprette et serverobjekt og registrere vores tjeneste under et navn i RMI. Det sker sådan her:


import java.rmi.Naming;
public class Kontoserver
{
  public static void main(String args[]) throws Exception
  {
    KontoI k = new KontoImpl();
    Naming.rebind("rmi://localhost/Kontotjeneste", k);
    System.out.println("Kontotjeneste registreret.");
  }
}


Derudover skal der køre en RMI-navnetjeneste, der holder styr på, hvilke tjenester der udbydes under hvilke navne og formidler kontakten til dem. Det er et lille program, der hedder rmiregistry. Det skal kende definitionen af de klasser, der overføres.


Når vi skal køre vores server sker de i fire trin:



18.2.2 På klientsiden

På klientsiden skal vi slå serverobjektet op i RMI-tjenesten og derefter bruge det objekt, vi får retur, som om det var serverobjektet selv (i virkeligheden er det en RMI-stub, der implementerer KontoI):


import java.util.Vector;
import java.rmi.Naming;

public class Kontoklient
{

public static void main(String[] args) { try { KontoI k =(KontoI) Naming.lookup("rmi://localhost/Kontotjeneste"); k.overførsel(100); k.overførsel(50); System.out.println( "Saldo er: "+ k.saldo() ); k.overførsel(-200); k.overførsel(51); System.out.println( "Saldo er: "+ k.saldo() ); Vector bevægelser = k.bevægelser(); System.out.println( "Bevægelser er: "+ bevægelser ); } catch (Exception e) { System.out.println(e); e.printStackTrace(); } } }

Saldo er: 250
Saldo er: 101
Bevægelser er: [Overførsel på 100 kr. Ny saldo er 200 kr., Overførsel på 50 kr. Ny saldo er 250 kr., Overførsel på -200 kr. Ny saldo er 50 kr., Overførsel på 51 kr. Ny saldo er 101 kr.]



Herunder ses de enkelte klassers funktioner.





Jacob Nordfalk - Objektorienteret programmering i Java - http://javabog.dk