Personal tools

Sr-05-lab-1.0

From Studia Informatyczne

Spis treści

Java Remote Method Invocation — podstawy

Wprowadzenie

Najkrócej ujmując, pakiet RMI (ang. Remote Method Invocation) to mechanizm obiektowego RPC dla języka Java. Pozwala on aplikacji klienta wywoływać metody zdalnych obiektów języka Java, czyli obiektów z innej przestrzeni adresowej, mogącej znajdować się na tej samej lub innej maszynie.

Celem niniejszego ćwiczenia jest zaprezentowanie podstaw tej technologii oraz konstrukcja nieskomplikowanej aplikacji rozproszonej z wykorzystaniem interfejsu RMI. W wykonaniu zadania pomoże prezentacja prostego programu, przedstawionego i objaśnionego w całości.

Procesy

Środowisko RMI obejmuje trzy procesy:

  • proces klienta, wywołującego metody zdalnych obiektów,
  • proces serwera, dostarczającego zdalnych obiektów,
  • proces rejestru (ang. object register), wiążącego obiekty z ich nazwami. Obiekty są zgłaszane do rejestru przez serwer. Aplikacja klienta może uzyskać dostęp do zarejestrowanego obiektu pod warunkiem, że najpierw używając wcześniej poznanej nazwy obiektu, skorzysta z rejestru, by otrzymać referencję do obiektu odpowiadającą tej nazwie.

Etapy konstrukcji aplikacji

Podczas tworzenia trzech procesów, tworzących środowisko RMI, należy przestrzegać następujących zasad:

  1. Interfejs obiektu i jego implementacja W pierwszej kolejności należy utworzyć interfejs zdalnego obiektu. Interfejs jest abstrakcją obiektu dostępną dla klienta. Rzeczywisty obiekt będący realizacją interfejsu znajduje się w serwerze i klient nie ma do niego dostępu. Interfejs zawiera specyfikacje zdalnie dostępnych dla klienta metod. Każda z metod deklarowanych w interfejsie powinna zgłaszać wyjątek typu java.rmi.RemoteException. Aby obiekt mógł być zdalnie dostępny, jego interfejs musi dziedziczyć z interfejsu Remote. Od klasy implementującej interfejs wymaga się zaś, by dziedziczyła z klasy java.rmi.server.UnicastRemoteObject.
  2. Program serwera Serwer to proces, w którego przestrzeni adresowej znajduje się obiekt. Program serwera musi mieć dostęp zarówno do interfejsu, jak i do klasy go implementującej.
  3. Program klienta Program klienta może być zrealizowany na różne sposoby, między innymi jako samodzielna aplikacja czy jako aplet języka Java. Od programu klienta wymaga się tylko, by miał dostęp do definicji interfejsu zdalnego obiektu.
  4. Utworzenie pieńka (ang. stub) dla klienta. Pieniek powstaje na bazie skompilowanych klas odpowiadających interfejsowi i klasie go implementującej. Pieniek u klienta służy mu jako obiekt pomocniczy, reprezentujący faktyczny zdalny obiekt. W rzeczywistości klient odwołuje się bezpośrednio do swojego pieńka, zatem również referencja obiektu używana przez klienta jest w istocie referencją do jego pieńka. Dopiero w obiekcie pieńka następuje odwołanie do zdalnego obiektu. Pieniek zajmuje się również przekształcaniem wywołań metod na komunikaty protokołu sieciowego.
  5. Aktywacja rejestru Model Java RMI wymaga istnienienia rejestru. Rejestr może zostać uruchomiony w procesie serwera (z wykorzystaniem klasy LocateRegistry) lub poza nim (polecenie rmiregistry w linii komend), może też działać na odrębnej maszynie.

Przykład prostej aplikacji

Poniżej przedstawiono i omówiono przykładową aplikację RMI. Jej działanie polega na wywołaniu w zdalnym obiekcie przez klienta metody, która wypisze po stronie serwera tekst na ekran.

Kod programu

Definicja interfejsu obiektu, zapisana w pliku o nazwie HelloInterface.java (zgodnie z wymogami języka java nazwa pliku musi być taka sama, jak nazwa interfejsu czy klasy), jest następująca:

import java.rmi.*;

public interface HelloInterface extends Remote
{
   void Hello(String message) throws RemoteException;
}

Interfejs zdalnego obiektu dziedziczy z interfejsu Remote. Metoda Hello() to jedyna operacja udostępniona klientom korzystającym z obiektu. A oto kod serwera, zapisany w pliku o nazwie HelloServer.java:

import java.net.*;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
 
public class HelloServer extends java.rmi.server.UnicastRemoteObject
                         implements HelloInterface 
{
  Registry reg;  // rejestr nazw obiektów 

  /**
   * Metoda, implementująca funkcję Hello() interfejsu HelloInterface, którą
   * zdalnie wywołuje klient
   */
  public void Hello(String message) throws RemoteException
  {
    System.out.println(message);
  }

  public HelloServer() throws RemoteException
  {
    try
    {
      // Utworzenie rejestru nazw
      reg = LocateRegistry.createRegistry(1099);
      // związanie z obiektem nazwy
      reg.rebind("HelloServer", this); 
    }
    catch(RemoteException e)
    {
      e.printStackTrace();
      throw e;
    }
  }
  
  public static void main(String args[])
  {
    // utworzenie obiektu dostępnego zdalnie
    try
    {
      HelloServer s = new HelloServer();
    } 
    catch (Exception e)
    {
       e.printStackTrace();
       System.exit(1);
    }
  }
}

Klasa HelloServer jest zgodnie z wymogami pochodną klasy UnicastRemoteObject. Obiekt reg klasy Registry to rejestr nazw obiektów. Zostaje utworzony i uruchomiony wywołaniem LocateRegistry.createRegistry i nasłuchuje portu numer 1099 (domyślnego dla usługi rejestru). Jak zatem widać, przyjęto powyżej rozwiązanie, w którym rejestr utuchomiany jest w programie serwera. Wywołanie reg.rebind("HelloServer", this) tworzy powiązanie obiektu (ściślej — referencji do obiektu) z nazwą, tutaj z nazwą ``HelloServer". Klient, szukając obiektu w rejestrze, powinien posłużyć się właśnie tą nazwą. W funkcji main() w pierwszej kolejności następuje utworzenie zarządcy bezpieczeństwa, który umożliwi ewentualne ładowanie zdalnego kodu podczas przekazywania parametrów. Jego istnienie w przykładowym programie nie jest wymagane, ale staje się takim, gdy programy przesyłają parametry będące obiektami klasy Serializable (patrz następne ćwiczenie). W drugiej kolejności tworzony jest obiekt klasy HelloServer, implementującej zdalny interfejs. Po tej chwili obiekt przyjmuje i obsługuje wywołania klientów.

Poniżej zamieszczono kod programu klienta, zapisany oczywiście w pliku o nazwie HelloClient.java.

import java.net.*;
import java.rmi.*;
import java.rmi.registry.*;
public class HelloClient
{
  public static void main(String args[])
  {
    HelloInterface remoteObject;   // referencja do zdalnego obiektu
    Registry reg;                  // rejestr nazw obiektów
    String serverAddr=args[0];
    try
    {
      // pobranie referencji do rejestru nazw obiektów
      reg = LocateRegistry.getRegistry(serverAddr);
      // odszukanie zdalnego obiektu po jego nazwie
      remoteObject = (HelloInterface) reg.lookup("HelloServer");
      // wywołanie metody zdalnego obiektu
      remoteObject.Hello("Hello world!");
    }
    catch(RemoteException e)
    {
      e.printStackTrace();
    }
    catch(NotBoundException e)
    {
      e.printStackTrace();
    }
  }
}

Zmienna remoteObject jest referencją do zdalnego obiektu. Zmienna reg wskazuje na rejestr nazw obiektów. A w zmiennej serverAddr pamiętany jest (przekazany jako parametr wywołania programu) adres IP maszyny, na której działa serwer. Zakłada się tu, że serwer i rejestr działają na tej samej maszynie.

W pierwszej kolejności klient uzyskuje dostęp do rejestru nazw obiektów za pomocą wywołania LocateRegistry.getRegistry(serverAddr). Następnie pobiera z rejestru referencję do obiektu o nazwie ``HelloServer". Robi to, stosując funkcję lookup klasy Register:

remoteObject = (HelloInterface) reg.lookup("HelloServer");

Konieczne jest przy tym rzutowanie pozyskanej referencji na typ interfejsu zdalnego obiektu, tutaj HelloInterface. Dysponując wskazaniem na obiekt (ściślej — klient dysponuje referencją do swojego pieńka, a dopiero ten ostatni — do zdalnego obiektu), klient wywołuje jego operacje tak, jakby obiekt był w jego lokalnej przestrzeni adresowej.

W powyższym prostym przykładzie klient nie przesyła nic do serwera. Oczywiście w typowej aplikacji takie przesłania, najczęściej w obie strony, mają miejsce. Przesyłanie wiadomości realizowane jest z reguły na dwa sposoby: poprzez przekazywanie w metodach parametrów predefiniowanych typów języka Java lub poprzez przekazywanie obiektów zdefiniowanych przez siebie klas. Bez względu na metodę, argumenty te stają się w rzeczywistości częścią komunikatu, przesyłanego przez sieć komputerową. W przypadku przesyłania obiektów zachodzi transfer kodu obiektu (ang. code transfer lub code loading) pomiędzy serwerem a klientem. Użycie tej metody jest przedmiotem innego ćwiczenia.

Kompilacja i uruchomienie programu

Podczas kompilacji i uruchomienia należy pamiętać o następujących zasadach:

  • Program klienta powinien mieć dostęp do definicji interfejsu oraz do klasy implementującej interfejs
  • Kod klasy implementującej interfejs powinien mieć dostęp do definicji interfejsu

Kompilacja programu serwera oraz klienta:

# javac HelloServer.java
# javac HelloClient.java

Utworzenie pieńka (ang. stub) dla klienta oraz szkieletu (ang. skeleton) dla serwera:

# rmic HelloServer

Polecenie to wygeneruje plik HelloServer_Stub.class, będący pieńkiem po stronie klienta.

Uruchomienie procesów:

# java HelloServer
# java HelloClient <adres IP serwera>

Zadanie

Należy zaprojektować i zrealizować z wykorzystaniem interfejsu Java RMI aplikację mini czat. Aplikacja powinna umożliwiać dołączanie nowych klientów do środowiska (ich liczba może być z góry ograniczona lub nawet stała), a następnie wymianę krótkich wiadomości pomiędzy nimi. To, co wyśle jeden klient, otrzymają następnie wszyscy pozostali. Przyjmowaniem wiadomości od klientów i powielaniem tych wiadomości powinien zajmować się zdalny obiekt, uruchomiony w serwerze. Dla ułatwienia, można się wzorować na przedstawionym wyżej programie.