1/1 Systemy rozproszone Dr inż. L. Miękina Department of Robotics and Mechatronics AGH University of Science and Technology Marzec, 2013
RMI - zdalne wywołanie metod Rozproszone obiekty Stan obiektu składa się z wartości jego pól. Zgodnie z paradygmatem obiektowym, stan całego programu jest reprezentowany przez oddzielne części, odpowiadające poszczególnym obiektom. Ponieważ obiektowo zorganizowane programy są w ten sposób logicznie podzielone, fizyczne rozłożenie obiektów pomiędzy różnymi procesami lub komputerami w systemie rozproszonym jest naturalnym rozszerzeniem. Rozproszone systemy obiektowe mogą być oparte na modelu klient-serwer. W tym przypadku obiekty są zarządzane przez serwery i ich klienci wywołują ich metody używając zdalnych wywołań metod. W RMI, żądanie klienta by wywołać metodę obiektu zostaje wysłane jako komunikat do serwera zawierającego obiekt. Wywołanie to jest realizowane przez metodę obiektu serwera, a rezultat jest zwracany do klienta w osobnym komunikacie. Ponieważ obiekty klientów i serwera są w różnych procesach, mamy do czynienia z naturalną enkapsulacją. To znaczy, że stan obiektu może być dostępny tylko za pomocą metod, co oznacza że nie jest możliwe by nieuprawnione metody (zewnętrzne) wpływały na stan. Pozwala to również odpowiednio rozwiązać zagadnienie równoczesnego dostępu, na przykład stosując mechanizmy synchronizacji (semafory, sekcje krytyczne). 2 / 1
RMI Rozproszony model obiektowy Obiekty które mogą podlegać zdalnym wywołaniom są nazywane zdalnymi obiektami. Aby umożliwić pracę ze zdalnymi obiektami wprowadzono szereg rozszerzeń modelu obiektowego. Każdy proces zawiera kolekcję obiektów, niektóre z nich mogą odbierać zarówno zdalne jak i lokalne wywołania metod, podczas gdy pozostałe mogą odbierać tylko lokalne wywołania, jak na schemacie poniżej. wywołania metod między obiektami w różnych procesach, na tym samym lub innym komputerze, są zdalne. wywołania metod między obiektami w tym samym procesie są lokalnymi wywołaniami metod. 3 / 1
RMI Rozproszony model obiektowy Zdalna referencja obiektu Identyfikator, który może być używany w systemie rozproszonym w celu odwołania się do wybranego unikalnego zdalnego obiektu. Reprezentacja jej jest zwykle różna od lokalnej referencji obiektu. Składa się ona z: adresu IP komputera i numeru portu związanego z procesem, który utworzył obiekt czasu utworzenia lokalnego numeru obiektu i identyfikatora interfejsu (nazwy) Zdalne referencje obiektu są podobne do lokalnych w tym że: aby zdalny obiekt mógł podlegać RMI musi być określony za pomocą zdalnej referencji obiektu zdalne referencje obiektów mogą być przekazane jako argumenty i zwracane jako rezultaty ze zdalnych wywołań metod Zdalny interfejs Interfejs, który deklaruje metody implementowane przez klasę zdalnego obiektu, na przykład jako publiczne metody w języku Java. Obiekty w innych procesach mogą wywoływać tylko te metody, które należą do zdalnego interfejsu, podczas gdy lokalne obiekty mogą wywoływać metody zdalnego interfejsu jak również inne publiczne metody implementowane przez zdalny obiekt. 4 / 1
RMI Architektura Zdalne wywołanie metod jest realizowane przez szereg obiektów i modułów. Ilustruje to poniższy schemat, gdzie obiekt A aplikacji klienta wywołuje metodę w zdalnym obiekcie B aplikacji serwera, dla którego posiada zdalną referencję obiektu. Po obu stronach połączenia istnieją: moduł zdalnych referencji i moduł komunikacyjny. RMI działa w oparciu o nie. 5 / 1
RMI Architektura - moduł komunikacyjny Oba moduły komunikacyjne realizują protokół request-reply, który transmituje komunikaty request i reply między klientem i serwerem. Struktura komunikatów request i reply jest następująca: Moduł komunikacyjny używa jedynie 3 pierwszych pól, które określają typ komunikatu, jego identyfikator i zdalną referencję obiektu, którego metoda ma być wywołana. Identyfikacja metody i serializacja argumentów spoczywa na oprogramowaniu RMI. Moduł komunikacyjny serwera wybiera dyspozytora klasy obiektu docelowego wywołania, przekazując mu lokalną referencję, która jest udostępniona przez moduł zdalnych referencji w odpowiedzi na zapytanie o zdalny identyfikator obiektu. 6 / 1
RMI Architektura - moduł zdalnych referencji Moduł zdalnych referencji (MZR) odpowiada za translację między lokalnymi i zdalnymi referencjami obiektu i za tworzenie zdalnych referencji obiektu. W tym celu MZR w każdym procesie ma tablicę zdalnych obiektów (TZO), która odzwierciedla powiązania między lokalnymi referencjami obiektów tego procesu i zdalnymi referencjami obiektów (które są unikalne w obrębie systemu rozproszonego). Tablica ta posiada: wpisy dla wszystkich zdalnych obiektów udostępnianych przez proces, np. zdalny obiekt B jest zapisany w TZO serwera wpisy dla wszystkich lokalnych pośredników (proxy), np. proxy dla obiektu B jest zapisany w TZO klienta Działanie MZR: kiedy zdalny obiekt ma być przekazany jako argument lub rezultat po raz pierwszy, MZR tworzy zdalną referencję obiektu i dodaje ją do TZO kiedy zdalna referencja obiektu jest odebrana w komunikacie request lub reply, MZR jest pytany o odpowiadającą lokalną referencję obiektu, która może odnosić się albo do proxy albo do zdalnego obiektu. Jeśli zdalna referencja obiektu nie występuje w TZO, tworzy się nowe proxy i MZR zapisuje je do swojej TZO. 7 / 1
RMI Warstwa RMI - pośrednik (proxy) Warstwa RMI występuje pomiędzy obiektami aplikacji a modułami komunikacyjnym i zdalnych referencji. Proxy Rola pośrednika polega na tym, by uczynić zdalne wywołanie metod przeźroczystym dla klienta, poprzez stworzenie wrażenia, że proxy jest lokalnym obiektem, choć w praktyce zamiast wywołać metodę tego obiektu, pośrednik przekazuje to wywołanie w komunikacie do zdalnego obiektu. Pośrednik ukrywa szczegóły zdalnej referencji obiektu, serializacji argumentów i de-serializacji rezultatów, a także wysyłania i odbioru komunikatów. Dla każdego zdalnego obiektu, dla którego proces posiada zdalną referencję istnieje oddzielny pośrednik. Klasa pośrednika implementuje metody zadeklarowane w zdalnym interfejsie zdalnego obiektu, który reprezentuje. To gwarantuje, że zdalne wywołania metod są odpowiednie do typu zdalnego obiektu. Jednakże pośrednik implementuje metody całkiem inaczej - każda metoda pośrednika serializuje referencję do obiektu docelowego, swój własny identyfikator i argumenty wywołania, a następnie wysyła je do obiektu docelowego, czeka na komunikat zwrotny (reply), deserializuje go i zwraca rezultat do metody wywołującej. 8 / 1
RMI Warstwa RMI - dyspozytor (dispatcher) i szkielet (skeleton) Dyspozytor Serwer posiada po jednym dyspozytorze i szkielecie dla każdej klasy reprezentujacej zdalny obiekt, w naszym przykładzie serwer ma dyspozytora i szkielet dla klasy zdalnego obiektu B. Dyspozytor odbiera komunikat request z modułu komunikacyjnego. Na podstawie pola methodid wybiera odpowiednią metodę szkieletu, przekazując jej komunikat request. Dyspozytor i pośrednik używają tych samych identyfikatorów methodid do metod zdalnego interfejsu. Szkielet Klasa zdalnego obiektu posiada szkielet, który implementuje metody zadeklarowane w zdalnym interfejsie. Są one zaimplementowane całkiem inaczej niż metody zdalnego obiektu. Metoda szkieletu de-serializuje argumenty komunikatu request i wywołuje odpowiednią metodę zdalnego obiektu. Następnie czeka na zrealizowanie wywołania i serializuje jego rezultat, razem z ewentualnymi wyjątkami, w komunikat reply do metody pośrednika, który inicjował wywołanie. 9 / 1
RMI Generowanie klas pośredników, dyspozytorów i szkieletów Klasy pośredników, dyspozytorów i szkieletów są generowane automatycznie przez kompilator interfejsu (interface compiler). Dla Java RMI, metody zdalnego obiektu są deklarowane jako interfejsy języka Java i są implementowane przez klasę zdalnego obiektu. Kompilator interfejsu języka Java RMI rmic generuje klasy namiastek (stub) w oparciu o klasę zdalnego obiektu. Dla implementacji standardu CORBA nazywanej Orbix, interfejsy zdalnych obiektów są definiowane w języku CORBA IDL, a następnie używa się kompilatora interfejsu do generowania klas pośredników, dyspozytorów i szkieletów w C++ lub innych językach. Obiekty CORBA są opisane za pomocą interfejsów wyrażonych w języku Interface Definition Language (IDL), zaprojektowanym przez OMG do definiowania interfejsu obiektów. Interfejs obiektu określa operacje które obiekt realizuje, ale nie określa sposobu implementacji. Implementacja obiektu CORBA jest domeną wybranego języka programowania, np. Java lub C++. Interfejs określa kontrakt wiążący kod użytkownika obiektu i kod implementujący obiekt. Interfejsy IDL są niezależne od języka programowania. IDL definiuje powiązania z wieloma językami programowania (C, C++, Java, Ada, COBOL, Smalltalk, Objective C i Lisp). Pozwala to wybrać dla implementacji obiektów najbardziej odpowiedni język, jak również nie ogranicza programistów klienta co do wyboru identycznego języka jaki użyto dla obiektu. 10 / 1
RMI Programy serwera i klientów Program serwera zawiera klasy dyspozytorów i szkieletów, razem z implementacjami klas wszystkich zdalnych obiektów, które udostępnia serwer. Dodatkowo program serwera zawiera sekcję inicjalizacji, zwykle w funkcji main kodu Java lub C++. Sekcja inicjalizacji odpowiada za tworzenie i inicjalizację co najmniej jednego zdalnego obiektu, spośród obiektów serwera; dodatkowe zdalne obiekty mogą być tworzone w odpowiedzi na żądania klientów. Sekcja inicjalizacji może ponadto rejestrować niektóre zdalne obiekty w tzw. binderze. Generalnie rejestrowany będzie tylko jeden zdalny obiekt, przez który realizowany jest dostęp do pozostałych. Program klienta zawiera klasy pośredników dla wszystkich zdalnych obiektów, które będzie wywoływać. Klient może używać bindera do wyszukiwania zdalnych referencji obiektów. Binder w systemie rozproszonym jest oddzielną usługą, która zarządza tablicą zawierającą odwzorowania nazw (tekstowych) na zdalne referencje obiektów. Usługi tej używają serwery do rejestrowania swoich zdalnych obiektów pod odpowiednimi nazwami, a klienci do wyszukiwania obiektów za pomocą nazw. Przykłady usług typu binder: Java RMIregistry i CORBA Naming Service 11 / 1
Java API dla RMI Pierwsza wersja Java RMI używała wszystkich klas pokazanych na diagramie architektury. Począwszy od Javy w wersji 1.2 zastosowano mechanizm refleksji do implementacji uniwersalnego dyspozytora i eliminacji szkieletów klas usługowych. Klasy pośredników klienta (proxy) są generowane przez kompilator rmic na podstawie skompilowanych klas usługowych (servant), a nie na podstawie definicji zdalnego interfejsu. Rys. powyżej pokazuje strukturę dziedziczenia klas obsługujących serwery Java RMI. Jedyną klasą istotną dla programisty jest UnicastRemoteObject, z której musi dziedziczyć każda klasa usługowa (servant). Klasa UnicastRemoteObject dziedziczy z abstrakcyjnej klasy RemoteServer, która deklaruje abstrakcyjne wersje metod wymaganych przez zdalne serwery. Inną podklasą RemoteServer jest Activatable, która dostarcza możliwości automatycznej aktywacji obiektu serwera, kiedy zajdzie taka potrzeba. 12 / 1
Użycie mechanizmu refleksji Refleksja jest używana do przekazywania w komunikacie request informacji o wywoływanej metodzie. Odbywa się to za pomocą klasy Method pakietu reflection. Każda instancja klasy Method reprezentuje charakterystykę konkretnej metody (jej klasę, typy argumentów, zwracanej wartości i wyjątków). UWAGA: instancja klasy Method może być uruchomiona dla obiektu odpowiedniej klasy za pomocą jej metody invoke. Metoda invoke wymaga dwu argumentów: pierwszy specyfikuje wywoływany obiekt drugi jest tablicą elementów typu Object, zawierającą argumenty Rezultat jest zwracany jako typ Object. Kolejność operacji: Pośrednik (proxy) szereguje informacje dot. metody i jej argumentów w komunikat request. Dane metody szereguje się w postaci obiektu klasy Method. Następnie pakuje się argumenty w tablicy elementów typu Object i szereguje się tę tablicę. Dyspozytor de-szereguje obiekt klasy Method i argumenty z tablicy elementów typu Object z komunikatu request. Jak zwykle, zdalna referencja obiektu docelowego jest de-szeregowana i odpowiednia lokalna referencja obiektu jest odczytana z modułu zdalnych referencji. Dyspozytor wywołuje metodę invoke obiektu klasy Method, z parametrami oznaczającymi obiekt docelowy i tablicę wartości argumentów. 13 / 1
Przekazywanie parametrów i rezultatu Dowolny serializowalny obiekt - tzn. taki który implementuje interfejs Serializable - może być przekazany jako argument lub rezultat. Wszystkie typy proste i zdalne obiekty są serializowalne. Klasy argumentów i rezultatu są ładowane w razie potrzeby do odbiorcy przez system RMI. Parametry i rezultaty mogą być przekazywane na dwa sposoby: Przez referencję. Jeśli typ parametru lub rezultatu jest definiowany jako zdalny interfejs, odpowiedni argument lub rezultat jest zawsze przekazywany jako zdalna referencja obiektu. Np., w 3 przykładzie RMI, metoda newshape zwraca rezultat typu Shape - czyli zdalny interfejs. Kiedy zdalna referencja obiektu jest odebrana, może być ona użyta w wywołaniu RMI zdalnego obiektu, do którego się odnosi. Przez wartość. Wszystkie serializowalne nie-zdalne obiekty są kopiowane i przekazywane przez wartość. Np., w 2 przykładzie RMI, argument metody add i rezultat metody get są typu Event, (który jest serializowalny) i są przekazywane przez wartość. Gdy obiekt jest przekazywany przez wartość, nowy obiekt jest tworzony w procesie odbiorcy. Metody tego nowego obiektu mogą być wywoływane lokalnie, powodując zmianę stanu w stosunku do oryginalnego obiektu w procesie nadawcy. Argumenty i zwracane wartości są serializowane do strumienia z użyciem następujących reguł: gdy serializowany jest obiekt który implementuje interfejs Remote, jest on zastępowany przez zdalną referencję obiektu, która zawiera nazwę klasy obiektu. gdy serializowany jest dowolny obiekt, informacja o jego klasie jest uzupełniona o lokalizację klasy (URL), co umożliwia ładowanie klasy do procesu odbiorcy. 14 / 1
Bezpieczeństwo Począwszy od wersji 1.2 języka Java istnieje możliwość definiowania przez użytkownika zabezpieczeń maszyny wirtualnej Java. Zabezpieczenia te mogą obejmować: definiowanie akceptowanych połączeń sieciowych (adresów IP, numerów portów) uprawnień do odczytu lub zapisu plików przez zewnętrzne aplikacje uprawnień do ładowania kodu z innych maszyn wirtualnych Java. Ostatni przypadek jest szczególnie ważny, gdyż przekazanie obiektu typu Serializable przez wartość powoduje załadowanie jego kodu i możliwość (a czasem ryzyko) wykonywania jego metod. W przypadku ładowania kodu z zewnątrz maszyna wirtualna Java wręcz wymaga, aby twórca aplikacji zdefiniował i wdrożył zasady bezpieczeństwa. Jeśli nie zostanie to zrobione, wykonanie programu zakończy następujący wyjątek: java.security.accesscontrolexception: access denied \ (java.net.socketpermission 127.0.0.1:1099 connect,resolve) Definicja polityki bezpieczeństwa powinna się znaleźć w pliku o nazwie java.policy. Taki plik znajduje się zazwyczaj w podkatalogu jre/lib/security katalogu instalacji środowiska Java, ale najczęściej nie jest używany do uruchamiania aplikacji, gdyż zawiera tylko zbiór domyślnych reguł. 15 / 1
Bezpieczeństwo Zazwyczaj twórca aplikacji definiuje własny plik java.policy, którego następnie używa przy uruchamianiu aplikacji. Najprostsza możliwa zawartość pliku java.policy, nie zabezpieczająca maszyny wirtualnej w żaden sposób, jest następująca: 1 grant { 2 // Pelne uprawnienia dla wszystkich ladowanych zdalnie klas 3 permission java. security. AllPermission ; 4 }; Za odczytanie i realizowanie w programie Java zdefiniowanych wcześniej zasad bezpieczeństwa odpowiadają klasy tzw. zarządców bezpieczeństwa: ogólna SecurityManager specjalizowana (dla RMI) RMISecurityManager. Zarządca bezpieczeństwa powinien zostać utworzony na samym początku funkcji main, na przykład w następujący sposób: 3 public static void main ( String args []){ 4 if ( System. getsecuritymanager () == null ) 5 System. setsecuritymanager ( new RMISecurityManager ()); 16 / 1
Przykład - wymagania funkcjonalne Przykład dotyczy aplikacji prostego monitora komunikatów, który odbiera komunikaty tekstowe od klientów i wyświetla je na ekranie serwera. Funkcjonalność: Serwer obsługuje listę odebranych komunikatów, dostarczając klientom operacji do przesyłania komunikatów tekstowych. Każdy odebrany komunikat jest wyświetlany przez serwer. 17 / 1
Przykład - zdalny interfejs Zdalne interfejsy są definiowane przez dziedziczenie z interfejsu Remote dostępnego w pakiecie java.rmi. Metody interfejsu muszą generować wyjątek RemoteException, a poza tym dowolne wyjątki specyficzne dla aplikacji. Przykład zdalnego interfejsu: 1 import java. rmi.*; 2 3 public interface MonitorInterface extends Remote { 4 void komunikuj ( String message ) throws RemoteException ; 5 boolean zarejestruj ( String nick ) throws RemoteException ; 6 } Ten zdalny interfejs deklaruje tylko dwie metody (komunikuj i zarejestruj), które posiadają jeden parametr typu String, przez który przekazywany jest łańcuch reprezentujący komunikat i nazwę użytkownika. 18 / 1
Przykład - kod serwera Kod serwera składa się z klas MonitorServer i MonitorServant. MonitorServer jest klasą główną i składa się z dwu pól i dwu metod. Pola są obiektami klas: Registry, który reprezentuje rmiregistry MonitorServant, który reprezentuje klasę usługową (servant). Metoda main serwera tworzy instancję klasy MonitorServer. Jeśli wystąpi jakikolwiek wyjątek, wyświetlany jest stos wywołań i kończy się działanie serwera. 1 import java.rmi. RemoteException ; 2 import java.rmi. registry. LocateRegistry ; 3 import java.rmi. registry. Registry ; 4 import java.rmi. server. UnicastRemoteObject ; 5 6 public class MonitorServer { 7 Registry reg ; /* rejestr nazw obiektow */ 8 MonitorServant servant ; 9 10 public static void main ( String [] args ) { 11 try { 12 MonitorServer s = new MonitorServer (); 13 } catch ( Exception e) { 14 e. printstacktrace (); System.exit (1); 15 } 16 } 19 / 1
Przykład - kod serwera Chroniony konstruktor klasy MonitorServer najpierw tworzy zdalny obiekt rejestru, pracujący na wybranym porcie. Następnie tworzy instancję klasy MonitorServant i wiąże ją z pewną nazwą w rejestrze RMIregistry. Wartość reprezentująca to powiązanie jest zdalną referencją obiektu, a jej typ jest typem jego zdalnego interfejsu - MonitorInterface. 18 protected MonitorServer () throws RemoteException { 19 try { 20 /* Utworzenie rejestru nazw */ 21 reg = LocateRegistry. createregistry (1099); 22 /* utworzenie obiektu dostepnego zdalnie */ 23 servant = new MonitorServant (); 24 /* zwiazanie z obiektem nazwy */ 25 reg. rebind (" MonitorServer ", servant ); 26 System.out. println (" Server READY "); 27 } catch ( RemoteException e) { 28 e. printstacktrace (); throw e; 29 } 30 } 31 } 20 / 1
Przykład - kod klasy usługowej MonitorServant 1 import java.rmi. RemoteException ; 2 import java.rmi. registry. LocateRegistry ; 3 import java.rmi. registry. Registry ; 4 import java.rmi. server. UnicastRemoteObject ; 5 6 public class MonitorServant 7 extends UnicastRemoteObject 8 implements MonitorInterface { 9 10 public MonitorServant () throws RemoteException { 11 } 12 13 // Metoda implementujaca funkcje komunikuj () interfejsu MonitorInterface 14 public void komunikuj ( String message ) throws RemoteException { 15 System.out. println (" Server. komunikuj () " + message ); 16 } 17 // Metoda implementujaca funkcje zarejestruj () interfejsu MonitorInterface 18 public boolean zarejestruj ( String nick ) throws RemoteException { 19 System.out. println (" Server. zarejestruj () " + nick ); 20 return true ; 21 } 22 } 21 / 1
Przykład - kod klasy klienta MonitorClient 1 import java.rmi. NotBoundException ; 2 import java.rmi. RemoteException ; 3 import java.rmi. registry. LocateRegistry ; 4 import java.rmi. registry. Registry ; 5 6 public class MonitorClient { 7 public static void main ( String [] args ) { 8 if (args. length < 2) { 9 System.out. println (" Usage : MonitorClient <server host name > <msg >"); 10 System.exit ( -1); 11 } 12 MonitorInterface remoteobject ; // referencja do zdalnego obiektu 13 Registry reg ; // rejestr nazw obiektow 14 try { 15 // pobranie referencji do rejestru nazw obiektow 16 reg = LocateRegistry. getregistry (args [0]); 17 // odszukanie zdalnego obiektu po jego nazwie 18 remoteobject = ( MonitorInterface ) reg. lookup (" MonitorServer "); 19 // wywolanie metody zdalnego obiektu 20 remoteobject. komunikuj (args [1]); 21 remoteobject. zarejestruj ("Luc "); 22 } 23 catch ( RemoteException e) { 24 e. printstacktrace (); 25 } 26 catch ( NotBoundException e) { 27 e. printstacktrace (); 28 } 29 } 30 } 22 / 1
Przykład - kompilacja i uruchomienie W celu kompilacji i uruchomienia rozproszonej aplikacji należy wykonać: 1 Kompilacja klasy serwera lm@lm-mint ~ $ javac MonitorServer.java 2 Kompilacja klasy klienta lm@lm-mint ~ $ javac MonitorClient.java 3 Generowanie namiastki zdalnego obiektu, w wyniku czego powstaje bajtkod zawarty w pliku MonitorServant_Stub.class lm@lm-mint ~ $ rmic MonitorServant 4 Uruchomienie serwera lm@lm-mint ~ $ java MonitorServer & Server READY 5 Uruchomienie klienta, aby umieścić na serwerze nowy komunikat lm@lm-mint ~ $ java MonitorClient localhost s1 Server.komunikuj() s1 Server.zarejestruj() Luc 23 / 1
Przykład 2 - rozbudowany monitor Przykład dotyczy aplikacji nieco rozbudowanego monitora komunikatów, który odbiera komunikaty tekstowe od klientów i wyświetla je na ekranie serwera, a ponadto umożliwia przeglądanie przez klientów zarejestrowanych komunikatów. Funkcjonalność: Serwer obsługuje listę odebranych komunikatów, dostarczając klientom operacji do przesyłania komunikatów tekstowych. Każdy odebrany komunikat jest wyświetlany i rejestrowany wraz z datą przez serwer. Serwer ponadto udostępnia operacje umożliwiające klientom odczyt ilości komunikatów, wybranego komunikatu i całej ich listy. 24 / 1
Przykład 2 - zdalny interfejs Zdalne interfejsy są definiowane przez dziedziczenie z interfejsu Remote dostępnego w pakiecie java.rmi. Metody interfejsu muszą generować wyjątek RemoteException, a poza tym dowolne wyjątki specyficzne dla aplikacji. Przykład zdalnego interfejsu: 1 import java. rmi.*; 2 import java.util. Vector ; 3 4 public interface MonitorInterface extends Remote { 5 void Inform ( String message ) throws RemoteException ; 6 Event get ( int i) throws RemoteException ; 7 void add ( Event event ) throws RemoteException ; 8 int getcount () throws RemoteException ; 9 Vector <Event > allevents () throws RemoteException ; 10 } Zdalny interfejs deklaruje następujące metody: inform, która posiada jeden parametr o nazwie message, przez który przekazywany jest łańcuch reprezentujący komunikat. get do odczytu komunikatu zapisanego pod wybranym indeksem add do rejestracji nowego komunikatu przez serwer getcount do odczytu ilości zarejestrowanych komunikatów allevents do odczytu wszystkich zarejestrowanych komunikatów 25 / 1
Przykład 2 - kod serwera Kod serwera składa się z klas MonitorServer i MonitorServant. MonitorServer jest klasą główną i składa się z dwu pól i dwu metod. Pola są instancjami klas: Registry, który reprezentuje rmiregistry MonitorServant, który reprezentuje klasę usługową (servant). Metoda main serwera tworzy instancję klasy MonitorServer. Jeśli wystąpi jakikolwiek wyjątek, wyświetlany jest stos wywołań i kończy się działanie serwera. 1 import java.rmi. RemoteException ; 2 import java.rmi. registry. LocateRegistry ; 3 import java.rmi. registry. Registry ; 4 import java.rmi. server. UnicastRemoteObject ; 5 6 public class MonitorServer { 7 Registry reg ; /* rejestr nazw obiektow */ 8 MonitorServant servant ; 9 10 public static void main ( String [] args ) { 11 try { 12 MonitorServer s = new MonitorServer (); 13 } catch ( Exception e) { 14 e. printstacktrace (); System.exit (1); 15 } 16 } 26 / 1
Przykład 2 - kod serwera Chroniony konstruktor klasy MonitorServer najpierw tworzy zdalny obiekt rejestru, pracujący na wybranym porcie. Nastepnie tworzy instancję klasy MonitorServant i wiąże ją z pewną nazwą w rejestrze RMIregistry. Wartość reprezentująca to powiązanie jest zdalną referencją obiektu, a jej typ jest typem jego zdalnego interfejsu - MonitorInterface. 18 protected MonitorServer () throws RemoteException { 19 try { 20 /* Utworzenie rejestru nazw */ 21 reg = LocateRegistry. createregistry (1099); 22 /* utworzenie obiektu dostepnego zdalnie */ 23 servant = new MonitorServant (); 24 /* zwiazanie z obiektem nazwy */ 25 reg. rebind (" MonitorServer ", servant ); 26 System.out. println (" Server v2 READY "); 27 } catch ( RemoteException e) { 28 e. printstacktrace (); throw e; 29 } 30 } 31 } 27 / 1
Przykład 2 - kod klasy usługowej MonitorInterface 1 import java.rmi. RemoteException ; 2 import java.rmi. registry. LocateRegistry ; 3 import java.rmi. registry. Registry ; 4 import java.rmi. server. UnicastRemoteObject ; 5 import java.util. Vector ; 6 7 public class MonitorServant extends UnicastRemoteObject implements 8 MonitorInterface { 9 10 private Vector <Event > events ; 11 12 public MonitorServant () throws RemoteException { 13 events = new Vector <Event >(); 14 } 15 16 // Metoda implementujaca funkcje Inform () interfejsu MonitorInterface 17 public void Inform ( String message ) throws RemoteException { 18 events.add ( new Event ( message )); 19 System.out. println (" Server : " + message ); 20 } 21 22 public Event get ( int i) throws RemoteException { 23 if (i < 0 i >= events. size ()) 24 return null ; 25 return events. get (i); 26 } 28 / 1
Przykład 2 - kod klasy usługowej MonitorInterface 28 public void add ( Event event ) throws RemoteException { 29 events.add ( event ); 30 } 31 32 public int getcount () throws RemoteException { 33 return events. size (); 34 } 35 36 public Vector <Event > allevents () throws RemoteException { 37 return events ; 38 } 39 40 } 29 / 1
Przykład - kod klasy klienta MonitorClient 1 import java.rmi. NotBoundException ; 2 import java.rmi. RemoteException ; 3 import java.rmi. registry. LocateRegistry ; 4 import java.rmi. registry. Registry ; 5 import java.util. Vector ; 6 7 public class MonitorClient { 8 9 public static void main ( String [] args ) { 10 if (args. length < 2) { 11 System.out. println (" Usage : MonitorClient <server host name > <msg >"); 12 System.exit ( -1); 13 } 14 MonitorInterface remoteobject ; // referencja do zdalnego obiektu 15 Registry reg ; // rejestr nazw obiektow 16 try { 17 // pobranie referencji do rejestru nazw obiektow 18 reg = LocateRegistry. getregistry (args [0]); 19 // odszukanie zdalnego obiektu po jego nazwie 20 remoteobject = ( MonitorInterface ) reg. lookup (" MonitorServer "); 21 // wywolanie metody zdalnego obiektu 22 remoteobject. Inform (args [1]); 23 remoteobject.add ( new Event (" Event sent from client " )); 24 int cnt = remoteobject. getcount (); 25 System.out. println (" There are " + cnt + " registered events :"); 26 Vector <Event > e = remoteobject. allevents (); 27 for ( Event ev : e) 28 ev. print (); 29 Event back = remoteobject. get (0); 30 back. print (); 31 } catch ( RemoteException e) { 32 e. printstacktrace (); 33 } catch ( NotBoundException e) { 30 / 1
Przykład 2 - kompilacja i uruchomienie W celu kompilacji i uruchomienia rozproszonej aplikacji należy wykonać: 1 Kompilacja klasy serwera lm@lm-mint ~$ javac MonitorServer.java 2 Kompilacja klasy klienta lm@lm-mint ~$ javac MonitorClient.java 3 Generowanie namiastki zdalnego obiektu, w wyniku czego powstaje bajtkod zawarty w pliku MonitorServant_Stub.class. lm@lm-mint ~$ rmic MonitorServant 4 Uruchomienie serwera lm@lm-mint ~$ java MonitorServer & Server v2 READY 5 Uruchomienie klienta aby umieścić na serwerze nowe komunikaty lm@lm-mint ~$ java MonitorClient localhost s1 Server: s1 There are 1 registered events: Mon May 02 23:16:34 CEST 2011: s1 lm@lm-mint ~$ java MonitorClient localhost s2 Server: s2 There are 2 registered events: Mon May 02 23:16:34 CEST 2011: s1 Mon May 02 23:16:51 CEST 2011: s2 31 / 1