Sr-13-lab-1.0
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
iwriter.cpp
— gotowy kod pośrednika IDL dla klientawriter_skel.h
iwriter_skel.cpp
— gotowy kod szkieletu dla serwerawriter_impl.h
iwriter_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
- Zaimplementować i uruchomić aplikację
writer
. Do wykonania ćwiczenia pomocny może się okazać przedstawiony powyżej kod. - 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 typustring
, a następnie ponownie zaimplementować tak zmodyfikowany obiekt. - 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].