Sr-13-lab-1.0

From Studia Informatyczne

Spis treści

CORBA

Wprowadzenie

CORBA (ang. Common Object Request Broker Architecture) to przemysłowy standard platformy programistycznej, służącej do budowania rozproszonych aplikacji obiektowych. Platformę CORBA wyróżnia to, że umożliwia współpracę aplikacji zaimplementowanych w różnych językach programowania, oraz istnienie bogatego zbioru usług pomocniczych ogólnego przeznaczenia (ang. CORBA Services).

Etapy tworzenia aplikacji

W pierwszej kolejności należy zaprojektować interfejs zdalnego obiektu. Jego definicję zapisuje się w specjalnym języku opisu interfejsu IDL (ang. Interface Definition Language). Interfejs ten jest następnie kompilowany do wybranego języka programowania. Program, który dokonuje tej kompilacji, nazywa się komilatorem IDL.

Wygenerowany na podstawie interfejsu kod objemuje przede wszystkim statycznego pośrednika IDL klienta (ang. static IDL stub) oraz szkielet (ang. skeleton) dla serwera. Zadaniem pośrednika IDL i szkieletu jest przetwarzanie komunikatów protokołu sieciowego na wywołania metod obiektu. Pośrednik IDL dodatkowo dostarcza aplikacji klienta pomocniczego obiektu (ang. proxy), którego interfejs jest taki sam, jak interfejs obiektu. Dzięki temu klient ma wrażenie, że pracuje bezpośrednio na zdalnym obiekcie, choć w rzeczywistości dokonuje wywołań na lokalnym obiekcie pomocniczym, który następnie przekazuje je za pośrednictwem ORB do zdalnego obiektu.

Najczęściej twórca obiektu generuje na podstawie definicji interfejsu również szablon implementacji obiektu, w którym następnie dopisuje kod metod obiektu.

Twórca obiektu oraz jego użytkownik muszą również dostarczyć programy: serwera, w którym obiekt zostanie utworzony, oraz klienta, który będzie wywoływał jego metody. Fragmenty kodu związane z utworzeniem obiektu CORBA są objęte standardem. Należy podkreślić, że programy klienta i serwera (czy obiektu) mogą zostać zaimplementowane w różnych językach programowania.

Ostatnim etapem przygotowania aplikacji CORBA jest kompilacja programów klienta i serwera do postaci wykonywalnej i ich uruchomienie.

Przykład prostej aplikacji

Niniejsze ćwiczenie ma na celu wprowadzenie platformy CORBA. Zostanie przedstawiona i omówiona prosta aplikacja, w której obiekt zdalny udostępnia jedną metodę, wypisującą na ekran maszyny serwera ciąg znaków przekazany jej przez klienta jako parametr. Programy klienta i serwera są zrealizowane w języku C++. Interfejs obiektu nazywa się writer i jest zapisany w pliku o nazwie printer.idl.

Interfejs obiektu

Interfejs obiektu writer jest zdefiniowany następująco (plik writer.idl):

interface writer
{
  void print (in string message);
};

Obiekt udostępnia poprzez swój interfejs jedną metodę o nazwie print. Przyjmuje ona wejściowy parametr typu string (jeden z predefiniowanych typów CORBA) o nazwie message; przekazuje go do obiektu klient, a zadaniem metody print będzie wyświetlenie jego wartości na ekranie maszyny serwera.

Kompilacja interfejsu

Kompilacji interfejsu IDL do wybranego języka programowania dokonuje się za pomocą tzw. kompilatora IDL. Dla każdego języka programowania wspieranego przez standard CORBA istnieje odrębny kompilator IDL (np. idl dla C++ czy jidl dla języka Java).

Kompilacja interfejsu writer.idl:

# idl –impl writer.idl

Opcja –impl oznacza żądanie dodatkowego wygenerowania szablonu implementacji obiektu (domyślnie nie jest on generowany). W wyniku kompilacji powstają następujące pliki:

  • writer.h i writer.cpp — gotowy kod pośrednika IDL dla klienta
  • writer_skel.h i writer_skel.cpp — gotowy kod szkieletu dla serwera
  • writer_impl.h i writer_impl.cpp — szablon implementacji obiektu, który zostanie uzupełniony kodem

Nazwy generowanych (przyrostki _impl oraz _skel) plików są objęte standardem, aby możliwe było przenoszenie kodu pomiędzy różnymi implementacjami standardu.

Kod implementacji obiektu

W pliku writer_impl.cpp należy dopisać kod wewnątrz wygenerowanego szablonu metody print (dla uproszczenia poniżej ukazano tylko ten fragment, w którym następują modyfikacje):

  1: //
  2: // IDL:writer/print:1.0
  3: //
  4: void
  5: writer_impl::print(const char* message)
  6:     throw(::CORBA::SystemException)
  7: {
  8:     // TODO: Implementation
  9:    
 10:    cout << message << endl;   // wypisanie ciągu znaków na ekran
 11: }   

Jak widać, w naszym prostym obiekcie implementacja metody sprowadza się do wypisania na ekran przekazanego w parametrze message ciągu znaków. Zastosowano tutaj operator strumieniowy "<<", zdefiniowany w pliku nagłówkowym iostream.h (należy ten plik włączyć do programu).

Program serwera

W programie serwera w pierwszej kolejności należy uruchomić pośrednika ORB. Następnie trzeba utworzyć zdalnie dostępny obiekt i wskazać adapter, który ma zarządzać tym obiektem. Po utworzeniu obiektu można przyjmować i obsługiwać wywołania jego metod. Oto kod przykładowego serwera:

  1: #include <OB/CORBA.h>
  2: #include <writer_impl.h>
  3: #include <fstream.h>
  4: #include <iostream.h>
  5: 
  6: int main(int argc, char* argv[])
  7: {
  8:    int status = EXIT_SUCCESS;
  9:    CORBA::ORB_var orb;
 10:    try
 11:    {
 12:       // inicjalizacja ORBa
 13:       orb = CORBA::ORB_init(argc, argv);
 14: 
 15:       // utworzenie zarządcy adapterów POA
 16:       CORBA::Object_var poaObj = orb -> resolve_initial_references("RootPOA");
 17:       PortableServer::POA_var rootPoa = PortableServer::POA::_narrow(poaObj);
 18: 
 19:      // utworzenie obiektu CORBA
 20:       writer_impl* obj = new writer_impl (rootPoa);
 21:       writer_var writerObj = obj -> _this();
 22:   
 23:       // zapisanie odniesienia do obiektu w pliku
 24:       ofstream out("IOR.ref");
 25:       out << orb -> object_to_string(writerObj) << endl;
 26:       out.close();
 27:   
 28:       // aktywacja adaptera i ORB
 29:       rootPoa -> the_POAManager() -> activate();
 30:       orb -> run();
 31:    }
 32:    catch (const CORBA::Exception&)
 33:    {
 34:       status = EXIT_FAILURE;
 35:    }
 36:    if (!CORBA::is_nil(orb))
 37:    {
 38:       try 
 39:       {
 40:          orb -> destroy();
 41:       }
 42:       catch (const CORBA::Exception&)
 43:       {
 44:          status = EXIT_FAILURE;
 45:       }
 46:    }
 47:    return status;
 48: } 

Powyższy program jest wprawdzie stosunkowo długi, jak na prostą aplikację, ale należy pamiętać, że jego kod jest w zasadzie standardowy — proces inicjalizacji obiektu przebiega zawsze tak samo, a zmianie podlega tylko nazwa jego klasy oraz nazwy zmiennych użytych w programie. Szablony kodu serwera i klienta są — jako standardowe — publicznie dostępne, na przykład w specyfikacjach CORBA. Poniżej omówiono najważniejsze fragmenty kodu serwera.

Przede wszystkim, zaimportowany został plik nagłówkowy writer_impl.h, zawierający definicję klasy implementującej zdalny obiekt oraz klasy realizującej szkielet. Na samym początku funkcji main() następuje inicjalizacja pośrednika ORB (operacja ORB_init()). Zaraz po tym pośrednik pobiera wskazanie do domyślnego adaptera obiektów (wywołanie resolve_initial_references("RootPOA"); adapter taki posiada domyślne polityki aktywacji). W kolejnej linii następuje zawężenie obiektu adaptera, będącego póki co ogólnym obiektem CORBA::Object_var, do typu adaptera PortableServer::POA_var.

Zdalny obiekt writer w serwerze to w rzeczywistiści obiekt klasy implementującej go, w tym przypadku klasy writer_impl. Tworząc go, jako parametr konstruktora należy przekazać adapter, który ma obsługiwać dany obiekt. W kolejnej linii utworzone zostaje odniesienie do obiektu CORBA (wywołanie _this()). W następnych trzech liniach następuje przekształcenie odniesienia do obiektu na postać ciągu znaków (wywołanie object_to_string pośrednika ORB) i zapisanie wyniku w pliku IOR.ref. Plik ten należy następnie w jakiś sposób udostępnić programowi klienta, by mógł on na podstawie zawartego w pliku odniesienia zlokalizować zdalny obiekt.

Uwaga — odniesienie w postaci ciągu znaków jest niezależne od języka programowania. Ma taką samą postać bez względu na to, w jakim języku napisano tworzący je serwer.

Program klienta

Podobnie jak dla serwera, również po stronie klienta istnieje konieczność uruchomienia pośrednika ORB. Posłuży on następnie klientowi do odnalezienia zdalnego obiektu i wywoływania jego metod. I znów, w zasadzie cały kod klienta (z wyjątkiem fragmentów związanych z wywoływaniem zdalnego obiektu) jest standardowy. Oto program klienta:

  1: #include <OB/CORBA.h>
  2: #include <writer.h>
  3: #include <fstream.h>
  4: #include <iostream.h>
  5: #include <unistd.h>
  6: 
  7: int main(int argc, char* argv[])
  8: {
  9:    int status = EXIT_SUCCESS;
 10:    CORBA::ORB_var orb;
 11:    try
 12:    {
 13:       // iniclalizacja ORBa
 14:       orb = CORBA::ORB_init(argc, argv);
 15:       // utworzenie referencji do obiektu CORBA
 16:       CORBA::Object_var obj = orb -> string_to_object("relfile:/IOR.ref");
 17:       writer_var writerObj = writer::_narrow(obj);
 18:       writerObj->print("Hello, server !!!");
 19:    }
 20:    catch (const CORBA::Exception&)
 21:    {
 22:       status = EXIT_FAILURE;
 23:    }
 24:    if (!CORBA::is_nil(orb))
 25:    {
 26:       try
 27:       {
 28:          orb -> destroy();
 29:       }
 30:       catch (const CORBA::Exception&)
 31:       {
 32:          status = EXIT_FAILURE;
 33:       }
 34:    }
 35:    return status;
 36: }

Po uruchomieniu pośrednika ORB klienta następuje utworzenie odniesienia do obiektu pomocniczego (ang. proxy), na którym klient będzie bezpośrednio wywoływał operacje zdalnego obiektu. Odniesienie to jest pozyskiwane z pliku (za pomocą operacji string_to_object), który wcześniej został udostępniony twórcy programu klienta. W dalszej kolejności następuje zawężenie obiektu do typu writer, a po nim — wywołanie metody.

Kompilacja i uruchomienie programów

Dla poniższej części ćwiczenia zakłada się wykorzystanie implementacji standardu CORBA firmy IONA Technologies, znanej pod nazwą ORBacus, oraz systemu operacyjnego Linux. Pliki nagłówkowe zapisane są domyślnie w katalogu /usr/local/include, a skompilowane biblioteki - w katalogu /usr/local/lib. W przypadku bibliotek dynamicznych należy pamiętać o ustawieniu w systemie Linux zmiennej systemowej LD_LIBRARY_PATH tak, by wskazywała katalog zawierając skompilowane biblioteki (tutaj /usr/local/lib). Potrzebne są cztery biblioteki: dwie systemowe, libdl i libpthread, oraz dwie dla wywołań CORBA, libOB (implementacja pośrednika ORB) i libJTC (emulacja wątków języka Java w C++). Poniżej przedstawione zostaną kolejno wszystkie polecenia kompilacji, można jednak skrócić proces kompilacji przez zapisanie tych poleceń w skrypcie lub przez użycie narzędzia make.

# gcc -c -I. -I/usr/local/include writer.cpp
# gcc -c -I. -I/usr/local/include writer_skel.cpp
# gcc -c -I. -I/usr/local/include writer_impl.cpp
# gcc -c -I. -I/usr/local/include client.cpp
# gcc -c -I. -I/usr/local/include server.cpp
# gcc -o client client.o writer.o -L/usr/local/lib -lOB -ldl -lJTC -lpthread
# gcc -o server server.o writer.o writer_skel.o writer_impl.o 
      -L/usr/local/lib -lOB -ldl -lJTC -lpthread

Uruchomienie programów serwera i klienta:

# ./server
# ./client

Wynik w programie serwera:

Hello, server !!!

Zadania

  1. Zaimplementować i uruchomić aplikację writer. Do wykonania ćwiczenia pomocny może się okazać przedstawiony powyżej kod.
  2. Zapoznać się z użyciem typu any, uogólniającego wszystkie (również obiektowe) typy CORBA. Użyć w wywołaniu metody print argumentu tego typu zamiast typu string, a następnie ponownie zaimplementować tak zmodyfikowany obiekt.
  3. Zaimplementować i uruchomić zmodyfikowaną aplikację writer, w której strony klienta i serwera zostaną zrealizowane w różnych językach programowania, C++ i Java (w dowolnej kombinacji). W przypadku języka Java pomocny będzie podręcznik [ION01].