HOJ RMI = Remote Method Invocation Ville Leppänen HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.1/37
Missä mennään... 1. Johdanto (1h) 2. Säikeet (2h) 3. Samanaikaisuudesta (2h) 4. Hajautetuista sovelluksista (1h) 5. Soketit (3h) 6. RMI ja J2EE (2h) 7. RPC (1h) 8. WWW-sovellustekniikoista (2h) 9. Pilvialustat (4h) 10. Haja-aiheita (2h) 99. Kertausluento (2h) + 1h pelivaraa HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.2/37
Remote Method Invocation: Yleistä 1/2 Palvelinsovellus kuuntelee yhteydenottoja; yhteydenotto = metodin kutsu ja suorittaminen palvelimessa. Asiakas kutsuu palvelinsovellusta suorittamaan jokin etäolion jotain tiettyä metodia (parametrit). Parametrit: tietoa asiakkaalta palvelimelle. Tulos: tietoa palvelimelta asiakkaalle. Tietoa välitetään Javan olioina! Palvelin- & asiakassovellus: Java-ohjelmia. Yleisesti: useita palvelimia ja asiakkaita. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.3/37
RMI: Yleistä 2/2 Etäolio (server object): palvelimessa oleva olio. Paikallinen olio (client object): asiakassovelluksessa. Edustajaolio (stub): asiakkaalla on olio, joka edustaa palvelimessa olevaa etäoliota. Tukee turvallisuusominaisuuksia ja roskien keruuta (yms). Asiakkaan ja palvelimen roolit eivät kiinteitä: osapuoli voi olla molemmissa rooleissa. Paketit java.rmi (asiakas); java.rmi.server (palvelin); java.rmi.registry (rekisteri: nimiä palvelimen olioille);... HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.4/37
RMI:n kulisseissa tapahtuu (asiakas) (palvelin) rekisteri palvelinohj. asiakasohj. etäolio luonti etäoliota vastaava edustajaolio rekisteristä etäolion rekisteröinti metodin kutsu paikalliselle ohjelmalle edustajaolio kutsun + param. ohjaus eteenpäin tulosten välittäminen edustajaoliolle kutsun suoritus edustajaolion olion kutsuja odottava säie tulokset takaisin HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.5/37
RMI: mitä kutsussa tapahtuu? Asiakas tekee kutsun edustajaolion välityksellä. Edustajaolio muodostaa etäkutsun: Etäolion tunniste Etäolion kutsuttava metodi Parametrien koodaus (marshalling) jonka se lähettää palvelimelle. Palvelin vastaanottaa tiedot, purkaa ne ja kutsuu etäolion metodia. Tuloksen palvelin lähettää koodattuna takaisin asiakassovellukselle, joka ottaa vastaan ja purkaa koodauksen. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.6/37
Parametrien koodaus Paikalliset oliot: syväkopiona Serializable:n avulla. Transitiivinen sulkeuma. Primitiiviarvot: kopioimalla. Etäoliot: viittauksena. Olion sarjallistamisen yhteyteen koodataan myös tieto luokasta: nimi, sormenjälki, yms. Toisessa päässä määrittelyt ladataan suoritusympäristöön ja tarkistetaan, että sillä tuntettavilla vastaavilla luokilla on sama sormenjälki. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.7/37
Etäolioon liittyvät luokat 1/3 Asiakassovelluksessa etäolioihin ei viitata suoraan niihin viitataan rajapinnan avulla. interface Noppa extends Remote {... metodeja } Pitää aina periä Remote:sta. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.8/37
Etäolioon liittyvät luokat 2/3 Etäolio toteuttaa kyseisen rajapinnan ja perii luokasta UnicastRemoteObject (yleisemmin: RemoteServer). public class NoppaToteutus extends UnicastRemoteObject implements Noppa {... toteutus... } HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.9/37
Etäolioon liittyvät luokat 3/3 Palvelinsovellus: itsenäinen luokka, joka luo ja nimeää etäoliot. Asiakassovellus: itsenäinen luokka, joka etäolion verkkonimen perusteella luo edustaolion, jonka avulla se toteuttaa RMI-kutsut. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.10/37
Noppa.java import java.rmi. ; public interface Noppa extends Remote { public void setseed(long l) throws RemoteException; public void setmax(int m) throws RemoteException; public void setmin(int m) throws RemoteException; public int getnext() throws RemoteException; } // interface Noppa HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.11/37
NoppaToteutus.java import java.rmi. ; import java.util. ; import java.rmi.server. ; public class NoppaToteutus extends UnicastRemoteObject implements Noppa { private Random gen = new Random(); private int min, max, diff; public NoppaToteutus() throws RemoteException { min = 1; max = 6; diff = 6; } public NoppaToteutus(int mi, int ma) throws RemoteException { min = mi; max = ma; diff = ma-mi+1; } public void setseed(long l) throws RemoteException { gen.setseed(l); } public void setmax(int m) throws RemoteException { max = m; diff = max - min; } public void setmin(int m) throws RemoteException { min = m; diff = max - min; } public int getnext() throws RemoteException { return Math.abs(gen.nextInt()%diff) + min; } } // class NoppaToteutus HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.12/37
NoppaServer.java import java.rmi. ; import java.rmi.server. ; public class NoppaServer { public static void main(string[] q) { try { NoppaToteutus noppa = new NoppaToteutus(); Naming.rebind("noppa", noppa); } catch (Exception e) { System.out.println("Error: " + e); } } // main } // class NoppaServer HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.13/37
NoppaClient.java import java.rmi. ; import java.rmi.server. ; public class NoppaClient { public static void main(string[] q) { // q[0] = RMI-registerikoneen nimi if (q.length 1) { System.out.println("Usage: NoppaClient <serverhost>"); System.exit(0); } System.setSecurityManager(new RMISecurityManager()); String osoite = "rmi://" + q[0] + "/noppa"; try { Noppa n = (Noppa)Naming.lookup(osoite); for (int i=0; i<20; i++) System.out.print(" " + n.getnext()); System.out.println(" "); } catch (Exception e) { System.out.println("Error: " + e); } } // main HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.14/37
Käyttäminen javac Noppa*.java (käännetään kaikki) rmic -v1.2 NoppaToteutus (luodaan edustaolio NoppaToteutus_Stub) start rmiregistry (Windows) rmiregistry & (Unix) (käynnistetään rmi-rekisteripalvelin; portti 1099) start java NoppaServer (Windows) java NoppaServer & (Unix) (käynnistetään Noppa-palvelinsovellus taustalle; registeröityy edelliselle) java -Djava.security.policy=Noppa.policy NoppaClient (käynnistetään sovellus määrätyllä turv.määrittelyillä) HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.15/37
Oikeudet Noppa.policy: grant { permission java.net.socketpermission "*:1024-65535", "connect"; }; HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.16/37
Luokkia RMISecurityManager Turvallisuusmanageri, joka kontrolloi suoritettavan koodin (stub) lataamista. Vain konstruktori. Ei appleteille. Asiakassovellukseen. Remote Rajapinta; ei metodeja. UnicastRemoteObject Luokka; mahdollistaa TCP:n päällä tapahtuvan etäkäytön. Kaikkiin metodeihin throws RemoteException. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.17/37
Luokkia: Naming Naming: Sen avulla toteutetaan olioiden nimeäminen (rekiströinti) URL:n tapaan. "rmi://kone.portti/nimi". static void bind(string, Remote) sitoo nimen olioon (ei saa olla jo määr.) static void rebind(string, Remote) sitoo nimen olioon static void unbind(string) tekee nimestä määrittelemättömän static Remote lookup(string) palauttaa nimeä vastaavan edustaolion static String[] list(string) rekisterissä olevat tunnisteet HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.18/37
Luokka RemoteException 1/2 RMI-kutsu edustaolion välityksellä voi päättyä poikkeukseen. Poikkeus syntyy palvelimessa ja se välitetään RMI:n avulla asiakkaalle. Tällaisia poikkeuksia varten luokka RemoteException. Useita aliluokkia (n. 10). Jokaisessa rajapinnasta Remote laajennetussa rajapinnassa pitää kaikissa metodeissa olla throws RemoteException. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.19/37
Luokka RemoteException 2/2 Syy: verkko-operaatiot voivat aihettaa poikkeuksen, kun etäoliota käytetään edustaolion välityksellä. Poikkeus pitää olla myös rajanpinnan toteuttavissa konstruktoreissa ja toteutettavissa metodeissa mutta ei muissa metodeissa. Lisäksi voidaan vapaasti määritellä muitakin poikkeuksia, jotka kulkeutuvat RMI:n välityksellä. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.20/37
NumeroPeli.java import java.rmi. ; interface NumeroPeli extends Remote { public boolean lisääpelaaja(numeropelaaja p) throws RemoteException; public int montakopelaajaa() throws RemoteException; public NumeroPelaaja annapelaaja(int nro) throws RemoteException; public boolean sovellasiirtoa(numeropelaaja p, boolean alusta) throws RemoteException; public int annapisteet(numeropelaaja p) throws RemoteException; public String näytäluvut() throws RemoteException; public int[] annaluvut() throws RemoteException; public boolean peliohi() throws RemoteException; } // interface NumeroPeli HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.21/37
NumeroPelaaja.java import java.rmi. ; interface NumeroPelaaja extends Remote { public String annanimi() throws RemoteException; public void teesiirto() throws RemoteException; public boolean issimilar(numeropelaaja p) throws RemoteException; public void tiedoita(string s) throws RemoteException; public void saavuoron() throws RemoteException; public void lopeta() throws RemoteException; } // interface NumeroPelaaja HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.22/37
NumeroPeliToteutus.java 1/7 import java.rmi. ; import java.rmi.server. ; import java.util. ; public class NumeroPeliToteutus extends UnicastRemoteObject implements NumeroPeli { private NumeroPelaaja player1, player2; private int points1, points2; private Vector numbers; private int remaining; public NumeroPeliToteutus() throws RemoteException { player1 = null; player2 = null; } // NumeroPeliToteutus() private void alusta() { points1 = 0; points2 = 0; numbers = new Vector(); for (int i=0; i<10; i++) numbers.add(new Integer((int)(Math.random() 21) - 10)); remaining = 10; } // alusta HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.23/37
NumeroPeliToteutus.java 2/7 public boolean lisääpelaaja(numeropelaaja p) throws RemoteException { if (player1 == null) { player1 = p; return false; } else if (player2 == null) { player2 = p; alusta(); player1.tiedoita("game begins."); player2.tiedoita("game begins."); String tied = player1.annanimi() + "versus " + player2.annanimi(); player1.tiedoita(tied); player2.tiedoita(tied); player1.tiedoita("numbers " + näytäluvut()); player2.tiedoita("numbers " + näytäluvut()); player2.saavuoron(); return true; } else throw new RemoteException("Game full!"); } // lisääpelaaja HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.24/37
NumeroPeliToteutus.java 3/7 public int montakopelaajaa() throws RemoteException { if (player2 null) return 2; if (player1 null) return 1; return 0; } // montakopelaajaa public NumeroPelaaja annapelaaja(int nro) throws RemoteException { if ((nro > 2) (nro < 1)) return null; if (nro == 1) return player1; else return player2; } // annapelaaja HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.25/37
NumeroPeliToteutus.java 4/7 public boolean sovellasiirtoa(numeropelaaja p, boolean alusta) throws RemoteException { if ((!player1.issimilar(p)) && (!player2.issimilar(p))) throw new RemoteException("Illegal player!"); if (remaining == 0) throw new RemoteException("Illegal move!"); int n = 0; String tied = " "; if (alusta) { n = ((Integer)numbers.remove(0)).intValue(); tied = "first"; } else { n = ((Integer)numbers.remove(numbers.size()-1)).intValue(); tied = "last"; } tied = "took the " + tied + "number " + n + "."; if (player1.issimilar(p)) { points1 += n; tied = "Player 1 " + tied; } else { points2 += n; tied = "Player 2 " + tied;} player1.tiedoita(tied); player2.tiedoita(tied); remaining--; HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.26/37
NumeroPeliToteutus.java 5/7 if (remaining == 0) { pelinlopetus(p); player1 = player2 = null; return true; } else { if (player1.issimilar(p)) player2.saavuoron(); if (player2.issimilar(p)) player1.saavuoron(); return false; } } // sovellasiirtoa HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.27/37
NumeroPeliToteutus.java 6/7 private void pelinlopetus(numeropelaaja p)throws RemoteException { String tied = "Player 1 score = " + points1; player1.tiedoita(tied); player2.tiedoita(tied); tied = "Player 2 score = " + points2; player1.tiedoita(tied); player2.tiedoita(tied); if (points1 < points2) tied = "Player 2 has won!"; else if (points1 > points2) tied = "Player 1 has won!"; else tied = "Game ended up to a draw."; player1.tiedoita(tied); player2.tiedoita(tied); if (player2.issimilar(p)) player1.lopeta(); else player2.lopeta(); // Toista pelaajaa käsketään lopettaa toisella // tapaa, koska sillä on vielä kutsu auki. } // pelinlopetus public int annapisteet(numeropelaaja p) throws RemoteException { if (player1.issimilar(p)) return points1; if (player2.issimilar(p)) return points2; throw new RemoteException("Player not playing"); HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.28/37 } // annapisteet
NumeroPeliToteutus.java 7/7 public String näytäluvut() throws RemoteException { String t = " "; for (int i=0; i<remaining; i++) t += ((Integer)numbers.get(i)).intValue() + ; return t; } // näytäluvut public int[] annaluvut() throws RemoteException { int[] q = new int[remaining]; for (int i=0; i<remaining; i++) q[i] = ((Integer)numbers.get(i)).intValue(); return q; } // annaluvut public boolean peliohi() throws RemoteException { return (remaining == 0); } public static void main(string[] q) throws Exception { Naming.rebind("NumeroPeliPalvelin", new NumeroPeliToteutus()); } // main } // class NumeroPeli HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.29/37
NumeroPelaajaToteutus.java 1/3 import java.rmi. ; import java.rmi.server. ; public class NumeroPelaajaToteutus extends UnicastRemoteObject implements NumeroPelaaja, Runnable { private NumeroPeli game; private String name; private boolean ismyturn; private boolean stillplaying; public static void main(string[] args) throws Exception { if (args.length < 2) { System.out.println("Paramters: <server> <player name>"); System.exit(0); } String palvelin = args[0].trim(); String pelaajannimi = args[1].trim(); NumeroPeli peli = (NumeroPeli)Naming.lookup(palvelin); new NumeroPelaajaToteutus(peli, pelaajannimi); } // main HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.30/37
NumeroPelaajaToteutus.java 2/3 public NumeroPelaajaToteutus(NumeroPeli peli, String nimi) throws RemoteException { game = peli; name = nimi; stillplaying = true; ismyturn = false; game.lisääpelaaja(this); new Thread(this).start(); } // NumeroPelaajaToteutus public String annanimi() throws RemoteException { return name; } public void teesiirto() throws RemoteException { boolean alusta = (Math.random() < 0.5); stillplaying =!(game.sovellasiirtoa(this, alusta)); } // teesiirto public boolean issimilar(numeropelaaja p) throws RemoteException { // Jos edustaolioita vertaa suoraan == :lla, // niin tulos voi olla "väärä", koska viime // kädessä halutaan verrata vastaavia olioita. return (this.name.equals(p.annanimi())); } // issimilar HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.31/37
NumeroPelaajaToteutus.java 3/3 public void tiedoita(string s) throws RemoteException { { System.out.println("Server: " + s); } public void saavuoron() throws RemoteException { { ismyturn = true; } public void run() { while (stillplaying) { if (ismyturn) { ismyturn = false; try { teesiirto(); } catch (Exception e) { throw new Error(e.toString()); } } try { Thread.sleep(300); } catch (InterruptedException e) { } } // while System.out.println("Game over!"); System.exit(0); } // run public void lopeta() throws RemoteException { stillplaying = false; // kills the thread } // lopeta } // class NumeroPelaajaToteutus HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.32/37
Sovellusten keskustelusta Kaksi palvelinsovellusta voi keskustella keskenään. RMI: metodin kutsu = vaikuttamisen yhteydessä pitää huolehtia, ettei tule avoimien kutsujen sykliä. Tavallaan kutsut tarkoittavat vuoron siirtämistä toiselle. Molemmat osapuolet aktiivisia: ei syklejä, jos metodin kutsu tarkoittaa palvelupyynnön välittämistä. Riittää kun vain toiseen suuntaan palvelupyyntöjä! Ratkaisu: molemmilla itsenäinen säie & RMI-kutsut ainakin toiseen suuntaan palvelupyyntöjä. Paljon säikeitä: main, itsenäinen säie (+ ilm. Remote-oliota varten syntyy oma säie). HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.33/37
Pelipöytä-sovellukset Tieto pelin tilasta palvelinsovelluksella. Palvelinsovellus kontrolloi pelin etenemistä. Asiakkaat myös etäolioita. Palvelimella asiakkaista edustaolio. Palvelin voi olla passiivinen: peli etenee vain pelaajien toimenpiteiden seurauksena. Pitää välttää avointen kutsujen syklit: palvelin välittää palvelupyyntöjä (ei kutsu suoraan peliä ohjaavia asiakkaan metodeja). Lopettaminen hankalaa: yleensä asiakkaan kutsun seurauksena. Palvelin tekee lopettamisen. Avoin etäolio pitää käsitellä toisin. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.34/37
Turvallisuudesta Mahdollista kryptata RMI-liikenne (voidaan vaihtaa RMI:n käyttämät tavalliset soketit SSL-soketeiksi). Palvelimen puolella ei ole tukea sille, että rajoitettaisiin oikeutta tehdä kutsu edustaolion kautta (miten palvelin tietää kuka sitä kutsuu? edustaolio voidaan välittää eteenpäin...). Palvelimeen voidaan tehdä login-osuus, jonka jälkeen saa varsinaisen etäolion palveluiden käyttämiseksi. Kyseinen olio voi olla erilainen eli käyttäjille. Palvelimen pitää varautua siihen, että asiakas häiriköi. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.35/37
Jakamisesta ja lukoista RMI ei varsinaisesti tunne lukkoja, mutta... RMI:n yhteydessä on mahdollista soveltaa lukkoja palvelipäässä. RMI-palvelin on monisäikeinen (kutakin samanaikaista kutsua toteuttaa eri säie). Määritellään kriittiset etäolion operaatiot sychronized-suojatuiksi. Suojaa etäoliokutsujen kautta tapahtuvaa samanaikaista käyttöä (sekä muuta palvelimessa etäolioon tapahtuvaa käyttöä). Asiakas ei siis suoraan saa lukkoa etäolioon (vaan sen edustaja etäkutsua suorittava säie palvelimessa). wait()/notify() hankalia mutta mahdollisia lähinnä vain estetään samanaikainen jaetun datan käyttö. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.36/37
Yhteenveto RMI = Remote Method Invocation. Etäolio + edustaolio. Kutsulla saadaan aikaan tiedon siirto molempiin suuntiin voidaan välittää olioita kopioimatta. Edustaolioiden == eri kuin etäolioiden ==!! Kutsuja aktiivinen; kutsuttava passiivinen. Pitää varoa avoimien kutsuketjujen sykliä. Palvelin vs useita asiakkaita: helppo. Palvelin & palvelin: vaikeampi. Toteutettu säikeillä ja soketeilla. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.37/37