RMI i RMI-IIOP

Piotr Bugalski

 

 

 

RMI i RMI-IIOP. 1

Wprowadzenie. 1

Zalety RMI 1

Ograniczenia RMI 1

Jak działa RMI 2

Warstwy RMI 2

Warstwa szkieletów i trzonów.. 3

Warstwa zdalnych referencji 3

Warstwa transportowa. 3

RMI-IIOP. 3

Dostęp do zdalnych obiektów.. 4

Jak używać RMI 4

Zdefiniowanie interfejsu usługi 4

Implementacja usługi 4

Kompilacja implementacji usługi 5

Utworzenie serwera. 5

Utworzenie klienta. 6

Uruchomienie. 6

Materiały: 7

 

Wprowadzenie

RMI (Remote Method Invocation) jest programowania rozproszonego wprowadzoną w JDK 1.1. RMI dostarcza zestaw narzędzi umożliwiających pisanie programów rozproszonych przy użyciu takiej samej syntaktyki i semantyki jak dla programów pracujących na jednym komputerze (jednej maszynie javy, JVM).

 

Zalety RMI

Podstawową zaletą RMI jest prostota jego używania. W porównaniu z np. CORBĄ, RMI jest proste i mało skomplikowane. Zapewnia niezależność programów od procesora (wystarczy działająca maszyna Java VM). Troszczy się o szczegóły przesyłania obiektów, ich serializację i deserializacją.

 

Ograniczenia RMI

Podstawowym ograniczeniem i wadą RMI jest wsparcie tylko dla programów napisanych w javie. Zarówno klient jak i serwer muszą być aplikacjami lub apletami javy i nie można komunikować się z programami napisanymi w żadnym innym języku programowania za pomocą tego mechanizmu. To bardzo ogranicza zastosowanie RMI, ponieważ o ile po stronie klienta prędkość (lub raczej powolność) programów napisanych w javie może być akceptowana, o tyle w przypadku serwera użycie javy często jest niemożliwe.

 

Jak działa RMI

RMI oddziela definicję zachowania (interfejs) od jego implementacji i pozwala na uruchamianie implementacji na oddzielnej maszynie javy. Odpowiada to programowaniu typu klient-serwer, gdzie klient wykorzystuje zdefiniowany interfejs, a serwer dostarcza jego implementację. RMI dba o szczegóły dotyczące protokołu, przekazywania parametrów zwracania wyników.

 

Interfejs javy nie zawiera wykonywalnego kodu, więc RMI używa dwóch klas implementujące ten sam interfejs. Pierwsza klasa implementuje działanie (service implementation) i jest uruchamiana na serwerze. Druga klasa funkcjonuje jako pośrednik (service proxy) dla wywołań klienta. Program klienta wywołuje metody w klasie pośredniczącej, a ta przekazuje je do zdalnego serwera oraz pobiera zawracane wyniki.

 

 

Warstwy RMI

Implementacja RMI oparta jest na trzech warstwach abstrakcji. Pierwsza to warstwa szkieletów i trzonów (stubs and skeletons). Warstwa ta jest bezpośrednio używana przez programistę. Przyjmuje ona wywołania metod skierowane do interfejsu i przekazuje do zdalnych wywołań RMI. Kolejną warstwą jest warstwa referencji (remote reference layer). Warstwa ta odpowiada za interpretację i zarządzanie referencjami do obiektów przekazywanych między klientem, a serwerem. Dokonuje automatycznej serializalizacji oraz deserializacji obiektów lub przekazuje zdalne referencje do nich. Warstwa transportowa (transport layer) bazuje na protokole TCP/IP i dostarcza podstawowego połączenia między systemami. Może być zastąpiona implementacją bazującą np. na protokole UDP bez zmiany wyższych warstw.

 

 

Warstwa szkieletów i trzonów

Szkielet jest klasą pomocniczą generowaną przez RMI, która potrafi komunikować się z trzonem poprzez łącze RMI. Szkielet jest klasą działającą na serwerze. Odbiera parametry z połączenia RMI, przekazuje je do wywoływanej klasy serwera. Otrzymany wynik przekazuje z powrotem do klienta poprzez łącze RMI. Od wersji 2 JDK klasy szkieletów nie są wymagane. Trzon łączy się bezpośrednio z obiektami na zdalnym serwerze. Trzon jest klasą generowaną przez RMI, służy jako pośrednik między programem klienta, a RMI. Przekazuje parametry wywołania do łącza RMI, odbiera wynik i zwraca do programu klienta.

 

Warstwa zdalnych referencji

Warstwa to dostarcza obiekt klasy RemoteRef, który reprezentuje łącze do zdalnej implementacji. Trzon wykorzystuje metodę invoke() do przekazywania wywołań do serwera.

W wersji JDK 1.1 znajduje się tylko jedna metoda zdalnego wywoływania: unicast. Zanim klient wywoła zdalną metodę, zdalna usługa musi zostać utworzona na serwerze i wyeksportowana do mechanizmu RMI.

JDK 1.2 dostarcza nowa metoda zdalnego wywoływania: za pomocą aktywowania. RMI samo zajmuje się tworzeniem zdalnych obiektów oraz odtwarzaniem ich stanu z dysku.

Możliwe jest dodawanie nowych rodzajów wywołań, np. skierowanych do wielu redundantnych serwerów usług. 

 

Warstwa transportowa

Tworzy połączenia między wirtualnymi maszynami javy. Wszystkie połączenia są oparte na strumieniach i używają TCP/IP, nawet jeśli obie JVM są uruchomione na tej samej maszynie.

RMI używa protokołu JRMP (Java Remote Method Protocol) opartego na bazie TCP/IP. Jest to strumieniowy protokół występujący w dwóch wersjach. Pierwsza wersja użyta w JDK 1.1 wymaga klas szkieletów na serwerze. Druga wersja pojawiła się wraz z JDK 2, zastała zoptymalizowana oraz nie wymaga klas szkieletów. Istnieją implementacje RMI, które nie używają JRMP (np. BEA Weblogic).

 

RMI-IIOP

Jest to następna wersja RMI, nad którą wspólnie prowadzą prace Sun oraz IBM. RMI-IIOP zamiast JRMP ma używać IIOP (Internet Inter-ORB Protocol) do komunikacji między klientem a serwerem. IIOP jest fragmentem standardu CORBA. RMI-IIOP ma implementować obecne działanie RMI za wyjątkiem:

Java.rmi.server.RMISocketFactory

 

Dostęp do zdalnych obiektów

Klient odnajduje zdalne usługi za pomocą usługi nazywania (naming or directory service). Usługa ta działać musi na znanym hoście oraz porcie. Do jej uruchomienia używany jest program rmiregistry. Domyślnie nasłuchuje on na porcie 1099 na wywołania klientów. Program klienta wykorzystuje statyczną klasę Naming. Dostarcza ona metodę lookup() służącą do odnajdowania zdalnych usług. Do nazywania serwerów używa się nazw podobnych jak przy http:

rmi://<nazwa_hosta>[:<port>]/nazwa_usługi

 

Jak używać RMI

 

Zdefiniowanie interfejsu usługi

Pierwszym krokiem jest zdefiniowanie interfejsu, który będzie udostępniał serwer. Tylko metody tego interfejsu będą dostępne dla klienta. Jeden serwer może udostępniać kilka interfejsów. Może mieć też metody, nie używane przez te interfejsy. Interfejs nie może zawierać konstruktora. Serwer konstruowany jest nie przez klienta, tylko przez program na zadalnej maszynie. Wszystkie metody muszą deklarować zgłaszanie wyjątku java.rmi.RemoteException (jest on zgłaszany np. przy błędach sieci), a interfejs musi dziedziczyć po java.rmi.Remote.

 

1.   public interface Calculator 
2.             extends java.rmi.Remote { 
3.       public long add(long a, long b) 
4.           throws java.rmi.RemoteException; 
5.    
6.       public long sub(long a, long b) 
7.           throws java.rmi.RemoteException; 
8.    
9.       public long mul(long a, long b) 
10.             throws java.rmi.RemoteException; 
11.      
12.         public long div(long a, long b) 
13.             throws java.rmi.RemoteException; 
14.     } 

 

Implementacja usługi

 

Kolejnym krokiem jest zdefiniowanie implementacji.

 

1.   public class CalculatorImpl 
2.       extends 
3.         java.rmi.server.UnicastRemoteObject 
4.       implements Calculator { 
5.    
6.       // Implementations must have an 
7.       //explicit constructor 
8.       // in order to declare the 
9.       //RemoteException exception 
10.         public CalculatorImpl() 
11.             throws java.rmi.RemoteException { 
12.             super(); 
13.         } 
14.      
15.         public long add(long a, long b) 
16.             throws java.rmi.RemoteException { 
17.             return a + b; 
18.         } 
19.      
20.         public long sub(long a, long b) 
21.             throws java.rmi.RemoteException { 
22.             return a - b; 
23.         } 
24.      
25.         public long mul(long a, long b) 
26.             throws java.rmi.RemoteException { 
27.             return a * b; 
28.         } 
29.      
30.         public long div(long a, long b) 
31.             throws java.rmi.RemoteException { 
32.             return a / b; 
33.         } 
34.     } 

 

Zamiast wyprowadzać klasę z UnicastRemoteObject, można wywołać statyczną metodę exportObject() tej klasy, aby wyeksportować obiekt.

 

Kompilacja implementacji usługi

Następnie tak przygotowaną klasę należy skompilować. Jednak zamiast programu javac użyć należ rmic, który dostępny jest wraz z JDK.

> rmic CalculatorImpl

 

W wyniku utworzone zostaną pliki:

Calculator.class

CalculatorImpl.class

CalculatorImpl_Stub.class

CalculatorImpl_Skel.class

 

Utworzenie serwera

 

Aby utworzyć serwer należy już tylko przygotować klasę:

1.   import java.rmi.Naming;
2.    
3.   public class CalculatorServer {
4.    
5.      public CalculatorServer() {
6.        try {
7.          Calculator c = new CalculatorImpl();
8.          Naming.rebind("rmi://localhost:1099/CalculatorService", c);
9.        } catch (Exception e) {
10.            System.out.println("Trouble: " + e);
11.          }
12.        }
13.      
14.        public static void main(String args[]) {
15.          new CalculatorServer();
16.        }
17.     }

 

Utworzy ona implementację interfejsu Calculator i zarejestruje ją jak usługę CalculatorService.

 

Utworzenie klienta

Klienta programuje się prawie jak zwykłą aplikację w javie. Należy jedyni pobrać referencję do zdalnej usługi przez Naming.lookup() i pamietać o możliwych wyjątkach.

 

1.    
2.   import java.rmi.Naming; 
3.   import java.rmi.RemoteException; 
4.   import java.net.MalformedURLException; 
5.   import java.rmi.NotBoundException; 
6.    
7.   public class CalculatorClient { 
8.    
9.       public static void main(String[] args) { 
10.             try { 
11.                 Calculator c = (Calculator)
12.                                Naming.lookup(
13.                      "rmi://localhost
14.                             /CalculatorService"); 
15.                 System.out.println( c.sub(4, 3) ); 
16.                 System.out.println( c.add(4, 5) ); 
17.                 System.out.println( c.mul(3, 6) ); 
18.                 System.out.println( c.div(9, 3) ); 
19.             } 
20.             catch (MalformedURLException murle) { 
21.                 System.out.println(); 
22.                 System.out.println(
23.                   "MalformedURLException"); 
24.                 System.out.println(murle); 
25.             } 
26.             catch (RemoteException re) { 
27.                 System.out.println(); 
28.                 System.out.println(
29.                             "RemoteException"); 
30.                 System.out.println(re); 
31.             } 
32.             catch (NotBoundException nbe) { 
33.                 System.out.println(); 
34.                 System.out.println(
35.                            "NotBoundException"); 
36.                 System.out.println(nbe); 
37.             } 
38.             catch (
39.                 java.lang.ArithmeticException
40.                                           ae) { 
41.                 System.out.println(); 
42.                 System.out.println(
43.                  "java.lang.ArithmeticException"); 
44.                 System.out.println(ae); 
45.             } 
46.         } 
47.     } 
 

 

Uruchomienie

Aby uruchomić całość należy na serwerze utuchomić program rmiregistry:

> rmiregistry

 

następnie serwer:

> java CalculatorServer

 

i gotowe. Po uruchomieniu klienta wszystko powinno pięknie działać.

 

 

Materiały:

 

http://developer.java.sun.com/developer/onlineTraining/rmi/ - opis RMI

„Java. Programowanie sieciowe” Elliotte Rusty Harold, Wydawnictwo RM, Warszawa 2001