19.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.

19.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:

  1. alle kildetekster oversættes til bytekode: javac *.java (eller i et udviklingsværktøj)

  2. KontoImpl skal have en RMI-stub og en RMI-skel: rmic KontoImpl.java

  3. rmiregistry startes i et separat vindue (fra samme katalog, som bytekoden ligger i): rmiregistry

  4. til sidst kan Kontoserver startes fra et separat vindue: java Kontoserver (eller i et udviklingsværktøj)

19.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();
    }
  }
}

Resultatet bliver:


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.

Figur 19-2. Java