RMI med BlueJ Tutorial lavet af Jákup W. Hansen TSU 2006 3.semester 11. desember 2007 Hvad er RMI? Når man arbejder med Distribuerede Systemer, som igen vil sige at man ønsker at flere end én komputer skal arbejde sammen. Så kan man sagtens bruge socket programmering. Men når programmerne bliver mere og mere kompliceret og man ønsker også at det skal kunne udvikle sig efter at det er taget i brug, så er socket programmering noget bøvlet. Med socket skal VI nemlig holde styr på det hele. Hvis vi ønsker at kalde et objekt hos en anden applikation/maskine, så må vi sende en besked og i denne besked må der ligge en beskrivels af hvad objekt og metode vi ønsker at kalde, og så må modtageren selvfølgelig også læse denne besked og gøre det vi beder om. Sagt på en anden måde, når man arbejder med socket programmering, så skal vi lave hele protokollen, dvs. med ifsætninger skal vi modtage tekst og så pege vider på metoderne. Man kan set at der kommer et extra lag når vi bruger socket, nemlig det lag som modtager og sender beskeder. RMI fjerner helt dette lag/protokol. Det resulterer i, at når jeg har fået et objekt som ligger på en anden maskine (et remot objekt), så kalder jeg bare direkte de metoder som jeg ønsker, lige som om det var et objekt som jeg havde lokalt på min maskine. Det gør programmeringsarbejdet noget nemmere, og vi kan bevare fokus på det abstraktions nivo som er vigtigt og ikke skulle ned i detaljerne/protokollen. Hvis du ikke kan se det smarte med RMI så skyldes det nok at du ikke har arbejdet nok med sockets, fordi det er kompliceret for RMI at holde styr på alle de Remote Objekter som ligger over hele verdenen, således at jeg kan bruge dem som om de ligger lokalt. Vi skal slet ikke gå ind i hvordan RMI gør alt dette, men hvis man ikke sætter pris på det vi får fra RMI, så håber jeg at denne gennemgang vil hjælpe på det. BlueJ: Er et udviklingsværktøj som er gratis, og som er lavet til undervisningsbrug. Det kan hents fra http://www.bluej.org/. 1
Java JDK: Når man bruger RMI er det meget forskellig hvordan koden skrives og hvordan man kompiler, alt afhængig af hvilken JDK man bruger. I denne tutorial bruger vi den nyeste, som medfører at der hverken bruges (synlig for os) Skeletons eller Stubs, og det gør det arbejde med RMI meget nemmer. Dertil behøver vi heller ikke at bruge en speciel RMI kompiler, som man måte bruge før. Se hvordan min opsætning ser ud i BlueJ. 2
Der er en god foklaring på wikipedia om udvikling, jeg har taget noget af den ned og markeret det som jeg syns er mest relevant i denne her diskusion. Java remote method invocation From Wikipedia, the free encyclopedia A typical implementation model of Java-RMI using Stub and Skeleton objects. Since Java 2 SDK, Standard Edition, v1.2 there is no need of Skeleton. The Java Remote Method Invocation API, or Java RMI, is a Java application programming interface for performing the object equivalent of remote procedure calls. There are two common implementations of the API. The original implementation depends on Java Virtual Machine (JVM) class representation mechanisms and it thus only supports making calls from one JVM to another. The protocol underlying this Javaonly implementation is known as Java Remote Method Protocol (JRMP). In order to support code running in a non-jvm context, a CORBA version was later developed. Usage of the term RMI may denote solely the programming interface or may signify both the API and JRMP, whereas the term RMI-IIOP, read RMI over IIOP, denotes the RMI interface delegating most of the functionality to the supporting CORBA implementation. The original RMI API was generalized somewhat to support different implementations, such as an HTTP transport. Additionally, work was done to CORBA, adding a pass by value capability, to support the RMI interface. Still, the RMI-IIOP and JRMP implementations are not fully identical in their interfaces. The package name is java.rmi. Note that with Java versions up to 5.0 it was necessary to compile RMI stubs in a separate compilation step using rmic. Version 5.0 of Java and beyond no longer require this step. 1 August 2007. All text is available under the terms of the GNU Free Documentation License. (See Copyrights for details.) Wikipedia is a registered trademark of the Wikimedia Foundation, Inc., a U.S. registered 501(c)(3) tax-deductible nonprofit charity. Your continued donations keep Wikipedia running! 3
Eksempel 1: Det første eksempel bruger 2 servere og 1 klient. Hvor næste eksempel, som kommer efter dette, har 1 server og 2 klienter,,,men det kommer vi til. Koden følger her, men jeg vil ikke gå ind i Koden, fordi den har selv rimeligt gode beskrivelser. Men lad mig først vise hvordan klasse-diagrammet ser ud i BlueJ. 4
Så er det koden... //Server. import java.rmi.*; import java.util.vector; import java.rmi.regis.*; Server public class Server private final String HOST1 = "localhost:2002"; //Vi bruger localhost fordi alt kører på én pc, men hvis du skal kommuniker med // en anden pc, så må du skriv dens ip addresse her, efterfulgt af :2002. private KontoObjektet KONTO; public Server() throws Exception KONTO = new KontoObjektet(); LocateRegis.createRegis(2002); //Starter er RMIRegis med PORT 2002. String rmiobjectname = "rmi://" + HOST1 + "/Account"; //vi giver vores objekt det samme navn som Server2, Naming.rebind(rmiObjectName,KONTO); //det kan vi fordi der er // to rmiregis en på port 2002 og en på 2007. System.out.println("Binding complete...\n"); public int henttal () return KONTO.interfaceGetTal(); catch(exception ex) return 0; public void ændretal(int t) KONTO.interfacePlus(t); System.out.println("SERVEREN har ændret værdien, som nu er: " + henttal()); catch(exception ex) 5
import java.rmi.*; import java.util.vector; import java.rmi.regis.*; Server2 public class Server2 private final String HOST2 = "localhost:2007"; private KontoObjektet KONTO; public Server2() throws Exception LocateRegis.createRegis(2007); KONTO = new KontoObjektet(); String rmiobjectname2 = "rmi://" + HOST2 + "/Account"; //Could omit host name, since 'localhost' would be //assumed by default. Naming.rebind(rmiObjectName2,KONTO); System.out.println("Binding complete...\n"); public int henttal2 () return KONTO.interfaceGetTal(); catch(exception ex) return 0; public void ændretal2(int t) KONTO.interfacePlus(t); System.out.println("SERVEREN 2 har ændret værdien, som nu er: " + henttal2()); catch(exception ex) KontoObjektet //Implementation of RMI interface. import java.rmi.*; import java.rmi.server.*; //Det er denne klasse vi laver ét objekt af i Bank1Server, som bliver det //objekt vi deler med clienten. I interfacet Bank1 har vi lovet at lave //2 metoder pluss() og gettal(), men her kan du også se at vi har lagt en //field med navnet TAL til, det kan man ikke se i interfacet. Så det er faktisk //ikke kun metoderne vi deles om, men selve objektet med både metoder og fields. public class KontoObjektet extends UnicastRemoteObject implements Konto private int TAL; public KontoObjektet ()throws RemoteException TAL = 5; public void interfaceplus(int tal) throws RemoteException TAL = tal+tal; public int interfacegettal() throws RemoteException return TAL; 6
Konto //RMI interface. import java.rmi.*; import java.util.vector; public interface Konto extends Remote public void interfaceplus(int tal ) throws RemoteException; public int interfacegettal() throws RemoteException; import java.rmi.*; public class Client private final String HOST1 = "localhost:2002"; private final String HOST2 = "localhost:2007"; Client private Konto KONTO1; private Konto KONTO2; public Client() KONTO1 = (Konto)Naming.lookup("rmi://" + HOST1 + "/Account"); //KONTO1 til Remote Objektet Account på port 2002. KONTO2 = (Konto)Naming.lookup("rmi://" + HOST2 + "/Account"); //KONTO2 til Remote Objektet Account på port 2007. System.out.println("KLIENTEN, har oprettet kontakt til begge Account objekter"); catch(connectexception conex) System.out.println("Unable to connect to server!"); System.exit(1); catch(exception ex) ex.printstacktrace(); System.exit(1); public int hentekonto1() return KONTO1.interfaceGetTal(); catch(exception ex) return 0; // se her,,,hvis vi ikke får fat på objektet, så returneres 0 public void ændrekonto1(int t) KONTO1.interfacePlus(t); System.out.println("KLIENTEN har ændret værdien, som nu er: " + hentekonto1()); catch(exception ex) public int hentekonto2() return KONTO2.interfaceGetTal(); catch(exception ex) return 0; // se her,,,hvis vi ikke får fat på objektet, så returneres 0 public void ændrekonto2(int t) KONTO2.interfacePlus(t); System.out.println("KLIENTEN 2 har ændret værdien, som nu er: " + hentekonto2()); catch(exception ex) 7
Sådan kører du eksempel 1: Start tre identiske projekter,,det vil se sådan ud... Du laver så som der vises, et objekt af Server, et af Server2 og et af Client, og alle i deres eget projekt. Det er vigtigt at det er 3 projektet. 8
Nu skal du ændre værdien på de Dist. Objekter ved at ændre i clienten. Du kan også ændre hos serveren hvis du vil. Og du ser at værdien ændres, og du kan/bør også kikke hos Serveren for at se, at de viser samme værdi som clienten. Det betyder at det må være samme objekt vi snakker med. Her har du fat i hovedfiduse med RMI. For at gøre det endnu klarere, prøv så og luk Clienten ned, som billedet viser. Lav så et nyt client objekt, og kald så hentkonto1() og hentkonto2() metoderne, og du vil se at værdien stadigvæk er den samme. Du kan også lave flere client objekter, og de vil alle vise det samme. Og ændrer du i værdien fra en client så gælder den for samtlige clienter og Serveren selvfølgelig. Det vigtigste for dig at forstå her er, at det har ingen indflydelse om clienten/clienterne lukker ned, det remote/dist. Objekt lever fint videre alligevel. Jeg håber at du forstår bedre hvordan det hele hænger sammen. Men lad os lige lave en test mere. 9
Her bygger vi vider på det som vi allerede har kørende. Altså vi har de tre Projekter kørende ligesom før. Først skal du Reset Machine Server1 objektet, det resulterer i at det hele i dette projekt bliver fjernet. Så skal vi...se efterfølgende billede. 10
Hvis du nu prøver at kalde det objekt som tilhører det projekt som du lige har lukket/resettet i mit tilfælde er det hentkonto1() så vil den returnere 0, som er det samme som at clienten ikke har fået fat på objektet se koden. Og det er også logisk, fordi objektet er væk. Nu skal vi gå over til det næste eksempel, som ligner en del, men dog viser med mere tydelighed nogle facetter som ikke kommer 100% frem her... 11
Eksempel 2: Dette eksempel ligner som sagt meget det første eksempel, men har i stedet for 2 servere kun én server men så 2 clienter i stedet for en. Så man kan sige at dette eksempel er en spejling af det første. Men vi får belyst noget tydeligere, hvor referencerne ligger, og vi skal også i dette eksempel SENDE referencer rundt, og vise at disse referencer også er Remote objekter. Det er faktisk fantastisk at det kan lade sig gøre,,,lad os starte. Før skal du se Klasse-Diagrammet. Så skal vi se koden, og lige som før, så vil jeg ikke diskutere koden, også fordi der er tekst i koden til at beskriv det vigtigst. 12
import java.rmi.*; import java.rmi.server.*; import java.util.vector; import java.rmi.regis.*; Server public class Server extends UnicastRemoteObject implements ServerInterface private static final String HOST = "localhost"; //Grunden til at vi ikke skal skrive portnummer dvs. localhost:1099 er ClientInterface client1; //kommer af at 1099 er standard port ligesom 80 er det for websider. ClientInterface client2; public Server() throws RemoteException LocateRegis.createRegis(1099); String rmiobjectname = "rmi://" + HOST + "/Connection"; Naming.rebind(rmiObjectName, this); System.out.println("Binding complete...\n"); catch(exception e) public void bindclient(clientinterface c) throws RemoteException if(client1 == null) client1 = c; String navn = c.getnavn(); System.out.println("SERVEREN: " + navn + " is now Client1"); c.printtext("besked FRA SERVEREN: You are now Client1"); else client2 = c; String navn = c.getnavn(); System.out.println("SERVEREN: " + navn + " is now Client2"); c.printtext("besked FRA SERVEREN: You are now Client2"); catch(exception e) public void ConnectClients() throws RemoteException client1.setotherclient(client2); client2.setotherclient(client1); public ClientInterface getclient1() throws RemoteException return client1; public ClientInterface getclient2() throws RemoteException return client2; 13
import java.rmi.*; import java.util.vector; ServerInterface public interface ServerInterface extends Remote public void bindclient(clientinterface c) throws RemoteException; public ClientInterface getclient1() throws RemoteException; public ClientInterface getclient2() throws RemoteException; import java.rmi.*; import java.util.vector; ClientInterface public interface ClientInterface extends Remote public void bindmetoserver() throws RemoteException; public void printtext(string text) throws RemoteException; public void setotherclient(clientinterface c) throws RemoteException; public String getnavn() throws RemoteException; public void talktootherclient(string text) throws RemoteException; 14
import java.rmi.*; import java.rmi.server.*; import java.io.serializable; Client public class Client extends UnicastRemoteObject implements ClientInterface public String Navn; private static final String HOST = "localhost"; private ServerInterface server; private ClientInterface OtherClient; public Client(String navn) throws RemoteException Navn = navn; server = (ServerInterface)Naming.lookup("rmi://" + HOST + "/Connection"); System.out.println("Connection established"); catch(connectexception conex) System.out.println("Unable to connect to server!"); System.exit(1); catch(exception ex) ex.printstacktrace(); System.exit(1); public String getnavn() return Navn; public void bindmetoserver() throws RemoteException server.bindclient(this); public void printtext(string text) System.out.println("KLIENT: Jeg er: " + getnavn() + " Har modtaget denne besked: " +text); public void setotherclient(clientinterface c) OtherClient = c; System.out.println("KLIENT: Jeg er: " + getnavn() + " Har fået en klient at snakke med"); public void talktootherclient(string text) throws RemoteException OtherClient.printText(Navn + " says: " + text); 15
Sådan kører du eksempel 2: Lav 3 projekter af samme type. Og kør så en server og to klienter. Se billedet... Nu kan klienterne snakke sammen, fordi de har fået referencerne til hinanden fra serveren. Det er måske ikke så imponerende, men hvis du tænker over hvor nemt det ser ud i koden, fordi man bare kalder direkte på referencen. Men vi er ikke helt færdige endnu, vi mangler faktisk hele fidusen. Mens du stadigvæk har det hele kørende (alle 3 projekter) skal vi lave en ændring. Vi skal nemlig slukke serveren. Det kan man gøre ved enten at højreklikke på bjælken over server objektet o vælge Reset machine eller man kan lukke hele projektet ned ved at kke på overste højre kryds, det er ligemeget. 16
Det interessante her er, at Clienterne kan blive ved med at kommunikere selv efter at serveren er lukket helt ned. Vi bruger således i dette eksempel kun serveren til at etablere en forbindelse i mellem clienterne, og efter det er serveren overflødig. Man kan filosofere over hvordan det kan lade sig gøre, men det behøves vi ikke, vi skal bara vide at det er alt dette vi får serveret fra RMI. Når er objekt bliver til et Remote Objekt så kan man deles om det. Den måde hvorpå RMI ved om et objekt skal være Remote (et objekt vi deler) og ikke en kopi, er ved at klassen bruger UnicastRemoteObject, som siger RMI, at dette objekt er et Remote objekt. Resten klarer RMI. 17
RMI illustration: Tak for denne gang. Jákup Wenningstedt Hansen. 18