Rozdział 20. CORBA
CORBA jest skrótem nazwy Common Object Request Broker Architecture (co oznacza ogólną architekturę pośrednika wywoływania obiektów). Jest to unormowany schemat budowy rozproszonych aplikacji obiektowych, którego elementy mają możliwość komunikowania się ze sobą niezależnie od platformy i użytego języka programowania.
Ogólny schemat architektury systemu wykorzystującego CORBA podany jest na poniższym rysunku. Klient i serwer aplikacji są programami, które można napisać samemu:
|
W architekturze CORBA wyróżnia się elementy (ang. components) i warstwy (ang. layers).
Język definicji interfejsu (IDL)
Jest to język używany w deklaracjach interfejsów obiektów. Bardzo mocno przypomina on deklaracje klas w języku C++.
IDL (Interface Definition Language) jest stosowany do deklarowania struktur danych i metod wykorzystywanych przez CORBA w aplikacjach. Dla danego zestawienia ORB (patrz niżej) i języka programowania IDL jest zwykle kompilowany do postaci „zrębów” (interfejsy statyczne) i „szkieletów” (interfejsy dynamiczne). Zręby (ang. stubs) można następnie dołączać do klienta, szkielety (ang. skeletons) do serwera, zaś kod generowany przez kompilator IDL jest wykorzystywany do zarządzania komunikacją między kodem użytym w kliencie lub serwerze, a pośrednikiem wywoływania obiektów (ORB). IDL przekazuje do ORB informację o rodzaju używanego systemu, wskazując rodzaj wymienianych danych oraz rodzaje wywołań wymaganych przez te dane.
Pośrednik wywołań obiektów (ORB)
Jest to warstwa podstawowa, wykorzystywana przez wszystkie pozostałe.
ORB (Object Request Broker) cechuje się właściwością zdalnego wywoływania. Na swoim najbardziej elementarnym poziomie pośredniczy między klientami żądającymi jakichś działań oraz przekazuje parametry między klientem i serwerem. Może on kierować żądania do innego pośrednika ORB, jeżeli on właśnie zarządza żądaną usługą.
Najważniejszą cechą zapewniającą takie działanie jest możliwość komunikacji między pośrednikami ORB niezależnie od ich implementacji. CORBA definiuje ogólne zasady tej komunikacji w postaci protokołu komunikacyjnego (oznaczanego skrótem GIOP od słów General Inter-ORB Protocol). GIOP określa wspólną reprezentację danych (CDR, czyli Common Data Representation), wspólne formaty komunikatów i wspólne założenia odnośnie do dowolnej sieciowej warstwy transportowej, która może być użyta do przekazywania komunikatów GIOP. Nazwy, które tutaj wymieniliśmy, stanowią zaledwie niewielki ułamek terminologii i skrótów, z którymi powinni zapoznać się zainteresowani czytelnicy!
Obecnie, przy względnej dominacji sieci wykorzystujących TCP/IP, prawie cała komunikacja między pośrednikami ORB i obsługiwanymi przez nie procesami korzysta z TCP/IP. Dlatego właśnie dodano specyfikację transportową do GIOP, definiując internetowy protokół komunikacji między pośrednikami ORB. Jest on oznaczamy skrótem IIOP pochodzącym od nazwy Internet Inter-ORB Protocol. Same protokoły w architekturze CORBA są zdefiniowane za pomocą IDL i właśnie dzięki temu uzyskano duże możliwości współdziałania różnych implementacji ORB. Jeżeli np. aplikacja ORBit (czyli aplikacja ORB należąca do GNOME) może mieć dostęp do obiektu ORBit za pomocą protokołu IIOP, to staje się wielce prawdopodobne, że może ona mieć dostęp także do obiektów zdefiniowanych za pomocą innych pośredników ORB, np. omniORB, niezależnie od tego, czy są one umieszczone na tym samym serwerze, czy na innym.
Protokół IIOP został znormalizowany, a więc można się spodziewać, że program-klient napisany dla pośrednika ORBit będzie pomyślnie korzystał z usług zdefiniowanych za pomocą omniORB. Nie wymaga to od programisty zbytniego wysiłku, a więc jeżeli została zdefiniowana odpowiednia specyfikacja takiego obiektu (IOR, patrz niżej), to odwołanie może być skierowane do oddalonego pośrednika ORB i tam przetworzone.
Współdziałające odwołanie do obiektu (IOR)
Żądanie wprowadzenia metody obiektu musi zawierać tzw. współdziałające odwołanie do obiektu (oznaczane skrótem IOR od nazwy Interoperable Object Reference). Jest to odwołanie do obiektu zdefiniowanego w CORBA mające postać napisu. Zawiera ono informację o użytym rodzaju IDL, nazwę węzła przechowującego obiekt, porządek bajtów (ang. endianness) dla tego węzła oraz w systemach TCP/IP — dane gniazda sieciowego, które może być wykorzystane do dostarczenia żądania do pośrednika ORB działającego w tym węźle.
Czasami warto podczas uruchamiania aplikacji podjąć próbę zdekodowania części IOR, ale w zasadzie powinno ono być traktowane jako przezroczyste, czyli jako coś, co powinien zinterpretować pośrednik ORB.
Zainicjowanie systemu CORBA zazwyczaj wymaga rozesłania jakichś IOR w celu poinformowania klientów o sposobie dostępu do odpowiednich serwerów. Jest to najbardziej dokuczliwy proces podczas uruchamiania aplikacji w architekturze CORBA.
Adapter obiektu
Pomiędzy pośrednikiem ORB a implementacją obiektu występuje tzw. adapter obiektu (ang. Object Adaptor). Zawiera on funkcje wspomagające tworzenie, identyfikację, uaktywnianie i na końcu zwalnianie obiektu.
Najpierw w architekturze CORBA zdefiniowany został podstawowy adapter obiektu (BOA, od słów Basic Object Adpator), ale jego specyfikacja nie była dokładnie określona. Różne pośredniki korzystały z całkowicie odmiennych wywołań, co wyjątkowo silnie ograniczało możliwość współdziałania aplikacji. Następnie wprowadzono przenośny adapter obiektu (POA, czyli Portable Object Adaptor), który umożliwiał korzystanie z wielu cykli pracy w znacznie bardziej wygodny sposób.
Oznacza to, że gdy jakiś pośrednik odwołań do obiektów okaże się nieprzydatny i jeżeli dostępne jest odwzorowanie użytego języka, to wprowadzając niewielkie zmiany w kodzie programu, można będzie przystosować go do innego pośrednika.
Serwery
Serwery zapewniają korzystanie z obiektów, zezwalając klientom na wywołania metod tych obiektów. Usługi CORBA zostały określone za pomocą standardowych definicji języka IDL, dzięki czemu znane są sposoby wykonywania ogólnodostępnych czynności. Dzięki usługom nazewniczym można budować rejestr dostępnych obiektów, a więc aplikacje w architekturze CORBA mogą wyszukiwać wymagane usługi. Obsługa zdarzeń zapewnia umieszczanie zdarzeń w kolejkach, dzięki czemu aplikacje korzystające ze zdarzeń przy wywołaniach w architekturze CORBA nie muszą szczegółowo analizować każdej usługi od początku.
Klienty nie muszą znać bezpośrednio danych identyfikacyjnych serwera, z którego usług chcą skorzystać. Komponenty serwera mogą być przechowywane na różnych maszynach rozrzuconych po całej sieci i klient nie musi znać ich dokładnej lokalizacji. Przeważnie wysyłają one żądania wymagające wykonania pewnych operacji na obiekcie, zaś pośrednik ORB kieruje te żądania do właściwego serwera w odpowiedniej lokalizacji. W architekturze CORBA istnieje kilka usług, które pozwalają programom rejestrować ich własne usługi. Oznacza to, że aplikacja CORBA może wyszukiwać dostępne usługi podczas swojej pracy.
Zwróćmy uwagę na to, że CORBA nie jest „czystym” systemem typu klient-serwer, bowiem zezwala również na komunikowanie się węzłów między sobą. Od wielu usług CORBA wymaga się, aby serwery odwracały swoją rolę, stając się klientami żądającymi usług od innych serwerów.
Jako przykład można podać aplikację obsługującą wypożyczalnię płyt DVD. Serwer tej aplikacji przetwarzający zapytania może być traktowany jako typowy serwer, ale zmienia się on w klienta podczas żądania danych od serwera bazy danych lub żądania potwierdzeń autentyczności od serwera zabezpieczeń.
Usługi nazewnicze i usługi wymiany
Wiele aplikacji używających usług nazewniczych i transakcyjnych (ang. Naming and Trading Services) w architekturze CORBA może równocześnie korzystać z tego samego „rejestru”, to zaś umożliwia zastosowanie nawet specyficznych programów pomocniczych do zarządzania systemem śledzenia informacji o wszystkich zarejestrowanych obiektach. Bez unormowanych zasad działania, takich jak usługi nazewnicze, nie można byłoby utworzyć aplikacji wykonującej takie zadanie. Usługi nazewnicze umożliwiają także tworzenie powiązań z usługami katalogowymi, np. z LDAP.
Nie wspomniano tutaj Nigdzie o użyciu UDP zamiast TCP, o nazwach węzłów sieci lub o stosowaniu określonego języka programowania przy tworzeniu klientów lub serwerów. Podczas korzystania z RPC lub gniazd sieciowych aplikacja musi zwracać uwagę na stany węzłów sieci i sposoby uzyskiwania połączeń z serwerami. W architekturze CORBA zagadnienia te są traktowane jako szczegółowy opis środowiska, który rozwiązuje dana implementacja. Zarządza tym wszystkim pośrednik ORB, przeważnie nie ingerując w aplikację. Gniazda sieciowe zapewniają podstawowy format powiązań z określonymi standardowymi protokołami. Dzięki temu aplikacja musi się troszczyć tylko o protokół oraz o format danych, które mają być przekazywane. W architekturze CORBA ten format opisuje IDL, zaś protokółem zajmuje się pośrednik ORB — a więc została tu wyeliminowana konieczność analizy surowych danych. Ścisłe określanie danych za pomocą IDL umożliwia przekaz tych danych o odpowiedniej strukturze z jednego systemu do innego, niezależnie od używanego systemu operacyjnego, protokołu sieciowego, języka programowania oraz pośrednika ORB.
Ogólna ocena architektury CORBA
Zastosowanie architektury CORBA jest początkowo bardziej kosztowne w porównaniu z bezpośrednim nakładaniem na siebie elementów tworzonego systemu. Zyski widać dopiero przy długoterminowym korzystaniu z tej architektury. Użycie CORBA do wydzielenia klienta i serwera z aplikacji w przypadku naszego programu obsługującego wypożyczalnię płyt DVD nie przyniesie znacznych ulepszeń w porównaniu z zastosowaniem gniazd sieciowych lub RPC. Uzyskujemy tylko to, że łatwiejsze stanie się zarządzanie następnymi modyfikacjami, które mogą obejmować:
powiązanie aplikacji obsługującej wypożyczalnię z systemem księgującym,
podłączenie czytnika kodu kreskowego umieszczonego w pojemniku na zwroty (zwrot płyty jest obsługiwany w momencie, gdy klient wsunie płytę do pojemnika odbiorczego, przesuwając ją nad czytnikiem kodu),
przenoszenie danych do bazy PostgreSQL bez konieczności angażowania interfejsu klienta, jeżeli nie ma takiego powodu,
zastosowanie języka Java w programie serwera,
rozdzielenie aplikacji w taki sposób, aby baza danych obsługująca klientów wypożyczalni była zarządzana z jednego komputera, zaś tytuły płyt były obsługiwane z innego, oraz aby dostęp do pozostałych elementów aplikacji był możliwy z różnych komputerów.
Wszystkie te zmiany środowiskowe można wprowadzić bez konieczności modyfikacji interfejsu klienta. Jeżeli zdarzy się, że zostaną wprowadzone nowe interfejsy, to jedyną zmianą będzie dołączenie nowego oprogramowania do istniejących serwerów wypożyczalni DVD. Nie zaburzy to w najmniejszym stopniu już istniejących programów-klientów.
Jeżeli zajdą zmiany funkcjonalne w serwerze, to prawdopodobnie trzeba będzie odnowić połączenia klientów w architekturze CORBA. Jest to wymagane do uzyskania informacji o nowych lokalizacjach usług obiektowych. Przy założeniu, że interfejs się nie zmieni, uzyskanie dostępu przez klienta do nowego serwera może polegać tylko na jego wylogowaniu i powtórnym zalogowaniu.
Zupełnie niezauważalnie można dodawać następne usługi. Usługi związane z bezpieczeństwem w architekturze CORBA mogą obejmować np. weryfikację tożsamości przeprowadzaną dzięki specjalnemu wspomaganiu ze strony ORB. Mogłoby to zmniejszyć wymagania odnośnie do zaufania między klientami a serwerami, a alternatywą byłoby dołączanie kodu weryfikującego do każdego z nich. Taka sytuacja dotyczy również niektórych innych usług CORBA: ich działanie można ulepszyć, przenosząc niektóre ich cechy funkcjonalne do pośrednika ORB.
Oddzielenie definicji IDL od języka programowania użytego w klientach i serwerach staje się szczególnie przydatne wtedy, gdy zespoły programistów zaczynają pracować w rozproszeniu. Najważniejsze jest wówczas utrzymanie dokładnie i jednoznacznie zdefiniowanych interfejsów, szczególnie zaś wtedy, gdy system ma mieć jakąś postać otwartego standardu, a programiści są zatrudnieni w różnych organizacjach i nawet w różnych krajach.
Interfejs musi być bardzo dokładnie zdefiniowany, ale przy implementacji usługi mamy pozostawioną możliwość wyboru. Ważne jest więc to, że zarówno środowisko, jak i implementacja mogą się zmieniać bez konieczności modyfikacji interfejsów.
W odróżnieniu od wielu rozwiązań alternatywnych, CORBA pozwala na łączenie wszelkich systemów operacyjnych i wszelkich rodzajów języków programowania. Konsorcjum o nazwie Object Management Group (OMG) zarządzające standardami CORBA uzgodniło znormalizowane sposoby odwzorowania konstrukcji IDL w kilku językach programowania: Ada, C, C++, COBOL, Common Lisp, Java i Smalltalk. Inne organizacje wprowadziły mniej sformalizowane zapisy dotyczące odwzorowania IDL w innych językach, np. Perl, Python i TCL.
Niektóre języki, jak np. C, nie obsługują bezpośrednio abstrakcji obiektowych, a więc obiekty muszą w nich być symulowane. Prowadzi to do nieco dziwnie wyglądającego kodu, ale jednocześnie możliwe jest uzyskanie wyników niedostępnych w inny sposób.
CORBA i RPC
CORBA najbardziej przypomina RPC (patrz rozdział 18), ponieważ umożliwia zdalne wywoływanie procedur przez klienty. Najważniejszą różnicą jest tu sposób lokalizacji serwerów: w systemach korzystających z CORBA nie są one wybierane na podstawie informacji o węźle i gnieździe utrzymywanej przez klienta, lecz na podstawie tzw. „odwołań obiektowych” używanych przy kierowaniu żądań przez ORB. Dzięki temu to ORB zarządza wymianą informacji między klientami a serwerami, a nie same klienty.
Podstawowym problemem w alternatywach, takich jak RPC i gniazda sieciowe, nie jest brak skomplikowanych abstrakcji, ale raczej to, że w miarę wzrostu stopnia złożoności aplikacji korzystających z takich mechanizmów coraz trudniejsze staje się zarządzanie nimi. Widać to szczególnie wyraźnie, gdy aplikacje muszą korzystać z dodatkowych usług, rejestrów i pośredników. Z drugiej strony, CORBA zawiera wzorzec dodawania nowych usług, zaś konsorcjum OMG unormowało wiele użytecznych dodatkowych abstrakcji obiektów oraz pewne właściwości tej architektury wspomagające aplikacje. Zagadnienia te zostaną omówione bardziej szczegółowo w następnych częściach książki.
CORBA rozwiązuje kilka znaczących ograniczeń spotykanych w RPC:
RPC wymaga, aby podczas kompilacji funkcje API były deklarowane statycznie.
Zarówno RPC, jak i CORBA zawierają języki definicji interfejsu, określające dozwolone operacje, czyli tzw. „protokół”. CORBA obsługuje dodatkowo tzw. interfejs wezwań dynamicznych (Dynamic Invocation Interface). Mogą z niego korzystać programy, sprawdzając podczas pracy w repozytorium interfejsów dostępność metod, które mogą być następnie użyte w wywołaniach. Stosując RPC, nie uzyskuje się żadnej możliwości takiego działania.
RPC umożliwia tylko synchroniczną wymianę komunikatów (ang. synchronous messaging).
W architekturze CORBA komunikacja między klientem a pośrednikiem (ORB), podobnie jak komunikacja między pośrednikiem a serwerem, odbywa się synchronicznie — w tradycyjny sposób. Można tu jednak bardzo prosto dołączyć elementy pośredniczące umożliwiające korzystanie z innych modeli wymiany komunikatów. Połączenia z pośrednikami mogą więc pozostać synchroniczne, zaś serwer CORBA przyjmujący i przechowujący żądania może symulować komunikację asynchroniczną w połączeniach z serwerem faktycznie obsługującym dane żądania.
Istnieje nowy sposób obsługi wykorzystujący tzw. asynchroniczną metodę wezwań (oznaczaną skrótem AMI od Asynchronous Method Invocation), który umożliwia prawdziwie asynchroniczny sposób wymiany komunikatów. Zastosowano go w kilku pośrednikach ORB.
RPC obsługuje tylko procedury, zaś współczesne systemy coraz częściej zaczynają korzystać z obiektów.
RPC bardzo mocno wiąże się z protokołami TCP/IP.
W architekturze CORBA założono wprawdzie charakterystykę wymiany informacji podobną do stosowanej przez TCP/IP, ale świadomie rozważa się użycie alternatywnych systemów transportu informacji, takich jak IPX, ATM, XNS firmy Xerox, SNA firmy IBM, DECnet, a także współużytkowanej pamięci lokalnej ze względu na jej olbrzymią szybkość działania.
W szczególności użycie współużytkowanej pamięci lokalnej może ogromnie powiększyć wydajność i jest nieomal obowiązkowe, jeżeli rozważa się zastosowanie architektury CORBA w obsłudze abstrakcji graficznych na najniższym poziomie — jak to ma miejsce w sterowaniu systemami okienkowymi (projekt Berlin, http://www.berlin-consortium.org/) — albo w systemach okienkowych wykorzystujących architekturę CORBA jako warstwę komunikacyjną między API a kontrolerem grafiki.
Mechanizmy te charakteryzują się całkowicie odmienną semantyką, a w architekturze CORBA świadomie starano się uniknąć ścisłego powiązania z jakimkolwiek mechanizmem. Obecnie „wszyscy” posługują się TCP/IP, co było nie do pomyślenia jeszcze dziesięć lat temu, ale można sobie wyobrazić, że w roku 2010. wszyscy będą korzystać z ATM w swoich osobistych łączach gigabitowych. CORBA może sprostać tym wymaganiom bez problemów. Niewielka liczba osób zajmujących się rozwojem RPC nie zdoła zapewne wprowadzić modyfikacji nadążających za takimi zmianami.
Wiele wysiłku projektanci i programiści poświęcają obecnie architekturze CORBA, zaś RPC praktycznie nie jest już rozwijane, ponieważ firmy sponsorujące te prace zajęły się właśnie architekturą CORBA. Jeżeli ktoś próbuje rozwijać RPC firmy ONC, to może okazać się jedynym użytkownikiem wykorzystującym efekty swojej pracy. Istniał wprawdzie projekt bezpłatnego udostępnienia implementacji sponsorowanej przez DCE, ale wydaje się, że wyszedł on już z mody.
CORBA i gniazda sieciowe
W podobny sposób, jak w poprzednim podrozdziale, porównamy teraz architekturę CORBA z gniazdami sieciowymi (ang. sockets):
Użycie IDL do dokładnego definiowania typów danych umożliwia przekaz danych o odpowiedniej strukturze z jednego systemu do innego, niezależnie od używanego systemu operacyjnego, protokołu sieciowego, języka programowania oraz pośrednika ORB.
Gniazda sieciowe obsługują „surowy” format danych powiązany z pewnymi standardowymi protokołami. Ich zastosowanie wymaga, aby aplikacja troszczyła się zarówno o protokół, jak i o format danych, które mają być przesłane. IDL w architekturze CORBA określa format, zaś sprawą protokołu zajmuje się ORB, co eliminuje konieczność przetwarzania surowych danych w aplikacji.
Gniazda sieciowe nie pozwalają na użycie żadnej z ogólnie przyjętych metod komunikacji asynchronicznej lub komunikacji z zastosowaniem pośredników.
Pośredniki HTTP są na pewno powszechnie stosowane, ale dotyczą one tylko bardzo specjalizowanego protokołu wykorzystującego gniazda, a nie wszystkich takich protokołów.
Systemy podobne do CORBA
Oprócz RPC, który omówiliśmy w rozdziale 18., istnieje jeszcze kilka systemów podobnych do architektury CORBA i zawierających narzędzia wspomagające tworzenie aplikacji rozproszonych.
DCOM lub COM+
Ten system jest rozpowszechniany przez Microsoft i najbardziej przypomina architekturę CORBA. Pierwotnie DCOM był rozprowadzany jako wersja COM, zaś ostatnio połączenie COM i DCOM zostało nazwane DCOM+. Dostępna jest także jego wersja dla Linuksa — ma ona nazwę EntireX, a sposób jej instalacji i konfiguracji został opisany w książce wydawnictwa Wrox Press pt. Professional Linux Deployment (ISBN 1861002874).
Podobnie jak w architekturze CORBA, w systemie DCOM występuje IDL prawie niezależny od użytego języka programowania, który służy do opisu interfejsów. Jednak w odróżnieniu od CORBA, IDL w wydaniu firmy Microsoft zawiera zazwyczaj bardzo szczegółowe informacje o konfiguracji systemu, łącznie z tymi, które normalnie są zarządzane za pomocą rejestru Windows.
Firma Microsoft dołączyła także wielką liczbę usług, a w tym:
MTS dla obsługi transakcji,
MSMQ dla obsługi komunikacji asynchronicznej,
DAO i ADO wykorzystywane w dostępie do danych.
Powyższe usługi działają równolegle z usługami występującymi w architekturze CORBA.
Ponieważ COM i DCOM są równocześnie produktem wytwórcy systemów, to wyposażono je w możliwość korzystania ze wszelkich właściwości Win32, takich jak np. rejestr systemowy lub jednorodna implementacja dyspozytorów i usług. W odróżnieniu od tego, w architekturze CORBA nie zakłada się jakiejkolwiek dostępności systemu plików, ponieważ elementy systemu muszą działać nawet w komputerach wbudowanych w urządzenia, wyposażonych w niewiele więcej niż jakiś czujnik i urządzenie komunikacyjne.
Niemożność przyjęcia założenia o dostępności rejestrów, systemów baz danych itp. powoduje, że uruchamianie aplikacji CORBA jest skomplikowane.
Z drugiej strony, firmy używające różnorodnego sprzętu komputerowego muszą w zasadzie odrzucić stosowanie COM/DCOM — albo z powodu posiadania systemów, na których DCOM nie działa, albo z powodu konieczności wprowadzenia bardzo dużych zmian w elementach używanych systemów.
Jeżeli trzeba połączyć obiekty CORBA i DCOM w jednym systemie, to można skorzystać z pewnych dostępnych „pomostów” umożliwiających komunikowanie się w obydwie strony. Konsorcjum OMG prowadzi intensywne prace nad standardami regulującymi takie współdziałanie.
Metoda zdalnych wezwań (RMI) w języku Java
Jest to rozproszony system obiektowy przeznaczony dla środowiska Java firmy Sun. Nie zawiera on tak wymyślnych i złożonych usług jak usługi CORBA. Jedynym wymaganiem jest to, aby system ten współdziałał z językiem Java i to jest powodem jego znacznego uproszczenia w porównaniu z architekturą CORBA. Zarówno CORBA, jak i RMI (Remote Method Invocation) są niezależne od sprzętu, ale, niestety, RMI działa tylko z programami napisanymi w języku Java.
Ostatnio trwają prace nad zastosowaniem w RMI tego samego protokołu sieciowego, którym posługuje się CORBA w komunikacji między pośrednikami, czyli IIOP. Pokazuje to pewną zbieżność obydwóch systemów. Powiększenie obszaru zastosowań IIOP może prowadzić do pewnego zbliżenia tych systemów, ale niekoniecznie oznacza to poprawę ich współdziałania.
Enterprise JavaBeans
Enterprise JavaBeans (EJB) posługuje się podobnym schematem działania, jak CORBA i DCOM, zapewniając unormowany i bardzo wyszukany sposób wzywania rozproszonych elementów, który stwarza podbudowę dla szybkiego przetwarzania trwale przechowywanych danych.
JavaBeans, podobnie jak Java RMI, ogranicza się tylko do języka Java i doświadcza wszystkich problemów z wydajnością, na które okazały się podatne systemy wykorzystujące ten język. Jeżeli kompilatory języka Java zostaną ulepszone, to wydajność przestanie być najważniejszym problemem.
Szkielet dla elementów CORBA, które mogą współpracować z Enterprise JavaBeans, znajduje się w fazie opracowywania i podobnie jak w wypadku Java RMI, obydwa systemy zbliżają się do siebie. Oprócz tego istnieje kilka wdrożeń EJB, takich jak np. ogólnie dostępny system Enhydra, który udowadnia, że zastosowanie EJB daje praktyczne korzyści. Może to kontrastować z wcześniejszymi planami dotyczącymi zapewnienia funkcjonalności „złożonego dokumentu” za pomocą OpenDOC w architekturze CORBA, które bez względu na swoją jakość zostały zarzucone przez firmy Apple i IBM.
MQSeries firmy IBM
Jest to system asynchronicznej wymiany komunikatów, który ma wielu naśladowców, między innymi w postaci MSMQ firmy Microsoft lub MQ firmy Falcon. Wykorzystano w nim model całkowicie odwracający założenia architektury CORBA dotyczące połączeń synchronicznych.
Wśród podstaw architektury CORBA znajduje się synchroniczna wymiana komunikatów, w czasie której klient zestawia synchroniczny „kanał komunikacyjny” z serwerem, korzystając z serwerów pośredniczących tylko wtedy, gdy wymagana jest komunikacja asynchroniczna. W systemach MQ (Message Queuing) sytuacja jest dokładnie odwrotna: domyślnie komunikacja odbywa się w sposób asynchroniczny, zaś zestawienie połączenia synchronicznego wymaga specjalnych działań ze strony programisty.
Obydwa sposoby są użyteczne; usługa wymiany komunikatów w systemie CORBA umożliwia integrację tej architektury ze wspomnianymi systemami. Dobrze oceniane systemy operacyjne QNX i BeOS intensywnie korzystają z kolejkowania komunikatów przy zarządzaniu komunikacją między procesami (a czasem także wewnątrz wątków). Abstrakcja tego rodzaju jest bardzo przydatna w systemach korzystających ze zdarzeń.
SOAP
Microsoft promuje tzw. prosty protokół dostępu do obiektów (SOAP, od słów Simple Object Access Protocol). W tym modelu zdalne wywołania procedur odbywają się za pomocą protokołu HTML, a dane są przekazywane w formacie XML.
Jest prawdopodobne, że ostatecznie będzie to połączone z MSMQ, który zapewni bardziej pewny transport, a wiadomości pozostaną w formacie XML.
Jeżeli traktuje się XML jako „rozwlekły” format, to łatwo się przekonać, że zastosowanie CDR jest bardziej wydajne, rozważając zarówno ilość przekazywanych danych, jak i nakład pracy poświęcany na interpretację treści komunikatów. Sloganowa popularność XML na pewno pomoże w promocji SOAP, niezależnie od jego ograniczonej wydajności.
Ponieważ do tej pory nie spotkaliśmy się z dużymi projektami korzystającymi z SOAP, to jeszcze jest zbyt wcześnie, aby ostatecznie przesądzać o wartości tego protokołu. Kompresja danych oraz inne metody „dostrajania” protokołu mogą polepszyć jego wydajność.
SOAP znacznie różni się od rozwiązań stosowanych w architekturze CORBA w tym sensie, że komunikaty są przekazywane albo jako tekst, albo w postaci łatwo przekształcalnej na tekst. Oznacza to, że przekazywane dane można łatwo sprawdzać i przekształcać podczas transportu, co przydaje się przy uruchamianiu systemu i wykrywaniu błędów.
Podsumowując te rozważania, należy stwierdzić, że istnieje dość dużo systemów wspomagających tworzenie aplikacji rozproszonych. Korzystając z nich, można osiągnąć podobne efekty, a ponieważ wszystkie oferują użyteczne abstrakcje, to dlatego też trwają prace mające na celu stworzenie możliwości ich współpracy z architekturą CORBA.
W różnych systemach spotyka się obsługę różnych abstrakcyjnych obiektów programowania i warto poznać ich podstawy, niezależnie od tego, jaki szkielet zostanie ostatecznie wybrany. Nawet, gdy nie będzie się korzystać z jakiegoś specyficznego systemu, to można przejrzeć jego implementacje w celu znalezienia metod przydatnych w innych systemach.
W przypadku oprogramowania tworzonego w systemie Linux i współpracującego z GNOME jedynym sensownym rozwiązaniem jest CORBA. Aby to pokazać, zajmiemy się pośrednikiem o nazwie ORBit przeznaczonym dla GNOME.
IDL: definiowanie interfejsu
Interfejsy obiektów w architekturze CORBA są konstruowane za pomocą IDL. Język ten przypomina deklaracje klas C++ lub Java i pomimo że sam nie jest językiem programowania w ścisłym znaczeniu, jest używany do tworzenia takich deklaracji. Korzystając z IDL, nie można utworzyć kodu, który „coś robi”.
Moduły
Moduły (ang. modules) są w IDL strukturą najwyższego poziomu i wykorzystywane są do grupowania komunikujących się ze sobą interfejsów w jednej przestrzeni nazw. Czasami może to powodować konflikty. Weźmy na przykład fragment IDL zawierający dwa moduły o nazwach ECHOING i NOTECHOING. W podanym niżej wierszu moduł ECHOING udostępnia przestrzeń nazw dla swojego interfejsu o nazwie Echo:
module ECHOING {
IDL obsługuje tu definicje typów, wykorzystywane bardzo podobnie jak w C lub w C++. Typ input jest typem lokalnym dla modułu ECHOING:
typedef string input;
Interfejs Echo i zawarte w nim operacje są lokalne w odniesieniu do przestrzeni nazw ECHOING:
interface Echo {
void echo (in input str);
};
};
Moduł NOTECHOING udostępnia oddzielną przestrzeń nazw dla swojego interfejsu o tej samej nazwie jak poprzedni, czyli Echo:
module NOTECHOING {
interface Echo {
void noecho (in ECHOING::input str);
};
};
Zwróćmy uwagę na to, że używając typu input (lokalnego dla modułu ECHOING), musimy wskazać za pomocą prefiksu ECHOING::, że ma on być zaimportowany z przestrzeni nazw. Odbywa się to w podobny sposób, jak zarządzanie odwołaniami do obcych przestrzeni nazw w języku C++.
W powyższym przykładzie istnieją dwa interfejsy o nazwach Echo oraz jeden operator o nazwie echo. Interfejsy te będą traktowane jako różne tylko wtedy, gdy są dokładnie zdefiniowane w oddzielnych modułach.
Pułapki
Należy bardzo ostrożnie używać wielkich liter. W architekturze CORBA zakłada się, że używane języki programowania nie rozróżniają wielkości liter, dlatego nie ma ona znaczenia w identyfikatorach. Jeśli więc utworzymy dwie deklaracje o nazwach różniących się tylko wielkością liter, to kompilator IDL zasygnalizuje to jako błąd.
Podczas tworzenia kodu należy pamiętać o języku programowania, który będzie użyty do tworzenia kodu klientów usług CORBA. Jeżeli język ten interpretuje znaki w pewien szczególny sposób (np. znaki podkreślenia), to najlepszym wyjściem będzie rezygnacja z jego stosowania. W języku C, który nie ma wbudowanego naturalnego „systemu obiektowego”, nazwy obiektów podczas odwzorowania są budowane z innych nazw połączonych ze sobą znakiem podkreślenia. Na przykład operator noecho w języku C mógłby być nazwany NOTECHOING_Echo_noecho.
Interfejsy
Interfejsy są przeznaczone do grupowania wiążących się ze sobą struktur i operacji.
W naszej aplikacji do obsługi wypożyczalni płyt DVD w module DVD rozsądnie będzie umieścić następujący zestaw interfejsów:
module DVD {
interface MEMEBERSHIP { // Metody używane do obsługi klientów wypożyczalni
};
interface TITLING { // Metody używane do przetwarzania tytułów filmów
};
interface DISKS { // Metody używane do przetwarzania danych o płytach DVD
};
interface RENTAL { // Metody używane do obsługi wypożyczeń i zwrotów
};
interface RESERVATIONS { // Metody używane do rezerwacji tytułów
};
interface UTILITIES { // Metody dodatkowe, nie wiążące się z innymi
};
interface FACTORY { // Interfejs podstawowy zapewniający dostęp do innych
};
};
Każdy interfejs zapewnia odwołanie do obiektu. Istnieje możliwość sterowania sposobem tworzenia tych odwołań i przekazywania ich do klientów w aplikacji, która obsługuje wypożyczalnię dzięki interfejsowi podstawowemu o nazwie FACTORY. Zawiera on operacje zwracające odwołania do innych interfejsów. Interfejs FACTORY powinien być używany jako zabezpieczenie pomocnicze, zwracające odwołania tylko w odpowiedzi na żądania klienta, który uzyskał potwierdzenie autentyczności żądającego. Nie wolno zagnieżdżać ani interfejsów, ani modułów.
Podstawowe typy danych
W architekturze CORBA zdefiniowano zestaw podstawowych typów danych, które są elementarnymi składnikami danych przekazywanych między pośrednikami. Mają one format ogólnej reprezentacji danych (CDR, Common Data Representation). Wymieniono je w poniższej tabeli:
Rodzaj IDL |
Opis |
short |
16-bitowa liczba całkowita |
long |
32-bitowa liczba całkowita |
long long |
64-bitowa liczba całkowita |
unsigned short |
16-bitowa liczba całkowita bez znaku |
unsigned long |
32-bitowa liczba całkowita bez znaku |
unsigned long long |
64-bitowa liczba całkowita bez znaku |
float |
32-bitowa liczba zmiennoprzecinkowa w formacie IEEE |
double |
64-bitowa liczba zmiennoprzecinkowa w formacie IEEE |
char |
znak o kodzie 8-bitowym |
wchar |
znak o kodzie 16-bitowym |
boolean |
typ logiczny przybierający wartość TRUE lub FALSE |
octet |
przezroczysty typ znakowy niezmienny podczas przekazu |
any |
może reprezentować dowolny typ podstawowy lub utworzony |
Większość z nich jest bardzo podobna do typów występujących w językach C i C++. Warto wskazać nieco bardziej unikatowy typ octet oznaczający 8-bitowy typ znakowy. Może on być wykorzystany podczas transmisji danych binarnych, które podczas przekazu mają pozostać nie zmienione. Jeżeli np. przekazuje się dane tekstowe między systemem UNIX stosującym kod ASCII i komputerem AS/400 firmy IBM stosującym kod EBCDIC, a dane te są typu char, to warto użyć pośrednika ORB do automatycznego tłumaczenia danych z pierwotnej postaci na postać docelową. Dzięki temu można wyeliminować angażowanie klienta i serwera w te czynności. Jeżeli dane nie mogą się zmienić podczas transmisji, to należy użyć typu octet.
Typy szablonowe
Są to typy określające dane o zmiennym rozmiarze.
Typ sequence
Typ sequence oznacza wektor o zmiennej długości. Elementy tego wektora mogą być dowolnego typu. Rozmiar wielkości typu sequence może być nieograniczony i wówczas można go użyć do przekazania nieograniczonej liczby wartości typu string:
sequence<string> message;
Można także ograniczyć rozmiar, podając maksymalną liczbę elementów:
sequence<char, 80> line;
Typy string i wstring
Typ string, podobnie jak w języku C, oznacza wektor składający się ze znaków (typu char) o zmiennej długości, zakończony elementem NULL. Jego rozszerzeniem jest typ wstring, który oznacza wektor elementów typu wchar używanych w językach korzystających z wielobajtowego kodowania znaków.
Typy te są równoważne typowi sequence<char>, ale wyróżniono je dlatego, że w niektórych językach programowania istnieją funkcje przeznaczone specjalnie do ich wydajnego przetwarzania.
Typ fixed
Jest to nowy typ, wprowadzony w wersji 2.3 architektury CORBA, który dotychczas nie znalazł jeszcze uniwersalnego zastosowania. Jest on przeznaczony do przechowywania ułamków dziesiętnych zawierających do 31 cyfr znaczących o ustalonej pozycji kropki dziesiętnej. Takie samo znaczenie ma typ PIC używany w języku COBOL; można go także stosować do przetwarzania danych typu MONEY występujących czasami w SQL.
Typ fixed jest szczególnie przydatny w aplikacjach finansowych, gdy wymagane są ustalone sposoby zaokrąglania, traktowania nadmiaru i dokładność danych.
fixed<10,2> totalamt;
W językach COBOL i Ada występuje typ danych dziesiętnych o ustalonej długości, ale w innych językach wymagane jest odpowiednie odwzorowanie IDL zapewniające odpowiednią reprezentację danych.
W języku C ta reprezentacja lokalna nie jest zupełnie naturalna. Kompilator ORBit języka IDL generuje bowiem następujący kod:
typedef struct
{
CORBA_unsigned_short _digits;
CORBA_short_scale;
CORBA_char _value[6];
} CORBA_fixed_10_2;
Reprezentuje on spakowane dane w formacie 2 cyfry na bajt, co oznacza, że potrzebne są specjalne funkcje przetwarzające takie wartości. Nie można tutaj zastosować zwykłych operacji arytmetycznych występujących w języku C.
Zbudowane typy danych
Takie typy są zbudowane z typów podstawowych. Mogą one zawierać elementy, które same są także typami zbudowanymi.
Deklaracja typedef
Deklaracja typedef działa tak samo, jak słowo kluczowe typedef w języku C.
typedef long dvdidt;
typedef fixed<10,2> money;
W powyższych wierszach zadeklarowano typ zbudowany dvdidt służący do przechowywania 32-bitowych liczb całkowitych oraz typ money, który służy do przechowywania danych dziesięciocyfrowych z dwoma miejscami po przecinku.
Struktury
Struktury przypominają struktury występujące w języku C, lecz mają nieco odmienną składnię:
struct dvddisks {
dvdidt diskid;
TITLING::titlet titleid;
};
W powyższym przykładzie nazwą struktury jest dvddisks, zaś jej element o nazwie diskid jest typu dvdidt (tak jak zadeklarowano wcześniej), czyli jest 32-bitową liczbą całkowitą.
Element struktury titleid jest typu titlet, który jest zadeklarowany w przestrzeni nazw TITLING. Przykład ten pokazuje sposób użycia przestrzeni nazw w odwołaniu do obcego typu zadeklarowanego w innym interfejsie.
Typy wyliczeniowe
Typy te są podobne do typów wyliczeniowych używanych w językach C i C++.
enum movieratings { G, PG13, NC17, R, NR, XXX };
Zwróćmy jednak uwagę na to, że nie ma tu możliwości określenia wartości początkowej, tak jak w językach C i C++. Oznacza to, że między klientami a serwerami nie można bezpośrednio przekazywać wartości porządkowych typu wyliczeniowego. To ograniczenie wprowadzono celowo, ponieważ w niektórych językach programowania typy wyliczeniowe są obsługiwane bez korzystania z jakichkolwiek liczb. Jako przykład można podać odwzorowanie w Common Lisp, w którym typy wyliczeniowe są reprezentowane bezpośrednio jako symbole, czyli:
(setf ratings-list (:G :PG13 :NC17 :R :NR :XXX))
W przykładowej aplikacji nie będziemy korzystać z wyliczeń tego rodzaju, ponieważ mogłoby to przestawić aplikację na ustalony i niezmienny zestaw rodzajów filmów. Zamiast tego wartości będą przechowywane w tabeli, a więc można będzie je modyfikować w dowolnym momencie bez konieczności modyfikacji interfejsu.
Tablice
W skład tablicy wchodzą wektory o ustalonej liczbie elementów. Deklaracja jednowymiarowej tablicy ma postać:
typedef char line[80];
Deklaracja dwuwymiarowej tablicy ma postać:
typedef double bezier[3][3];
Unie
Unie w języku IDL różnią się od konstrukcji spotykanych w językach C i C++. W IDL muszą one być dyskryminowane, można w nich stosować etykiety case: wielokrotnie w tym samym elemencie oraz używać domyślnej alternatywy (default):
union PCode switch(Country) { /* Międzynarodowe kody pocztowe */
case USA: /* Na przykład 75063-2921 */
long main;
short sub;
case Canada: /* Na przykład K0A 2N0 */
char p1;
short p2;
char p3;
short p4;
char p5;
short p6;
default:
string pcode;
};
W powyższym przykładzie zadeklarowano reprezentację międzynarodowych kodów pocztowych, których format zależy od kraju podanego w polu Country. Określono tu specjalne formaty dla Kanady i USA, zaś dla pozostałych krajów używany jest po prostu pierwotny napis.
Operacje
Operacje reprezentują metody obiektów i wyglądają podobnie jak wywołania funkcji w językach C lub C++. Zawierają one:
Opcjonalny atrybut operacji określający semantykę przekazu danych — obsługiwany jest jedynie atrybut oneway oznaczający przetwarzanie asynchroniczne, przy którym wywołanie kończy się natychmiast bez zwracania jakiejkolwiek wartości. Ten atrybut jest już przestarzały w porównaniu z usługami wymiany komunikatów (Messaging Service) w architekturze CORBA, które zapewniają lepiej określone właściwości oceny jakości usługi,
Specyfikację typu określającą typ wartości zwracanej przez metodę. Może to być dowolny typ zadeklarowany w IDL lub typ void
Identyfikator określający nazwę operacji
Listę parametrów określających dane przekazywane do serwera lub zwracane przez serwer. Parametry te zawierają atrybut parametru określający kierunek przekazu. Dozwolonymi atrybutami parametrów są:
in |
Atrybut informujący, że parametr jest przekazywany od klienta do serwera. |
out |
Atrybut informujący, że parametr jest przekazywany od serwera do klienta. |
inout |
Atrybut informujący, że parametr jest przekazywany w obu kierunkach. |
Ten atrybut może być obsługiwany w taki sposób, że usuwane są wartości in i przydzielane nowe wartości out, zatem nie można zakładać, że dane pozostaną w tym samym miejscu,
Specyfikację typu parametru,
Nazwę parametru określającą jaki parametr ma być wywołany.
Poniżej podano kilka przykładów operacji. Oto najprostsza z nich:
void simplemethod ();
A oto metoda nadająca wartość:
void set(in storemembers recordtoupdate);
Oto metoda wyszukująca wartość memberid i zwracająca wartość recordcomplete:
void get(in memberidt memberid,
out storemembers recordcomplete)
raises (NOSUCHMEMBER);
Jeżeli operacja się nie uda, to serwer zasygnalizuje ten fakt, zwracając wyjątek NOSUCHMEMBER.
Metoda zmieniająca swoje dane wejściowe ma postać:
long modifyrecord(inout memrecordtype member)
raises (TOTALLYMESSEDUP, TOTALLYRADICALDUDE);
Zastosowano tutaj opcjonalne wyrażenie raises określające wyjątki zadeklarowane w aplikacji, które mogą być zwracane przez daną operację. Kilka pokazanych wyżej operatorów zawiera deklaracje wyrażeń obsługujących wyjątki, które zostaną omówione w następnym podrozdziale. Opcjonalne wyrażenie kontekstowe określa informację kontekstową. Bardzo mocno przypomina to sposób używany przy przekazywaniu zmiennych środowiskowych do procesu w systemie UNIX. Niestety, wartości kontekstowe nie zachowują typu, co stwarza problemy przy ich wyszukiwaniu, dlatego ich użycie nie jest polecane.
Wyjątki
Byłoby pięknie, gdyby interfejsy działały w prosty sposób, bez potrzeby interpretacji wyników. Niestety, nie wszystko przebiega w zamierzonej kolejności. Interfejsy mogą np. żądać od usług nazewniczych informacji o usłudze logowania, która nie została zarejestrowana. Może się także zdarzyć, że żądana jest informacja o kliencie, który nie istnieje, albo informacja, do przetwarzania której brak uprawnień. Aby obsłużyć takie sytuacje, w architekturze CORBA na poziomie systemowym zdefiniowano wyjątki wykorzystywane w komunikacji z pośrednikiem przy napotkaniu na błąd. Oto one:
UNKNOWN |
PERSIST_STORE |
BAD_PARAM |
BAD_INV_ORDER |
NO_MEMORY |
TRANSIENT |
IMP_LIMIT |
FREE_MEM |
COMM_FAILURE |
INV_IDENT |
INV_OBJREF |
INV_FLAG |
NO_PERMISSION |
INTF_REPOS |
INTERNAL |
BAD_CONTEXT |
MARSHAL |
OBJ_ADAPTER |
INITIALIZE |
DATA_CONVERSION |
NO_IMPLEMENT |
OBJECT_NOT_EXIST |
BAD_TYPECODE |
TRANSACTION_REQUIRED |
BAD_OPERATION |
TRANSACTION_ROLLEDBACK |
NO_RESOURCES |
INVALID_TRANSACTION |
NO_RESPONSE |
|
W IDL również istnieje możliwość deklaracji wyjątków, które stanowią albo logiczną reprezentację wskaźnika stanu, w którym wystąpił błąd:
exception NOSUCHMEMBER {};
albo mogą być rozwijane szerzej, informując o swojej istocie (w takim wypadku klient może zareagować w sposób bardziej odpowiedni dla danego problemu):
exception FIELDOVERFLOW {long maxsize, long offendingsize,
string shorttext, string longtext};
Standardowe wyjątki w architekturze CORBA mogą być podnoszone w dowolnym miejscu. Najczęściej zdarza się to w pośredniku ORB lub w szkieletach funkcji, gdy na poziomie ORB występują niepowodzenia podczas wzywania operacji. W odróżnieniu od tego, należy jawnie zadeklarować miejsce, w którym mają być użyte deklaracje wyjątków określone we własnym kodzie IDL, tak jak poniżej:
void updatesurname (in memberidt memberid, in string surname)
raises (FIELDOVERFLOW);
Atrybuty
Atrybuty pozwalają na zdeklarowanie — za pomocą jednej operacji — zmiennej stanu związanej z interfejsem, której wartości można zarówno nadawać (set), jak i pobierać (get). Dwa podane niżej interfejsy IDL są sobie równoważne:
interface SomeDumbInterface{
attribute int count;
};
interface SomeDumbInterface{
int _get_count;
void _set_count(in int c);
};
Można tu także użyć atrybutu readonly, który spowoduje pomijanie funkcji _set_count.
Przykład aplikacji obsługującej wypożyczalnię DVD
Poniżej podano przykładowy kod IDL opisujący API dla aplikacji obsługującej wypożyczalnię płyt DVD:
module DVD {
typedef string datec;
// IDL jest podzielony na kilka interfejsów:
// MEMBERSHIP - do obsługi zbioru klientów wypożyczalni
// TITLING - do obsługi _dostępnych_ tytułów płyt
// DISKS - do obsługi zbioru fizycznych płyt DVD, które są
// dostępne do wypożyczenia
// RENTAL - obsługa transakcji wypożyczania płyt
// RESERVATIONS - obsługa rezerwacji tytułów na przyszłość
// UTILITIES - niektóre operacje pomocnicze
// FACTORY - interfejs zapewniający możliwość odwoływania się
// do innych interfejsów
// Metody stosowane w obsłudze klientów wypożyczalni
interface MEMBERSHIP {
exception NOSUCHMEMBER {};
// Wewnętrzna reprezentacja jest typu long integer
typedef long memberidt;
// memberidList użyto w tym celu, aby operatory mogły zwracać
// listę klientów wypożyczalni
typedef sequence<memberidt> memberidList;
// Infromacja o każdym kliencie:
struct storemembers {
memberidt memberid; /* wewnętrzny id [1..] */
string memberno; /* numer klienta, którym się on posługuje */
string title; /* Mr Mrs Ms Dr Sir */
string fname; /* imię */
string lname; /* nazwisko */
string houseflatref; /* np. 5 lub 'The birches' itd. */
string address1; /* pierwszy wiersz adresu */
string address2; /* drugi wiersz adresu */
string town; /* miejscowość */
string state; /* stan - wymagany tylko w USA */
string phone; /* nr telefonu w postaci +44(0)123 456789 */
string zipcode; /* LE1 1AA lub kod pocztowy innego rodzaju */
};
Dalej występują operatory pozwalające tworzyć, usuwać, pobierać i nadawać wartości klientom wypożyczalni:
void set(in storemembers recordtoupdate);
void get(in memberidt memberid,
out storemembers recordtocomplete)
raises (NOSUCHMEMBER);
void create (in storemembers recordtoadd,
out memberidt memberid);
void delete (in memberidt memberid)
raises (NOSUCHMEMBER);
Podczas wyszukiwania nazwiska otrzymujemy listę klientów wypożyczalni o nazwiskach pasujących do wzorca:
void search (in string lname,
out memberidList resultids);
// idfromnumber przekształaca "publiczny" numer klienta na wartości
// używane wewnętrznie.
void idfromnumber (in string memberno,
out memberidt memberid)
raises (NOSUCHMEMBER);
};
Metody służące do manipulacji tytułami płyt DVD:
interface TITLING {
exception NOSUCHTITLE {};
exception NOSUCHGENRE {};
typedef string classif;
typedef sequence<classif> classList;
typedef string genres ;
typedef sequence<genres> genreList;
typedef long titlet;
typedef sequence<titlet> titleList;
struct dvdtitles {
titlet titleid; /* wewnętrzny ID [1..] */
string titletext; /* np. 'Milczenie owiec' */
string asin; /* 10-cyfrowy numer katalogowy */
string director; /* nazwisko reżysera */
genres genre; /* rodzaj, np. 'Horror', 'comedy' itd.
API dla standardowej listy dalej */
classif classification;
string actor1; /* np. 'Jeremy Irons' */
string actor2; /* np. 'Ingmar Bergman' */
datec releasedate; /* data YYYYMMDD plus null */
string rentalcost; /* cena za dzień wypożyczenia
w formacie $$$.cc */
};
Zauważmy, że set, get, create, delete i search są powtórnie użyte w przestrzeni nazw TITLING. Będą one też ponownie używane w następnych interfejsach:
void set (in dvdtitles recordtoupdate);
void get (in titlet titleid,
out dvdtitles recordtocomplete)
raises (NOSUCHTITLE);
void create (in dvdtitles recordtoadd,
out titlet titleid);
void delete (in titlet titleid)
raises (NOSUCHTITLE);
void search (in string title, in string name,
out titleList resultids);
};
// Metody używane do manipulacji płytami DVD
//
Dotyczy to rzeczywistego, fizycznego egzemplarza płyty DVD znajdującego się w wypożyczalni, który może być wypożyczony klientowi (np. jedna z siedmiu kopii „Siedmiu samurajów”):
interface DISKS {
exception NOSUCHDISK {};
typedef long dvdidt;
typedef sequence<dvdidt> dvdList;
struct dvddisks {
dvdidt diskid; /* wewnętrzny ID [1..] (nie powiązany z title_id) */
TITLING::titlet titleid;
} ;
// Zwróćmy uwagę na powtórne użycie tych samych nazw operatorów:
// set/get/create/delete
void set (in dvddisks recordtoupdate);
void get (in dvdidt diskid,
out dvddisks recordtocomplete)
raises (NOSUCHDISK);
void create (in dvddisks recordtoadd,
out dvdidt diskid);
void delete (in dvdidt diskid)
raises (NOSUCHDISK);
void search (in TITLING::titlet titleid,
out dvdList resultids);
};
Interfejsy RENTAL i RESERVATIONS posługują się bardziej złożonymi operatorami, niż można by wnioskować z ich opisowych nazw:
interface RENTAL { // Metody używane do obsługi wypożyczeń i zwrotów płyt
exception FORBIDDENRATING {}; // Dzieci nie mogą wypożyczać pornografii
void renttitle (in MEMBERSHIP::memberidt memberid,
in TITLING::titlet titleid,
out DISKS::dvdidt diskid)
raises (DISKS::NOSUCHDISK, MEMBERSHIP::NOSUCHMEMBER,
TITLING::NOSUCHTITLE, FORBIDDENRATING);
void rentdiskinfo (in DISKS::dvdidt diskid,
out MEMBERSHIP::memberidt memberid,
out datec daterented)
raises (DISKS::NOSUCHDISK);
void diskreturn (in DISKS::dvdidt diskid,
in datec returndate,
out MEMBERSHIP::memberidt memberid)
raises (DISKS::NOSUCHDISK);
long titleavailable (in TITLING::titlet titleid, in datec date)
raises (TITLING::NOSUCHTITLE);
void overduedisks (in datec fromdate, in datec todate,
out DISKS::dvdList latedisks);
};
interface RESERVATIONS { // Metody obsługujące rezerwację tytułów
void reservetitle (in datec needed, in TITLING::titlet titleid,
in MEMBERSHIP::memberidt memberid)
raises (TITLING::NOSUCHTITLE, MEMBERSHIP::NOSUCHMEMBER,
RENTAL::FORBIDDENRATING);
void cancelreservation (in MEMBERSHIP::memberidt memberid)
raises (MEMBERSHIP::NOSUCHMEMBER);
void queryreservationbymember (in MEMBERSHIP::memberidt memberid,
out TITLING::titlet titleid)
raises (MEMBERSHIP::NOSUCHMEMBER);
void queryreservationbytitle (in TITLING::titlet titleid,
in datec date,
out MEMBERSHIP::memberidList memberids)
raises (TITLING::NOSUCHTITLE);
};
// Pozostałe metody, których nie można zaklasyfikować gdzie indziej
interface UTILITIES {
void getgenres(out TITLING::genreList genrelist);
void getclassifications (out TITLING::classList classlist);
void errtext(in long errnumber, out string messagetoshow);
void today(out datec date);
// Czas _powinien_ być obsługiwany przez Time Service, ale nasze
// wymagania są znacznie prostsze
};
Interfejs FACTORY jest interfejsem podstawowym, w którym zawarto operatory zwracające odwołania do wszystkich pozostałych interfejsów. Reprezentuje on swego rodzaju automatyczne uruchamianie systemu. Można zmienić sposób rozpowszechniania systemu, zmieniając sposób zwracania tych interfejsów:
interface FACTORY {
MEMBERSHIP MEMBERSHIPFactory ();
TITLING TITLINGFactory ();
DISKS DISKSFactory ();
RENTAL RENTALFactory ();
RESERVATIONS RESERVATIONSFactory ();
UTILITIES UTILITIESFactory ();
};
};
Odwzorowania języków
Ponieważ CORBA ma współpracować z różnymi językami programowania, to trzeba ustalić, w jaki sposób opisane w IDL funkcje i struktury danych będą reprezentowane w języku użytym do zaprogramowania procesów klienta i serwera. Każdy użyty tu język wymaga odwzorowania. Patrząc z perspektywy historycznej, pierwsze pojawiły się odwzorowania języków C i Smalltalk, zaś dzisiaj najbardziej rozpowszechnione i rozwijane są prawdopodobnie odwzorowania dla języków C++ i Java.
Nie ma potrzeby tworzenia uniwersalnego wsparcia o pełnej funkcjonalności dla wszystkich języków, jeśli np. wersja Emacs Lisp może być użyta tylko w programie klienta. Może się okazać, że łatwiej użyć języków dynamicznych lub skryptowych, takich jak np. Python lub Lisp do obsługi obiektów w dynamicznie wzywanych interfejsach (DII, czyli Dynamic Invoked Interface), ponieważ nie rozpoznaje się w nich typów wartości w fazie działania programu — co jest całkowicie zgodne z DII. Jedno odwzorowanie języka Python o nazwie PyORBit wygląda całkowicie wydajnie tylko w zastosowaniu dla DII.
Formalne specyfikacje odwzorowań podane przez konsorcjum OMG dotyczą następujących języków:
Ada,
C,
C++,
COBOL,
Java,
Smalltalk,
Common Lisp
Jeżeli ktoś ma zamiar używać któregokolwiek z powyższych odwzorowań, to warto pobrać pełną dokumentację ropozpowszechnianą przez konsorcjum OMG w postaci dokumentów w formatach Adobe PDF i Postscript (http://www.omg.org/technology/documents/formal/corba_language_mapping_specifica.htm).
Istnieją także odwzorowania dla innych języków, które nie zostały oficjalnie zaakceptowane przez MOG, a były opracowane w sposób nieco mniej sformalizowany przez zainteresowane organizacje. Należą do nich:
Odwzorowanie języka Eiffel na IDL w architekturze CORBA,
Odwzorowanie IDL na język Python (http://orbit-python.sault.org/),
Odwzorowanie IDL na język Erlang (http://www.erlang.se/doc/doc-4.7.3/lib/orber-2.0/doc/html/ch_erl_map.html),
Obsługa języka Haskell w architekturze CORBA (http://www.cse.unsw.edu.au/~chak/haskell/gnome/gnome-small/node11.html),
Odwzorowanie języka Perl w architekturze CORBA (http://people.redhat.com/otaylor/corba/orbit.html),
Implementacja architektury CORBA po stronie klienta Emacs o nazwie corba.el (http://www.lysator.liu.se/~lenst/corba.el).
Składniki odwzorowania języka
Odwzorowanie zapewnia środki służące do wyrażania następujących informacji w danym języku:
wszystkich podstawowych typów danych występujących w IDL,
wszystkich zbudowanych typów danych występujących w IDL,
odwołań do stałych zadeklarowanych w IDL,
odwołań do obiektów zadeklarowanych w IDL,
wezwań operacji, łącznie z transmisją parametrów i odbiorem wyników,
wyjątków, łącznie z tymi, które opisują, co się dzieje przy obsłudze wyjątku, oraz transmisji parametrów opisujących wyjątki,
dostępu do atrybutów,
sygnatur dla operacji zdefiniowanych w ORB, takich jak np. w DII adapterów obiektów itp.
Dobre odwzorowanie pozwala programistom na dostęp do wszystkich funkcji ORB i wyrażenie ich w sposób wygodny dla danego języka programowania. Jeżeli język nie obsługuje sam z siebie funkcjonalności IDL, tak jak np. obsługa wyjątków w języku C, to odwzorowanie może być w takim przypadku nieco zagmatwane.
Celem normalizacji odwzorowania jest zapewnienie, że ten sam kod może być stosowany w dowolnym pośredniku ORB. Oprócz tego, bardzo irytująca byłaby sytuacja, gdyby np. kod w języku C++ przeznaczony do pośrednika omniORB nie mógł być zastosowany z pośrednikiem TAO i cała aplikacja wymagałby gruntownej przeróbki.
Odwzorowania dla języka C
W tym podrozdziale zapoznamy się z różnymi odwzorowaniami IDL dla języka C. Wiele z nich wykorzystuje IDL zastosowany w aplikacji obsługującej wypożyczalnię płyt DVD. W rzeczywistości poznamy jedynie niewielki ułamek tego, co jest wyrażane za pomocą standardowego odwzorowania języka C, ale nawet to pokaże sposób działania całości.
Podstawowe odwzorowania typów danych IDL
Podstawowe odwzorowania typów danych występujących w architekturze CORBA na typy języka C są następujące:
Typ IDL |
Odwzorowanie typu w C |
short |
CORBA_short |
unsigned short |
CORBA_unsigned_short |
long |
CORBA_long |
unsigned long |
CORBA_unsigned_long |
long long |
CORBA_long_long |
unsigned long long |
CORBA_unsigned_long_long |
float |
CORBA_float |
double |
CORBA_double |
long double |
CORBA_long_double |
boolean |
CORBA_boolean |
char |
CORBA_char |
wchar |
CORBA_wchar |
Zamiast korzystać z konstrukcji w rodzaju long some_long_variable, w programach trzeba używać konstrukcji CORBA_long some_long_variable. Powoduje to użycie specyficznego typu danych wymaganego w architekturze CORBA, a nie pozostawienie wyboru dla konkretnej implementacji języka C. Ten problem może nie być aż tak bardzo ważny w przypadku 32-bitowej architektury wykorzystującej procesory firmy Intel, gdzie większość typów jest w zasadzie zgodna z typami stosowanymi w CORBA, ale na pewno warto o nim pamiętać przy przejściu do procesorów 64-bitowych, gdzie typy „rodzime” są przeważnie także 64-bitowe.
W interfejsie MEMBERSHIP, zamiast typedef long memberidt, mamy więc następujące wyrażenie:
typedef CORBA_long DVD_MEMBERSHIP_memberidt;
Dla typedef long titlet w interfejsie TITLING występuje:
typedef CORBA_long DVD_TITLING_titlet;
Zbudowane i szablonowe typy danych
Odwzorowanie zawiera:
Napisy
Rozważmy napis (string) oznaczający datę:
typedef string datec;
Odwzorowanie C dla tej definicji jest następujące:
typedef CORBA_char *DVD_datec;
Uwidacznia to fakt, że napisy w języku C są po prostu tablicami znakowymi.
Struktury
Odwzorowanie w tym przypadku nie wnosi niespodzianek. Konstrukcja IDL dla storemembers w interfejsie MEMBERSHIP w module DVD jest następująca:
struct storemembers {
memberidt memberid; /* wewnętrzny identyfikator [1..] */
string memberno; /* numer, którym posługuje się klient */
string title; /* Mr Mrs Dr */
string fname; /* imię */
string lname; /* nazwisko */
string houseflatref; /* np. 5 lub 'The birches' itd. */
string address1; /* 1. wiersz adresu */
string address2; /* 2. wiersz adresu */
string town; /* miejscowość */
string state; /* stan - wymagany tylko w USA */
string phone; /* +44(0)123 456789 */
string zipcode; /* LE1 1AA lub jakikolwiek kod pocztowy */
To odwzorowuje się na następującą strukturę w języku C:
typedef struct
{
DVD_MEMBERSHIP_memberidt memberid;
CORBA_char *memberno;
CORBA_char *title;
CORBA_char *fname;
CORBA_char *lname;
CORBA_char *houseflatref;
CORBA_char *address1;
CORBA_char *address2;
CORBA_char *town;
CORBA_char *state;
CORBA_char *phone;
CORBA_char *zipcode;
} DVD_MEMBERSHIP_storemembers;
Nazwa modułu, interfejsu i struktury są łączone ze sobą do postaci stosowanej w języku C, czyli DVD_MEMBERSHIP_storemembers.
Sekwencje
Sekwencje powstają w wyniku generacji struktury pobierającej składowe do tablicy. Na przykład w interfejsie MEMBERSHIP zadeklarowano sekwencję memberidList:
typedef sequence<memberidt> memberidList;
Powoduje to generację następującej struktury:
typedef struct
{
CORBA_unsigned_long _maximum,
Dozwolone jest tu użycie _maximum, które określa maksymalną liczbę elementów tworzących sekwencję:
_length;
Element _length jest obowiązkowy i reprezentuje liczbę wpisów przekazanych do sekwencji:
DVD_MEMBERSHIP_memberidt *_buffer;
Tutaj _buffer jest tablicą wskaźników na różne przekazywane wartości, które w tym przypadku w IDL będą typu memberidt, zaś w języku C będą typu DVD_MEMBERSHIP_memebridt:
CORBA_boolean _release;
Zmienna _release informuje, czy informacja o sekwencji powinna być wydana przez ORB po zakończeniu transmisji danych, czy nie:
} CORBA_sequence_DVD_MEMBERSHIP_memberidt;
Aby użyć takiej sekwencji w języku C, należy posłużyć się następującym kodem:
/* Ustawienie zmiennej sekwencyjnej */
CORBA_sequence_DVD_MEMBERSHIP_memberidt membs;
/* Przydzielenie wpisów w sekwencji */
membs._buffer = CORBA_alloc_DVD_MEMBERSHIP_memberidt(6);
membs._length = 6; /* Ustalenie liczby wpisów */
membs._buffer[0] = member1;
membs._buffer[1] = member2;
membs._buffer[2] = member3;
membs._buffer[3] = memebr4;
membs._buffer[4] = member5;
membs._buffer[5] = member6;
Uzyskaliśmy w ten sposób membs, czyli wielkość, którą można przekazać jako argument do serwera lub pobrać jako wynik do klienta.
Typy wyliczeniowe
Podany niżej przykład nie występuje w naszej aplikacji, ale można go tam użyć:
enum emovieratings {G, PG13, NC17, R, NR, XXX};
Można to odwzorować jako:
typedef enum { DVD_G, DVD_PG13, DVD_NC17, DVD_R, DVD_NR, DVD_XXX }
DVD_emovieratings;
Tablice
Mamy następującą definicję:
typedef double memid[8][12];
Uzyskujemy definicje odwzorowane:
typedef CORBA_double DVD_memid[8][12];
typedef CORBA_double DVD_memid_slice[12];
Wielkości o nazwie slice reprezentują wycinki tablicy, które w razie potrzeby mogą być przydzielane oddzielnie.
Odwołania do stałych
Można zadeklarować stałe różnych typów podstawowych, które w języku C zostaną odwzorowane za pomocą dyrektyw #define. Mogą one zawierać obliczenia z zakresu podstawowych działań arytmetycznych i operatory logiczne (czyli np. dodawanie, mnożenie, koniunkcja i przesunięcia bitowe).
Prawdopodobnie najlepiej sprawdzić to na przykładach, szkicując je w IDL i badając odpowiedniki w C. Oto niektóre deklaracje stałych różnego typu w IDL; w kilku z nich wykorzystano obliczenia:
module CONSAMPLES {
enum Colour { Red, Green, Blue, Brown, Fuchsia, Lime};
const Colour FAVORITECOLOUR = Fuchsia;
const long LV = 3;
const long long sdiff = (2500000 << 4) - (254 >> 2) * LV;
const double Pi = 3.14159265358979323846;
const double Piover2 = Pi / 2.0;
IDL zostaje przekształcony w następujący zestaw deklaracji języka C:
typedef enum {
CONSAMPLES_Red, CONSAMPLES_Green, CONSAMPLES_Blue,
CONSAMPLES_Brown, CONSAMPLES_Fuchsia, CONSAMPLES_Lime
} CONSAMPLES_Colour;
#define CONSAMPLES_FAVORITECOLOUR CONSAMPLES_Fuchsia
#define CONSAMPLES_LV 3
#define CONSAMPLES_sdiff 39999811
#define CONSAMPLES_Pi 3.141593
#define CONSAMPLES_Piover2 1.570796
Wzywanie operacji
Stanowi to całą istotę tego, czym zajmuje się CORBA. Oto kilka operacji zadeklarowanych w IDL:
module opsamples {
interface ops {
void op1 (); /* Nie przekazuje żadnych wartości */
void op2 (in long inl); /* Przekazuje jako long */
void op3 (in long inl, out long outl); /* Odbiera i wysyła jako long */
long op4 (inout long iol); /* Zmienia wartość long na long */
};
};
Odwzorowują się one na następujące deklaracje języka C:
/** prototypy **/
void opsamples_ops_op1(opsamples_ops _obj, CORBA_Environment * ev);
void opsamples_ops_op2(opsamples_ops _obj, const CORBA_long inl,
CORBA_Environment * ev);
void opsamples_ops_op3(opsamples_ops _obj, const CORBA_long inl,
CORBA_long * outl, CORBA_Environment * ev);
CORBA_long opsamples_ops_op4(opsamples_ops -obj, CORBA_long * iol,
CORBA_Environment * ev);
Kod serwera używa tych funkcji, zaś kod klienta będzie z nich korzystał, żądając wykonania jakichś czynności.
Obsługa wyjątków
Jest to jedyny obszar, w którym odwzorowanie w języku C jest szczególnie mocno pogmatwane. IDL definiuje strukturalny system obsługi wyjątków podobny do stosowanego w C++. Język C nie dysponuje tego rodzaju właściwościami i w wyniku tego jego odwzorowanie musi zawierać dodatkowe wartości zwracane przez każdą metodę, która zwraca „środowisko wyjątku” i które następnie musi być interpretowane przez program. Można się o tym przekonać, próbując zbudować funkcje pomocnicze tak, aby użyć „rodzimego” odwzorowania tylko kilkakrotnie.
Załóżmy, że deklarujemy zestaw wyjątków następująco:
module main {
exception foo {}; // Wyjątek BEZ argumentu
interface secondary {
exception bar { //Wyjątek Z argumentem
string msg;
};
};
};
Odwzorowanie w języku C daje wówczas następujące deklaracje:
#define ex_main_foo "IDL:main/foo:1.0"
#define _main_foo_defined 1
typedef struct {
int dummy;
} main_foo;
#define ex_main_secondary_bar "IDL:main/secondary/bar:1.0"
#define _main_secondary_bar_defined 1
typedef struct {
CORBA_char *msg;
} main_secondary_bar;
Po powrocie z wezwania operacji CORBA generowana jest struktura wyjątku w następującej postaci:
typedef struct CORBA_environment {
CORBA_exception_type _major;
... inne fragmenty zależne od ORB
} CORBA_environment;
Wartość _major może zawierać albo CORBA_NO_EXCEPTION (co oznacza brak wyjątku), albo CORBA_USER_EXCEPTION (wskazuje to na jeden z wyjątków zadeklarowanych w wywołanym IDL), albo CORBA_SYSTEM_EXCEPTION (jeżeli został wywołany jeden ze standardowych wyjątków architektury CORBA). Nazwę wyjątku można określić, wywołując CORBA_exception_id, zaś jego wartość — wywołując CORBA_exception_value.
Atrybuty
To odwzorowanie najlepiej pokazać na przykładzie. Załóżmy, że deklarujemy zestaw wyjątków:
module attsamp {
interface circle {
attribute float radius;
};
};
Odwzorowanie w języku C daje w wyniku następujące deklaracje:
/** prototypy **/
CORBA_float attsamp_circle__get_radius(attsamp_circle _obj,
CORBA_Environment * ev);
void attsamp_circle__set_radius(attsamp_circle _obj,
const CORBA_float value,
CORBA_Environment * ev);
Pokazane wyżej metody będą używane jako zwykłe operatory, co oznacza, że zmniejszenie nakładu pracy występuje tylko przy pisaniu kodu IDL, co zazwyczaj nie sprawia kłopotów.
Przykład wprowadzający: prosty system powiadamiania
Rozpoczniemy od pokazania pełnego kodu zupełnie prostego systemu. Program będzie przekazywał dwa argumenty: nazwę nadawcy i nazwę odbiorcy, a następnie odczytywał komunikat ze stdin i dostarczał go do serwera komunikatów CORBA. Pełny kod jest dostępny na serwerze ftp wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/zaprli.zip).
Rozważany przykład pokazuje następujące zagadnienia:
niewielki, ale kompletny interfejs IDL,
sposób użycia IDL dla pośrednika ORBit do generacji plików szkieletowych,
kod podstawowego programu maskującego potrzebnego w tym celu, aby w aplikacji można było użyć architektury CORBA,
obsługę i dostarczanie danych do IOR,
możliwość współdziałania z wieloma językami (serwer został napisany w języku Python).
Prosta wymiana komunikatów
Aplikacja pokazana jako przykład służy do przesyłania komunikatów do serwera. Komunikat (wiadomość) zawiera następujące elementy:
identyfikator „nadawcy”,
identyfikator „odbiorcy”, dla którego jest przeznaczony komunikat,
sekwencja wierszy tworzących właściwą treść komunikatu.
Nie jest to aplikacja, którą można bezpośrednio wykorzystać w praktyce, ale jeżeli rozszerzy się pojęcia „nadawcy” i „odbiorcy” i dołączy kilka dodatkowych pól, to można znaleźć podobieństwo do bardzo wielu programów zajmujących się przesyłaniem wiadomości z jednego miejsca do innego.
IDL dla tej aplikacji ma następującą postać:
module MESSAGING {
typedef sequence#&60;string#&62; msgLines;
struct msg {
string fr;
string to;
msgLines body;
};
interface mail {
void submit (in msg message);
};
};
Zastosowanie ORBit z IDL
Pierwotnie w projekcie GNOME używany był pośrednik o nazwie ORBit, a więc nasza aplikacja właśnie z niego będzie korzystać. Jeżeli w systemie są zainstalowane narzędzia programistyczne GNOME, to wśród nich powinien być także i ORBit.
Zachowajmy IDL MESSAGES w pliku o nazwie msg.idl. Polecenie orbit-idl msg.idl spowoduje utworzenie czterech plików, które są zbyt rozwlekłe, aby je tutaj szczegółowo omawiać:
Msg.h |
Zawiera publiczne typy danych i definicje funkcji, które będą używane przez każdy program korzystający z interfejsów zadeklarowanych w msg.idl. |
msg-common.c |
Ten plik zawiera zwykłe funkcje i typy danych, które będą używane w interfejsach zarówno przez klienta, jak i przez serwery. W szczególności są tu funkcje do przydzielania i uwalniania struktur danych zdefiniowanych w interfejsach. |
msg-skels.c |
Ten plik zawiera „szkielety” funkcji i inne definicje wymagane przez ORBit do odbioru żądań. Zadaniem tego pliku jest przechowywanie funkcji odbierających żądania bezpośrednio od ORBit, rozdzielających je i przekazujących do funkcji C obsługujących operacje. Oprócz tego są tu funkcje pobierające wartości przekazywane przez klienty, rozdzielające je i przekazujące ponownie do pośrednika. Dlatego właśnie powinny one zostać wkompilowane w kod serwera komunikatów, wykorzystującego ORB. |
msg-stubs.c |
Ten plik zawiera wyniki konwersji „szkieletów” z pliku msg-skels.c, zapewniając zręby komunikacji między żądaniem generowanym w programie a pośrednikiem. Zręby te zarządzają rozdzielaniem żądań i ich wysyłaniem, a następnie ich odbiorem i łączeniem wyników. |
Klient komunikatów
Utworzymy program klienta w języku C, dzięki czemu zobaczymy odwzorcowania C:
/* Prosty klient */
#include "stdio.h"
#include "orb/orbit.h" /* Nagłówek ORBit */
#include "msg.h" /* Plik nagłówkowy IDL */
int readlines (void);
#define MAXMSGLEN 2500 /* Maksymalna liczba wierszy w msg */
char *contents[MAXMSGLEN+1];
int main (int argc, char *argv[])
{
/* Zmienne używane do odczytu IOR */
FILE *ifp; char *ior, filebuffer[1024];
/* Zmienne krytyczne dla POA */
CORBA_Environment ev;
CORBA_ORB orb;
CORBA_Object msg_client; /* Łącze do obiektu Message */
/* Zmienne używane do przechowywania danych wiadomości, która
ma być wysłana. */
MESSAGING_msg *ourmessage;
MESSAGING_msgLines *mbody;
/* Różne */
int lines, i;
/* Standardowa inicjacja ORB. Zauważmy, że ORB_init "zjada"
wejście z wiersza poleceń */
CORBA_exception_init(#&46;ev);
orb = CORBA_ORB_init(#&46; argv, "orbit-local-orb", #&46;ev);
Następna część pobiera IOR (odwołanie do obiektu). Powinno ono być wpisane do pliku msg.ior przez msg-server w postaci napisu. Jeżeli serwer działa w tej samej lokalizacji co klient, to wszystko powinno się udać. W innym wypadku pojawi się „problem dystrybucji IOR”.
ifp = fopen("msg-server.ior","r");
if( ifp == NULL ){
g_error("No msg-server.ior file!");
exit(-1);
}fgets(filebuffer,1024,ifp);
ior = g_strdup(filebuffer);
fclose(ifp);
Teraz znajdźmy łącze z faktycznym obiektem z interfejsu wiadomości:
msg_client = CORBA_ORB_string_to_object(orb, ior, #&46;ev);
if (!msg_client) {
printf("Cannot bind to %s\n", ior);
exit(-2);
}
Następnie utwórzmy prostą wiadomość, najpierw przydzielając strukturę wiadomości:
ourmessage=MESSAGING_msg__alloc();
A teraz dołączając do niej podstawowe pola:
if (argc < 3) {
printf("Need two arguments: from to\n");
exit(-3);
}
ourmessage->fr=argv[1];
ourmessage->to=argv[2];
mbody = MESSAGING_msgLines__alloc(); /* Przydzielenie struktury... */
lines = readlines(); /* Wczytanie zawartości */
mbody->_length = lines;
mbody->_buffer = CORBA_sequence_CORBA_string_allocbuf(lines);
for (i = 0; i < lines; i++) {
mbody->_buffer[i] = contents[i];
}
ourmessage->body = *mbody; /* Włączenie sekwencji do komunikatu */
/* Teraz wysyłka komunikatu... */
MESSAGING_mail_submit(msg_client, ourmessage, &ev);
Przechwytujemy jakieś wyjątki (spowodowane np. niedostępnością sieci):
if(ev._major != CORBA_NO_EXCEPTION) {
printf("we got exception %d from submit!\n", ev._major);
return 1;
}
/* Oczyszczenie zawartości... */
CORBA_Object_release(msg_client, &ev);
CORBA_Object_release((CORBA_Object)orb, &ev);
return 0;
}
Na zakończenie funkcja, która odczytuje zbitkę wierszy i przenosi je do bufora:
int readlines (void) {
char linebuff[4096]; /* Tymczasowy bufor dla wierszy... */
int nlines, llen;
for (nlines = 0; fgets(linebuff, 4096, stdin); !feof(stdin)) {
llen = strlen(linebuff);
contents[nlines] = (char*) malloc(llen+1);
linebuff[llen-1]=0; /* Wymuszenie ogranicznika wiersza */
strcpy (contents[nlines], linebuff);
nlines++;
if (nlines >= MAXMSGLEN) {
return nlines;
}
}
return nlines;
}
Serwer komunikatów
Kod serwera komunikatów pokazuje niezależność od języka programowania, ponieważ napisano go w języku Python. Widać tu również sposób, w jaki Python umożliwia bardziej czytelne odwzorowanie niż język C, ponieważ ma wbudowane właściwości obsługujące obiekty, klasy i wyjątki (tutaj nie używane).
Oprócz tego Python dysponuje możliwością usuwania pozostałości, co oznacza, że nie musimy się martwić o ubarwianie kodu wywołaniami malloc i free w celu utrzymania pamięci w ryzach. Pamięć musi być tu również przydzielana i zwalniana, ale nie wymaga to wprowadzania dodatkowego kodu. Oto zawartość pliku msg-server.py:
#!/usr/bin/env python
import CORBA
class mail:
msgs = 0 # Message counter
def submit(self, msg):
print "Message Received from:", msg.fr
print "Message for:", msg.to
for line in msg.body:
print line
self.msgs = self.msgs + 1
print "Messages: ", self.msgs
CORBA.load_idl("msg.idl")
CORBA.load_idl("/usr/share/idl/name-service.idl")
orb = CORBA.ORB_init((), CORBA.ORB_ID)
poa = orb.resolve_initial_references("RootPOA")
servant = POA.MESSAGING.mail(mail())
poa.activate_object(servant)
ref = poa.servant_to_reference(servant)
open("./msg-server.ior", "w").write(orb.object_to_string(ref))
print "Wrote out IOR: ", orb.object_to_string(ref)
poa.the_POAManager.activate()
orb.run() # Serwer znajduje się w pętli zdarzeń ORBit i oczekuje żądań
Kompilowanie aplikacji ORBit
A oto plik konfiguracyjny makefile dla naszej aplikacji:
### Makefile dla systemu powiadamiania
# Konfiguracja ORBit:
ORBIT_IDL = /usr/bin/orbit-idl
ORBIT_CFLAGS = -I/usr/lib/glib/include -I/usr/include
ORBIT_LIBS = -L/usr/lib -lORBit -lIIOP -lORBitutil -lglib -lm
CFLAGS = $(ORBIT_CFLAGS) -g
LFLAGS = $(ORBIT_LIBS)
all: msg-client msg.hh msgSK.cc
### Kilka przekształceń ORBit IDL
%.h : %.idl
orbit-idl $<
%-common.c : %.idl
orbit-idl $<
%-skels.c : %.idl
orbit-idl $<
%-stubs.c : %.idl
orbit-idl $<
### Zależności omniORB...
%SK.cc: %.idl
omniidl2 $<
%.hh : %.idl
omniidl2 $<
### Teraz nasze ulubione zależności:
msg.h: msg.idl
msg-common.c: msg.idl
msg-stubs.c: msg.idl
msg-skels.c: msg.idl
### Tutaj ustawiono powiązanie z omniORB2 zapewniając dodatkową weryfikację
### czy IDL jest poprawnie zbudowane z perspektywy innego języka,
### ORB i parsera. Jeżeli ten pośrednik nie jest zaistalowany, to
### powiązanie się nie uda, generując błąd.
msg.hh: msg.idl
msgSK.cc: msg.idl
### Kompilacja klienta komunikatów
msg-client: msg-client.o msg-common.o msg-stubs.o
$(CC) -o msg-client msg-client.o msg-stubs.o msg-common.o \
$(LFLAGS)
msg-client.o: msg-client.c
Może nie jest najlepszym pomysłem użycie make do automatycznej generacji zrębów z IDL, ale to działa. Zauważmy, że makefile tworzy pliki powiązań do C++ dzięki użyciu pośrednika omniORB. Faktycznie nie korzystamy z tego, lecz taka konstrukcja pomaga w wyszukiwaniu błędów w plikach IDL, ponieważ omniORB szuka problemów odnoszących się do języka C++.
Uruchamianie przykładowej aplikacji
Najpierw należy się upewnić, czy ORBit, Python oraz ORBit-Python są zainstalowane w systemie. Pierwsze dwa są prawdopodobnie dostarczone w dystrybucji Linuksa. ORBit-Python umożliwiający powiązanie języka Python z pośrednikiem ORBit można pobrać ze strony http://orbit-python.sault.org/.
Załóżmy, że wszystkie wspomniane do tej pory pliki są już w katalogu /usr/local/erc/ORBitsamples. Przejdźmy do tego katalogu i użyjmy polecenia:
$ make
Zostaną utworzone zręby IDL i msg-client, a także msg.hh oraz msgSK.cc, jeżeli był zainstalowany pośrednik omniORB.
Musimy teraz uruchomić serwer, aby można było dokonać rozruchu naszej rozproszonej aplikacji:
$ ./msg-server
Dokonuje się to z aktualnie używanego terminala, a więc można uruchomić ten program w tle, aby dalsze korzystanie z terminala było możliwe.
Polecenie spowodowało uruchomienie serwera i zapis IOR w postaci znakowej do pliku /usr/local/src/ORBitsamples/msg-server.ior. Można przejrzeć zawartość tego pliku, aby sprawdzić, jak wygląda IOR. Teraz nadeszła chwila prawdy: przejdźmy na dostępny terminal i wpiszmy polecenie:
$ ./msg-client me you < msg-server.ior
Terminal, na którym został uruchomiony msg-server, powinien teraz wyświetlić liczbę komunikatów, informację o każdym komunikacie i zawartość pliku IOR. Doskonale! Mamy więc pierwszą aplikację działającą w architekturze CORBA. Jeżeli jest dostępny inny komputer połączony za pomocą protokołu TCP/IP, to można spróbować zestawić zdalne połączenie. Należy w tym celu wykonać kilka czynności: na tym komputerze trzeba uruchomić msg-client i mieć dostęp do pliku msg-server.ior. Jednym ze sposobów rozwiązania tego problemu jest zamontowanie katalogu /usr/local na obydwóch komputerach. Można także skopiować wspomniane pliki za pomocą FTP lub przenieść je na dyskietce. W przypadku komputera Alpha firmy Compaq potrzebna będzie ponowna kompilacja programu msg-client.
Wszystko to pokazuje, że można wysyłać komunikaty z klienta do serwera. Serwer napisany w języku Python był obciążony dla celów testowych wiadomościami przesyłanymi przez dwa inne serwery i w ciągu 10 minut przetworzył 15000 komunikatów bez zgłaszania błędów.
Materiały źródłowe
Poniżej podajemy użyteczne strony WWW:
Konsorcjum Object Management Group (http://www.omg.org)
ORRBit (http://www.labs.redhat.com/orbit)
Enhydra (http://www.enhydra.org)
CORBA Links dra Douglasa Schmidta (http:/www.cs.wustl.edu/~schmidt/corba.html)
Bezpłatny pośrednik ORB Toma Valesky (http://patriot.net/~tvalesky/freecorba.html)
Open Source Java i XML Application Server z projektu Enhydra (http://www.enhydra.org)
Simple Object Access Protocol (SOAP) (http://www.w3.org/TR/SOAP/)
Podsumowanie
W tym rozdziale pokazano architekturę CORBA jako sposób budowy rozproszonych aplikacji obiektowych oraz jej ważniejsze składniki, czyli pośredniki wywołań obiektów (ORB) i język definicji interfejsu (IDL).
Porównano architekturę CORBA z niektórymi innymi systemami realizującymi podobne zadania. Pod względem funkcjonalnym zbliżają się one do siebie, ponieważ organizacje wspomagające CORBA zaczynają wdrażać usługi przypominające usługi występujące w innych systemach lub pomosty komunikacyjne między systemami.
Zapoznaliśmy się z niektórymi szczegółami struktur IDL oraz ze sposobem definiowania interfejsów obiektów. Zobaczyliśmy także, jak w aplikacjach CORBA można łączyć IDL niezależny od języka ze specyficznymi językami programowania, zwracając szczególną uwagę na odwzorowanie w języku C.
Pokazano tu również kod źródłowy prostego systemu powiadamiania, zawierający fragmenty napisane w językach C i Python oraz korzystający z pośrednika ORBit.
32 Część I ♦ Podstawy obsługi systemu WhizBang (Nagłówek strony)
32 H:\Książki\!Wit\Zaawansowane programowanie w systemie Linux\6 po jezykowej\R-20-06.doc