RMI är ett sätt för processer att kommunicera mellan varandra över nätverket, fast istället för att vi skickar objekt eller meddelanden anropar vi metoder. Simpelt sagt anropar klienten en metod som körs på serven, vilket sedan skickar tillbaka vårt resultat. På klient-sidan ser det ut som om vi anropar en lokal metod, men i själva verket skickas parametrar och resultat över nätverket automagiskt! Detta kräver dock att resultat och parametrar är av typen Serializable, Remote eller primära datatyper.
Liksom i all nätverksprogrammering behöver vi ett protokoll, eftersom det är metoder det handlar om i detta fall behöver vi skapa ett fjärr-interface som deklarerar de metoder vi vill använda oss av mellan server och klient. Klienten använder interface-klassen för att anropa servens metoder, och serven använder interface-klassen för att implementera metoderna.
För att en klient ska kunna hitta serven använder RMI ett speciellt register där vi kan registrera och hämta referenser till våra fjärr-interface. Detta register är en server man kan starta antingen manuellt genom att köra "rmiregistry" eller via LocateRegistry.createRegistry(...) metoden. Register serven behöver inte köras på samma maskin som varken klient eller server.
Vi kan skapa en klient/server lösning med RMI där serven räknar ut ett tals fakultet som ett exempel nedan.
Vi börjar med att skapa en interface klass "Protocol" som deklarerar metoden "kalkyleraFakultet":
// File: fakultet/Protocol.java
import java.rmi.Remote; import java.rmi.RemoteException; public interface Protocol extends Remote { public Long kalkyleraFakultet( Integer tal ) throws RemoteException; }
Genom att ärva "Remote" identifierar vi vår Protocol som ett interface vars metoder kan bli anropade ifrån en annan java applikation. Vi måste även se till att alla metoder som ska gå att fjärr-anropa deklareras med throws RemoteException - detta eftersom RMI-systemet ska kunna meddela oss om något gick fel i kommunikationen t.ex. att anslutningen bröts.
Nästa steg är att implementera "serven". Detta kan göras på många olika sätt, ett av dessa är att låta serven implementera remote interface-klassen rakt av - men man skulle lika gärna kunna implementera denna som en extern eller nästlad klass. För att hålla detta simpelt väljer jag att implementera klassen rakt av i serven, vi får något liknande detta:
// File: fakultet/Server.java
import java.rmi.*; import java.rmi.registry.*; import java.rmi.server.*; public class Server implements Protocol { // Vår implementering av Protocol-interfacet, returnerar // fakulteten för ett angivet tal. public Long kalkyleraFakultet( Integer tal ){ int i = tal; Long res = 1l; while( i > 1 ){ res = res * i--; } return res; } public static void main( String[] args ) throws Exception { // Starta upp ett register över Remote-objekt om // inget redan körs. Vi använder standard porten 1099 try { LocateRegistry.createRegistry(1099); System.out.println("RMI register skapad, lyssnar på port 1099."); } catch (RemoteException e) { System.out.println("RMI register körs redan."); } // Instansiera vårt "protokoll" Protocol server = new Server(); // Börja acceptera klienter på system-vald port Protocol remote = (Protocol) UnicastRemoteObject.exportObject(server, 0); // Spara vår server i registret så klienter kan hitta denna Naming.rebind("//localhost/kalkylator",remote); // Här slutar main-tråden, men RMI fortsätter att leva vidare! System.out.println("Servern är nu registrerad på '//localhost/kalkylator'."); } }
OBS! Ingen felhantering utförs i exemplet, kastas exception låter vi serven krascha!
Det första vi gör i vår main-metod är att starta upp register-serven, om denna inte redan körs dvs. Vi skapar sedan ett objekt av Server, och med denna öppnar upp så klienter kan börja använda sig av serven via följande rad:
Protocol remote = (Protocol) UnicastRemoteObject.exportObject(server, 0);
exportObject tar två parametrar, ett Remote objekt (vår Protocol-implementering) samt en port att lyssna på. Genom att sätta porten till 0 låter vi RMI eller underliggande system bestämma vilken port som ska användas (vilket rekomenderas).
Metoden returnerar en såkallad "stub", det är viktigt att vi tar emot denna som en Protocol-instans eftersom detta objekt endast implementerar fjärr-interfacet hos objektet som exporterades (dvs Server objektet).
Där efter behöver vi bara registrera vår stub så klienter kan hitta denna på nätverket! Vi ansluter till "localhost" och registrerar vår stub vid namnet "kalkylator".
Sist har vi klienten, detta är troligen den lättaste biten, endast en rad behövs för att upprätta en anslutning till serven:
// File: fakultet/Client.java
import java.rmi.*; import java.rmi.registry.*; public class Client { public static void main( String args[] ) throws Exception { // Hämtar anslutning vår server via Protocol-interfacet Protocol server = (Protocol) Naming.lookup("//localhost/kalkylator"); // Beräknar fakulteten av 20 och skriv ut svaret Long f = server.kalkyleraFakultet(20); System.out.println(f); } }
OBS! Ingen felhantering utförs i exemplet, kastas exception låter vi klienten krascha!
Finns inte så mycket att förtydliga här, vi ansluter till register-serven på "localhost" och hämtar Protocol referensen vid namn "kalkylator". Vi kan sedan använda detta objekt som om det vore lokalt, men själva exekveringen sker på serven. Tänk på att samtliga fjärr-metoder kan kasta RemoteException!
Utförs som vanligt, se till att Server och Client har tillgång till Protocol.class - för att snabbt komma igång kan ni låta samtliga java-filer ligga i en och samma mapp vid kompilering och exekvering.
Information: Innan JRE 5.0 var man tvungen att skapa stub-filer av interface-klassen som användes mellan klient och server via den speciella kompilatorn rmic - detta behövs inte längre göras så länge man inte vill ha bakåtkompatiblitet med de lägre JRE versionerna. Kompilering idag fungerar alltså precis som i vanliga applikationer, och inga speciella steg behövs. Var dock noga med att servern och klienten delar samma version av interface-klassen!