Rozdział 21. Implementacja
CORBA z pośrednikiem
ORBit
Jakiś czas temu grupa osób biorących udział w projekcie GNOME szukała pośrednika ORB.
Sprawdzono kilka rozwiązań, spośród których tylko jedno korzystało z języka C, a reszta z
C++.
Opracowany w firmie Xerox pakiet ILU (Inter Language Unification) zawierał usługi CORBA i
obsługiwał sporo języków programowania, w tym również język C. W roku 1998 licencja tego
programu budziła jednak wątpliwości, zaś firma Xerox nie udzielała wystarczających wyjaśnień,
które pozwoliłyby na użycie ILU.
Oprócz tego w pakiecie ILU występował inny problem: nie spełniał on dokładnie specyfikacji
CORBA, ponieważ zastosowano w nim własny język definicji interfejsu o nazwie ISL (podobny do
IDL) oraz kilka specyficznych protokołów, co zmuszało do dodatkowych przekształceń przy
próbie użycia z aplikacją CORBA. Jest to bardzo interesujące pole do doświadczeń z metodami
zbliżonymi do CORBA, ale nie jest to dokładnie ta architektura.
Z powodu tych ograniczeń licencyjnych Dick Porter i Elliot Lee zbudowali najpierw parser IDL,
rozpoczynając tym samym tworzenie pośrednika ORBit będącego implementacją architektury
CORBA. Kontynuują oni swoje prace aż do dzisiaj.
Obecnie wyjaśniono już zagadnienia licencyjne dotyczące ILU, ale w ciągu tego czasu ORBit
całkiem dobrze się rozwinął. Zaletą tego pośrednika są niewielkie rozmiary i to, że został
zaprojektowany tylko jako implementacja architektury CORBA. ORBit koncentruje się więc na
usługach tylko jednego rodzaju, w odróżnieniu od ILU.
CORBA w aplikacji obsługującej
wypożyczalnię płyt DVD
Celem tego rozdziału jest pokazanie, że można zbudować aplikację korzystającą z architektury
CORBA, dokonując niewielkich modyfikacji w pierwotnym interfejsie graficznym. Zalety
zastosowania CORBA w porównaniu z monolitycznym podejściem stosowanym do tej pory w
naszej aplikacji są następujące:
Aplikacja umożliwi równoczesne połączenie wielu użytkowników do jednego zródła
danych bez konieczności stosowania dodatkowego programu zarządzającego relacyjną
bazą danych dzięki temu serwer aplikacji stanie się tym właśnie programem.
Klienty nie muszą same troszczyć się o synchronizację dostępu do danych.
Systemy klient-serwer o konstrukcji plikowej tradycyjnie wymagają wiele wysiłku poświęconego
na zapewnienie odpowiedniego blokowania plików (tak, aby użytkownicy nie niszczyli sobie
nawzajem danych). Najnowocześniejsza wersja bazy danych Berkeley o nazwie Sleepycat DB
zawiera przeznaczony specjalnie do tego celu proces lock server. Nieustannie krytykuje się też
NFS za brak odpowiedniego mechanizmu blokowania.
Nasza aplikacja do obsługi wypożyczalni płyt DVD nie musi posługiwać się rozproszonym
mechanizmem blokowania, ponieważ dostęp do danych odbywa się tylko z jednego miejsca z
serwera dostarczającego interfejsy. Ponieważ PostgreSQL również zawiera takie funkcje
zarządzania danymi, to w większych systemach może być potrzebne porównanie, które podejście
jest skuteczniejsze. Prawdopodobnie najlepszy okaże się sposób pośredni, czyli wykorzystanie
systemu zarządzania bazą danych oraz jakiegoś systemu transakcyjnego. Producenci potwierdzili
to doświadczalnie, próbując utworzyć systemy porównań wydajności, ponieważ często posługują
się zarówno relacyjnymi bazami danych (np. Oracle) równolegle z monitorowaniem oddzielnego
przetwarzania transakcji (np. BEA Tuxedo).
Klient DVD
Jest to aplikacja GNOME omawiana już w wielu postaciach. Zmiany w tym programie w
większości dotyczą API określającego dostęp do danych i podanego w pliku flatfile.c.
Zmodyfikowany program ma nazwę corbaclient.c, co wskazuje na wprowadzenie metod
używanych w architekturze CORBA zamiast lokalnego dostępu do plików.
Oprócz tego wprowadzono kilka innych zmian: dodano pomocnicze pliki dvd.c, dvd-common.c
i dvd-stubs.c utworzone przez orbit-idl w celu obsługi operatorów CORBA, dodano także
fragment kodu obsługujący żądania dostępu do usług CORBA w pliku main.c.
Serwer DVD
Ten program napisany w języku Python działa głównie jako serwer CORBA, zawierając funkcje
wykonujące różne operacje zadeklarowane w interfejsach.
Serwer przechowuje dane w plikach bazy danych zbliżonej do Berkeley DB. Na każdy z
interfejsów MEMEBERSHIP, TITLING, DISKS, RESERVATIONS i RENTALS przypada po jednym
pliku. Dane są przechowywane w postaci napisów, co nie jest w pełni zgodne ze sposobem
wymaganym w CORBA do zapisu odwołań obiektowych jako IOR. Program w roli klienta CORBA
umożliwia obsługę logów, śledząc przychodzące żądania.
Serwer obsługi logów
Ten element monitoruje działanie serwera DVD. W naszej aplikacji system obsługi logów jest
używany głównie jako narzędzie pomocnicze do wyszukiwania błędów i nie umożliwia przejścia
na obsługę komunikatów lokalnych podczas awarii sieci.
W systemach bardziej zaawansowanych serwer obsługi logów (ang. logging server) może być
najważniejszym narzędziem zabezpieczenia, dzięki któremu będą gubione transakcje. Może on
także udostępniać repozytorium danych przydatne np. po awarii, gdy trzeba sprawdzić kto, co i
gdzie zrobił.
Dzięki zastosowaniu serwera obsługi logów można uzyskać zwiększenie pewności działania. W
systemach zarządzania bazami danych stosuje się metodę zwaną zapisem logu transakcji (ang.
transaction logging). Każda zmiana dokonana w bazie danych jest wpisywana do logu transakcji
w kolejności chronologicznej. Jeżeli baza uszkodzi się, np. podczas awarii sprzętu, to można ją
odtworzyć, korzystając z wcześniejszej kopii i logu transakcji, na podstawie którego odtwarzane
są czynności wykonane w bazie do momentu awarii.
W wersji bardziej rozbudowanej można wprowadzić kolejny zestaw interfejsów, łącznie z
procesem logowania wymagającym potwierdzania autentyczności. Aatwo można dodać interfejs,
który sterowałby dostępem do bazy danych na podstawie informacji podawanych przez
użytkownika podczas logowania. Istnieje tzw. interfejs Co-Security zdefiniowany w ramach usług
zabezpieczania, zapewniający całkiem sprawny mechanizm potwierdzania autentyczności.
Niestety, nie jest on dostępny w żadnej z bezpłatnych implementacji architektury CORBA.
Serwer weryfikacji
Jedną z ważniejszych zalet stosowania relacyjnej bazy danych takiej jak np. PostgreSQL jest to, że
zapewnia ona możliwość wprowadzania relacji między fragmentami danych oraz środki
kontrolujące integralność tych relacji. Na przykład relacja klucza obcego może być użyta jako
zabezpieczenie, że płyta DVD będzie wypożyczona tylko tym klientom wypożyczalni, którzy są
wobec niej niezadłużeni. Podobnie jak w wypadku zagadnień blokowania dostępu do plików, w
wypadku niewielkiej aplikacji można wymusić odpowiednią relację w bazie danych albo
weryfikację i zarządzanie relacjami pozostawić serwerowi.
Przy większych aplikacjach obsługujących mocno obciążone bazy danych można podwyższyć
wydajność, przenosząc część czynności weryfikacyjnych do usług CORBA, które działają jeszcze
przed podjęciem próby modyfikacji bazy danych. Jeżeli wszystkie weryfikacje będą się odbywać
w systemie zarządzającym relacyjną bazą danych, to każda nieudana próba modyfikacji bazy
powoduje jej dodatkowe obciążenie (należy bowiem rozpocząć modyfikację, wykryć i
zasygnalizować okoliczności powstania błędu, a następnie anulować zmiany). W odróżnieniu od
takiego sposobu działania systemu, wychwytywanie błędów jeszcze przed dostępem do bazy
znacznie zmniejsza liczbę anulowanych transakcji.
Kod klienta
Pierwsza zmiana kodu zródłowego występuje w main.c:
int main (int argc, char *argv[])
{
GnomeClient *client;
#ifdef ENABLE_NLS
bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR);
textdomain (PACKAGE);
#endif
user = NULL;
passwd = NULL;
/* Nowy fragment kodu dla aplikacji CORBA! */
ConnectORB(argc, argv); /* Podłączenie do serwera DVD */
gnome_init_with_popt_table("dvdstore", VERSION, argc, argv,
options, 0, NULL);
/* ... i tak dalej ... */
}
Kod serwera obsługi logów
Serwer obsługujący logi dostarcza strukturalną informację o zapisach w logach, korzystając z
następującego IDL:
module LOG {
struct loginfo {
string hostname;
string userid;
string application;
string messagetype;
string shortmessage;
};
interface LOG {
void addlog (in loginfo info);
};
};
Interfejsowi temu celowo nadajemy prostą postać, ponieważ nie chcemy dawać mu zbyt wielu
szans na błędne działanie:
#!/usr/bin/env python
# $ID$
import CORBA, sys, regex, string, random, time
from string import split, strip, joinfields
from time import localtime, strftime, time
# Tutaj są funkcje wspomagające interfejs LOG
class Log: # interface
def addlog (self, info):
logfile = open("./logs.log", "a")
logfile.write(joinfields([info.hostname,
strftime("%Y/%m/%d %H:%M:%S %Z",
localtime(time())),
info.userid, info.application,
info.messagetype, info.shortmessage], "|"))
logfile.write("\n")
logfile.close()
CORBA.load_idl("logger.idl")
orb = CORBA.ORB_init((), CORBA.ORB_ID)
poa = orb.resolve_initial_references("RootPOA")
servant = POA.LOG.LOG(Log())
poa.activate_object(servant)
ref = poa.servant_to_reference(servant)
open("./logger.ior", "w").write(orb.object_to_string(ref))
print "Done initialization: Proceed!"
poa.the_POAManager.activate()
orb.run()
Występuje tu tylko jeden interfejs zawierający jedną funkcję. W zupełności wystarcza to do
przekazania komunikatu do logu.
Plik logu jest otwierany i zamykany przy każdym wpisie, co może być przyczyną niewielkiego
spadku wydajności, ale jednocześnie zapewnia zapisanie wszystkich modyfikacji na dysk (co jest
szczególnie pożądane podczas awarii). Oznacza to, że jeżeli proces zarządzający logiem zmieni
nazwę tego pliku, np. w celu usunięcia starszych zapisów, to serwer nie musi zajmować się tą
czynnością.
Kod serwera DVD
Serwer DVD został napisany w języku Python z zastosowaniem odwzorowania ORBit-Python
(http://orbit-python.sault.org/).
Rozpoczynamy od definicji zestawu funkcji pomocniczych na potrzeby wewnętrzne serwera.
Należą do nich definicje repozytoriów danych, zachowujących za pomocą bsddb.btopen dane
na dysku w postaci drzew B-Tree. Funkcje korzystające z tych baz danych wymagają
wymuszonych modyfikacji danych na dysku za pomocą wywołań takich jak SHMEMBERS.sync:
.#!/usr/bin/env python
import CORBA, sys, string, random, time, bsddb, os
from string import split, strip, joinfields
from random import randint
from time import localtime, strftime, time
### Podłączenie uzupełniających się tablic do plików
SHDISKS=bsddb.btopen("disks.db", 'c')
SHMEMBERS=bsddb.btopen("members.db", 'c')
SHRENTALS=bsddb.btopen("rentals.db", 'c')
SHRESERVATIONS=bsddb.btopen("reservations.db", 'c')
SHTITLES=bsddb.btopen("titles.db", 'c')
Funkcje należące do klasy SETUPCOUNTERS umożliwiają tworzenie nowych wpisów w tabelach
przechowujących dane płyt, klientów wypożyczalni oraz tytułów. Pokazuje to, że implementacja
nie ogranicza się tylko do korzystania z zestawu operacji wyliczonych w IDL. W IDL definiuje się
interfejsy publiczne, zaś serwer zawiera kilka interfejsów prywatnych, wykorzystywanych na jego
własne potrzeby.
### Teraz funkcje pomocnicze...
class setupCounters:
def maxforall(self):
self.maxfordisks()
self.maxformembers()
self.maxfortitles()
def maxfordisks(self):
if DBMAX.has_key("disks"):
max = DBMAX["disks"]
else:
max = 1
try:
i=SHDISKS.first()
while i != None:
iint = string.atoi(i)
if iint > max:
max=iint+1
i=SHDISKS.next(i)
except:
DBMAX["disks"] = max
def maxformembers(self):
if DBMAX.has_key("members"):
max = DBMAX["members"]
else:
max = 1
try:
i=SHMEMBERS.first()
while i != None:
iint = string.atoi(i)
if iint > max:
max=iint+1
i=SHMEMBERS.next(i)
except:
DBMAX["members"] = max
def maxfortitles(self):
if DBMAX.has_key("titles"):
max = DBMAX["titles"]
else:
max = 1
try:
i=SHTITLES.first()
while i != None:
iint = string.atoi(i)
if iint > max:
max=iint+1
i=SHTITLES.next(i)
except:
DBMAX["titles"] = max
Funkcja logit jest wykorzystywana do zamaskowania operacji, które są wykonywane przez
serwer obsługujący logi, co upraszcza działanie całości:
uname = os.uname()
hostname = uname[1]
def logit(type, info):
try:
LOGORB.addlog(LOG.loginfo(hostname=hostname,
userid="%d" % os.getuid(),
application="dvd-server",
messagetype=type,
shortmessage=info))
except:
print "logging server broken!"
Istnieje kilka funkcji przekształcających wartości na napisy i odwrotnie. Problem stanowi tu takie
przekształcanie struktur danych występujące w architekturze CORBA na napisy i odwrotnie, aby
można je było wstawić do baz danych bsddb:
### Funcje pakujące i rozpakowujące informacje z pliku DBM
def idtostring (id):
return "%d" % id
def destringizereservationinfo(sres):
rout=DVD.RESERVATIONS.reservation
(mbr, ttl, dd)=string.split(sres, "\\")
rout.memberid=string.atoi(mbr)
rout.titleid=string.atoi(ttl)
rout.dwanted=dd
return rout
def stringizereservation(res):
return string.join(("%d"%res.memberid, "%d"%res.titleid,
res.dwanted), "\\")
def destringizerentinfo(srental):
rout=DVD.RENTAL.rentinfo
(dsk, mbr, dd) = string.split(srental, "\\")
rout.diskid=string.atoi(dsk)
rout.memberid=string.atoi(mbr)
rout.drented=dd
return rout
def stringizerentinfo(rrec):
return string.join(("%d"%rrec.diskid, "%d"%rrec.memberid,
rrec.drented), "\\")
def destringizedisk(sdisk):
disk=DVD.DISKS.dvddisks()
(sd, st) = string.split(sdisk, "\\")
disk.diskid = string.atoi(sd)
disk.titleid = string.atoi(st)
return disk
def stringizedisk(disk):
return string.join(("%d"%disk.diskid, "%d"%disk.titleid), "\\")
def stringizetitle(title):
return string.join(("%d" % title.titleid, title.titletext,
title.asin, title.director, title.genre, title.classification, title.actor1,
title.actor2, title.releasedate, title.rentalcost, title.image), "\\")
def destringizetitle(stitle):
title=DVD.TITLING.dvdtitles()
(mttl, title.titletext, title.asin, title.director, title.genre,
title.classification, title.actor1, title.actor2, title.releasedate,
title.rentalcost, title.image) = string.split(stitle, "\\")
title.titleid=string.atoi(mttl)
return title
def stringizemember(member):
return string.join(("%d" % member.memberid, member.memberno,
member.title, member.fname, member.lname,
member.houseflatref, member.address1,
member.address2, member.town, member.state,
member.phone, member.zipcode), "\\")
def destringizemember(smember):
member=DVD.MEMBERSHIP.storemembers()
(mid, member.memberno, member.title, member.fname, member.lname,
member.houseflatref, member.address1, member.address2,
member.town, member.state, member.phone, member.zipcode) =
string.split(smember, "\\")
member.memberid=string.atoi(mid)
return member
Dalsze ulepszenia systemu mogą polegać na zastosowaniu klas shelve lub pickle języka
Python, dzięki którym będzie można bardziej wydajnie przetwarzać struktury danych. Ostatecznie
można też zezwolić na komunikację serwera DVD z serwerem bazy danych PostgreSQL.
Zakończymy ustawianie składników nie wchodzących do architektury CORBA, tworząc połączone
tablice do przechowywania komunikatów o błędach i uruchamiając funkcje maxforall tworzącą
liczniki wpisów:
### Inicjacja fragmentów spoza ORB...
FACTORYOBJECT = {}
DBMAX = {}
ERRNDICT = {
0 : "DVD_SUCCESS" ,
-1 : "DVD_ERR_NO_FILE" ,
-2 : "DVD_ERR_BAD_TABLE" ,
-3 : "DVD_ERR_NO_MEMBER_TABLE" ,
-4 : "DVD_ERR_BAD_MEMBER_TABLE",
-5 : "DVD_ERR_BAD_TITLE_TABLE" ,
-6 : "DVD_ERR_BAD_DISK_TABLE" ,
-7 : "DVD_ERR_BAD_SEEK" ,
-8 : "DVD_ERR_NULL_POINTER" ,
-9 : "DVD_ERR_BAD_WRITE" ,
-10 : "DVD_ERR_BAD_READ" ,
-11 : "DVD_ERR_NOT_FOUND" ,
-12 : "DVD_ERR_NO_MEMORY" ,
-13 : "DVD_ERR_BAD_RENTAL_TABLE" ,
-14 : "DVD_ERR_BAD_RESERVE_TABLE" }
ERRMSGDICT = {
"DVD_SUCCESS" : "no error",
"DVD_ERR_NO_FILE" : "cannot open file",
"DVD_ERR_BAD_TABLE" : "corrupt table file",
"DVD_ERR_NO_MEMBER_TABLE" : "no member table",
"DVD_ERR_BAD_MEMBER_TABLE" : "corrupt member table",
"DVD_ERR_BAD_TITLE_TABLE" : "corrupt title table",
"DVD_ERR_BAD_DISK_TABLE" : "corrupt disk table",
"DVD_ERR_BAD_RENTAL_TABLE" : "corrupt rental table",
"DVD_ERR_BAD_RESERVE_TABLE" : "corrupt reserve table",
"DVD_ERR_BAD_SEEK" : "cannot seek in file",
"DVD_ERR_NULL_POINTER" : "null data pointer",
"DVD_ERR_BAD_WRITE" : "cannot write to file",
"DVD_ERR_BAD_READ" : "cannot read file",
"DVD_ERR_NOT_FOUND" : "no match found",
"DVD_ERR_NO_MEMORY" : "out of memory"}
SETUP=setupCounters()
SETUP.maxforall()
Klasa języka Python o nazwie Factory zawiera funkcje inicjujące i udostępniające klientom
obiekty odwołujące się do interfejsów DVD:
class Factory:
def generateFactory(self, name, instance):
new_instance = instance
poa.activate_object(new_instance)
FACTORYOBJECT[name] = poa.servant_to_reference(new_instance)
return FACTORYOBJECT[name]
def DISKSFactory(self):
if FACTORYOBJECT.has_key("disks"):
return FACTORYOBJECT["disks"]
else:
logit("Factory", "Create DISKS Interface")
return self.generateFactory("disks", POA.DVD.DISKS(Disks()))
def UTILITIESFactory(self):
if FACTORYOBJECT.has_key("utilities"):
return FACTORYOBJECT["utilities"]
else:
logit("Factory", "Create utilities interface")
return self.generateFactory("utilities",
POA.DVD.UTILITIES(Utilities()))
def MEMBERSHIPFactory(self):
if FACTORYOBJECT.has_key("membership"):
return FACTORYOBJECT["membership"]
else:
logit("Factory", "Create membership interface")
return self.generateFactory("membership",
POA.DVD.MEMBERSHIP(Membership()))
def TITLINGFactory(self):
if FACTORYOBJECT.has_key("titling"):
return FACTORYOBJECT["titling"]
else:
logit("Factory", "Create titling interface")
return self.generateFactory("titling",
POA.DVD.TITLING(Titling()))
def DISKSFactory(self):
if FACTORYOBJECT.has_key("disks"):
return FACTORYOBJECT["disks"]
else:
logit("Factory", "Create disks interface")
return self.generateFactory("disks",
POA.DVD.DISKS(Disks()))
def RENTALFactory(self):
if FACTORYOBJECT.has_key("rental"):
return FACTORYOBJECT["rental"]
else:
logit("Factory", "Create rental interface")
return self.generateFactory("rental",
POA.DVD.RENTAL(Rental()))
def RESERVATIONSFactory(self):
if FACTORYOBJECT.has_key("reservations"):
return FACTORYOBJECT["reservations"]
else:
logit("Factory", "Create reservations interface")
return self.generateFactory("reservations",
POA.DVD.RESERVATIONS(Reservations()))
Klasa MEMBERSHIP obsługuje od strony serwera interfejs opisujący klientów
wypożyczalni:
class Membership:
def set (self, recordtoupdate):
logit("Membership", "Set contents for %d" %
recordtoupdate.memberid)
SHMEMBERS[idtostring(recordtoupdate.memberid)]=
stringizemember(recordtoupdate)
SHMEMBERS.sync()
def get (self, memberid):
try:
record=SHMEMBERS[idtostring(memberid)]
except:
logit("Membership", "Failure of get() contents for member %d"
% memberid)
print "Couldn't get member", memberid
raise DVD.MEMBERSHIP.NOSUCHMEMBER
logit("Membership", "Success of get() contents for member %d"
% memberid)
return destringizemember(record)
def delete (self, memberid):
try:
del SHMEMBERS[idtostring(memberid)]
logit("Membership", "delete contents for %d" % memberid)
SHMEMBERS.sync()
except:
raise DVD.MEMBERSHIP.NOSUCHMEMBER
def create (self, recordtoadd):
lastid = DBMAX["members"]
lastid = lastid + 1
logit("Membership", "Create new member record - %d" % lastid)
DBMAX["members"] = lastid
recordtoadd.memberid = lastid
recordtoadd.memberno = "%d" % lastid
SHMEMBERS[idtostring(lastid)]=stringizemember(recordtoadd)
SHMEMBERS.sync()
logit("Membership", "Create new member for %d" % lastid)
return lastid
def search (self, lname):
rseq = []
try:
(key,value)=SHMEMBERS.first()
while 1 == 1:
lst=string.split(value, "\\")
surname=lst[4]
if string.upper(surname) == string.upper(lname):
rseq.append (string.atoi(key))
(key,value)=SHMEMBERS.next()
except:
done = ""
logit("Membership", "Search for %s" % lname)
rseq.sort()
return rseq
def idfromnumber (self, memberno):
logit("Membership", "id-to-number for %s" % memberno)
try:
(key,value)=SHMEMBERS.first()
while 1 == 1:
lst = string.split(value, "\\")
no = lst[1]
if no == memberno:
return string.atoi(key)
(key,value) = SHMEMBERS.next()
except:
raise DVD.MEMBERSHIP.NOSUCHMEMBER
Klasy TITLING oraz DISKS także obsługują odpowiednie interfejsy od strony serwera. Ponieważ
ich kod jest bardzo podobny do kodu MEMBERSHIP, nie będziemy go tutaj cytować.
Klasa RENTALS obsługuje od strony serwera operacje wypożyczania. Nie został jeszcze
zbudowany pełny interfejs graficzny, a więc niektóre operacje będą uzupełnione pózniej.
Zwróćmy uwagę na to, że IDL nie definiuje funkcji set lub get (a więc klienty nie mają do nich
dostępu). Są one zdefiniowane jako prywatne i używane w innych funkcjach w tym interfejsie:
def set (self, disk, rentinfo):
SHRENTALS[idtostring(disk)] = stringizerentinfo(rentinfo)
# Powinno to obsługiwać wyjątki, lecz na razie tak się nie dzieje.
# Korzystają z tego tylko inne funkcje wewnątrz serwera,
# zaś kontrola błędów jest dokonywana wewnątrz nich.
def get(self, disk):
try:
record=SHRENTALS[idtostring(disk)]
except:
logit("DISKS", "Failure of get() contents for disk %d"
% disk)
raise DVD.DISKS.NOSUCHDISK
logit("DISKS", "Success of get() contents for disk %d" % disk)
return destringizerentinfo(record)
def renttitle (self, memberid, titleid):
MEM= FACTORYOBJECT["membership"]
TTL= FACTORYOBJECT["titling"]
DSK= FACTORYOBJECT["disks"]
RNT= FACTORYOBJECT["rentals"]
RES= FACTORYOBJECT["reservations"]
try:
mbr=MEM.get(memberid)
except:
raise DVD.MEMBERSHIP.NOSUCHMEMBER
try:
ttl=TTL.get(titleid)
except:
raise DVD.TITLING.NOSUCHTITLE
### Jeżeli zachowujemy informację o klasyfikacji dozwolonej
### dla każdego klienta wypożyczalni, to możemy przetwarzać
### DVD.RENTAL.FORBIDDENRATING w tym miejscu...
dlist = DSK.search(titleid) # Pobranie listy płyt...
# Następnie sprawdzenie dostępności pozycji z listy
availabledisk = 0
for d in dlist:
# Sprawdzenie wypożyczenia
# Jeśli wypożyczona, to kontynuacja
try:
r=RNT.get(d)
if r.datec != "":
skip="Y"
except:
skip="N"
# Wyszukiwanie rezerwacji tytułu
# Jeśli zarezerwowany i pasuje ID klienta, to mamy wynik
if skip == "N":
try:
r=RES.get(d)
if (r.memberid == memberid) and
(r.titleid == titleid):
founddisk = d
except: pass
# Jeśli wszystko OK, to ustawiamy dostępność płyty i przerwa
if skip != "Y":
founddisk = d
break
# Teraz wypożyczenie płyty...
try:
logit("Rental", "rentdiskinfo - disk %d - failed" % diskid)
rrec=DVD.Rental.rentinfo(diskid=founddisk,
memberid=memberid, drented="20000801")
SRENTAL[idtostring(founddisk)]= stringizerentinfo(rrec)
logit("Rental", "rentdisk - disk %d member %d"
% (founddisk, memberid))
# Jeżeli klient zarezerwował ten tytuł, to anulowanie
# tej operacji teraz...
except:
logit("Rental", "rentdiskinfo - failed for %d" % titleid)
raise DVD.DISKS.NOSUCHDISK
try:
rtitle = RES.queryreservationbymember(memberid)
if rtitle == titleid:
RES.cancelreservation(memberid)
except:
raise DVD.MEMBERSHIP.NOSUCHMEMBER
return founddisk
# Zwraca ID płyty, jeśli istnieje...
def rentdiskinfo (self, diskid):
print "Finish RENTAL::rentdiskinfo()"
try:
rtl=destringizerentinfo(SRENTAL[idtostring(diskid)])
mbr=rtl.memberid
dt=rtl.drented
logit("Rental", "rentdiskinfo - disk %d - Found member %d date %s"
% (diskid, mbr, returndate))
except:
logit("Rental", "rentdiskinfo - disk %d - failed" % diskid)
raise DVD.DISKS.NOSUCHDISK
return mbr, dt
def diskreturn (self, diskid, returndate):
try:
dsk= destringizerentinfo(SRENTAL[idtostring(diskid)])
del SRENTAL[idtostring(diskid)]
logit("Rental", "Return disk %d %s" % (diskid, returndate))
return dsk.memberid
except:
raise DVD.DISKS.NOSUCHDISK
def titleavailable (self, titleid, date):
print "titleavailable not yet used, so no need..."
return [2, 3, 4]
def overduedisks (self, fromdate, todate):
print "overduedisks not yet used, so no need..."
return []
Interfejs RESERVATIONS, podobnie jak RENTALS, udostępnia zestaw operacji do użytku
publicznego oraz zawiera kilka funkcji wykorzystywanych na własny użytek wewnętrzny.
Klasa UTILITIES obejmuje operacje pomocnicze realizowane od strony serwera:
class Utilities:
def getclassifications(self):
logit("Utilities", "Query Classifications")
return ["E", "U", "PG", "12", "15", "18", "XXX"]
def getgenres(self):
logit("Utilities", "Query Genres")
return ["Action", "Education", "Comedy", "Thriller",
"Foreign", "Romance", "Science Fiction"]
def errortext(self, errnumber):
logit("Utilities", "Get Errnum for %d" % errnumber)
# To korzysta ze słowników zdefiniowanych niżej...
try:
errname=ERRNDICT[errnumber]
errmsg=ERRMSGDICT[errname]
except:
errmsg="Unknown error type: %d " % errnumber
return errmsg
def today(self):
return strftime("%Y%m%d", localtime(time()))
Na zakończenie mamy fragment kodu, który ładuje IDL, zestawia połączenia z ORBit, korzystając
z przenośnego adaptera obiektów (POA, skrót od Portable Object Adaptor), a następnie uruchamia
pętlę oczekiwania na zdarzenia orb.run oczekującą na wywołania klientów DVD:
# Najpierw dokonamy rozbioru IDL. Dzięki temu uzyskamy relacje
# między operatorami zdefiniowanymi w IDL a klasami i metodami
# języka Python.
CORBA.load_idl("dvdc.idl")
CORBA.load_idl("logger.idl")
# Inicjacja ORBit i podłączenie do Portable Object Adaptor
orb = CORBA.ORB_init((), CORBA.ORB_ID)
poa = orb.resolve_initial_references("RootPOA")
# Teraz próba uzyskania odwołania do obiektu przechowywanego
# w LOGORB, dzięki czemu połaczymy się z serwerem obsługującym logi
try:
logior = open("./logger.ior").readline()
LOGORB = orb.string_to_object(logior)
print LOGORB.__repo_id
LOGORB.addlog(LOG.loginfo(hostname="knuth", userid="cbbrowne",
application="dvd-server",
messagetype="info",
shortmessage="Start up DVD Server"))
except:
print "Could not open Logger!"
# Ustawienie usługobiorcy, który uruchomi usługi serwera DVD
servant = POA.DVD.FACTORY(Factory())
poa.activate_object(servant)
# Następnie potrzebujemy odniesienia do tego obiektu, aby przekazać je
# do innych procesów, które mogłyby skorzystać z jego usług.
# IOR jest zapisywany w pliku aby udostępnić sposób dostępu
# do serwera.
ref = poa.servant_to_reference(servant)
open("./dvd-server.ior", "w").write(orb.object_to_string(ref))
# Ponieważ odniesienie jest już opublikowane, to uaktywniamy POA,
# a potem zezwalamy, by ORBit zaczął działać, uruchamiając pętlę
poa.the_POAManager.activate()
orb.run()
Odwzorowanie operatorów CORBA dla API w języku C
W tym podrozdziale zajmiemy się kodem włączanym między metody CORBA a istniejący kod
aplikacji dla wypożyczalni DVD napisany w języku C.
Rozpoczniemy od najważniejszego ustawienia:
/*
Implementacja wzorcowego API aplikacji obsługującej wypożyczalnię DVD
Ten plik zawiera definicje funkcji spełniających wymagania
zawarte w API aplikacji obsługującej bazę danych wypożyczalni.
*/
#include
#include
#include
#include
#include "dvd.h"
#include "dvdc.h"
static void open_factory_error (CORBA_Object interface,
char *interfacename);
/* struktury CORBA */
static CORBA_Environment ev;
static CORBA_ORB orb;
static CORBA_Object dvd_client;
/* Ta funkcja zestawia połączenie z ORBit. */
#define IORLOCATION "../server/dvd-server.ior"
void ConnectORB (int argc, char *argv[]) {
FILE *ifp;
char *ior;
charfilebuffer[1024];
/* Start obsługi wyjątków */
CORBA_exception_init(&ev);
/* Żądanie połączenia z ORBit */
orb = CORBA_ORB_init(&args, argv, "orbit-local-orb", &ev);
ifp = fopen(IORLOCATION, "r");
if( ifp == NULL ) {
printf("No dvd-server.ior file!");
exit(-1);
}
fgets(filebuffer,1024,ifp);
printf("%s\n", filebuffer);
ior = g_strdup(filebuffer);
fclose(ifp);
dvd_client = CORBA_ORB_string_to_object(orb, ior, &ev);
if (!dvd_client) {
printf("Cannot bind to IOR: %s\n", ior);
exit(-1);
}
}
Lokalizacja pliku ../server/dvd-server.ior używana przez dvdstore do wyszukiwania
IOR jest tu podana w sposób jawny. Byłoby wskazane, aby użytkownik miał możliwość
wskazania tej lokalizacji.
Pokazuje to szerszy aspekt uruchamiania aplikacji CORBA, co nie jest zadaniem banalnym. Aby
uzyskać działające połączenia, trzeba niektóre z odwołań do obiektów udostępniać za pomocą
mechanizmów nie należących do architektury CORBA.
W systemie z wieloma komputerami trzeba rozesłać podstawowy IOR do wszystkich komputerów-
klientów albo za pomocą współużytkowanego systemu plików wykorzystującego NFS, albo
przekazać go za pomocą FTP i odpowiednio skonfigurowanego programu zarządzającego
CFengine, albo też publikując go na serwerze WWW w określonej lokalizacji. Usługi nazewnicze
architektury CORBA mogą być tu przydatne do zarządzania listami odwołań do obiektów, ale w
celu wyszukania tych usług nadal potrzebne będzie takie odwołanie.
Funkcja Build_Factories zestawia połączenia z interfejsami. Odbywa się to w drugim etapie
uruchamiania aplikacji:
static DVD_MEMBERSHIP membership;
static DVD_TITLING titling;
static DVD_DISKS disks;
static DVD_RENTAL rental;
static DVD_RESERVATIONS reservations;
static DVD_UTILITIES utilities;
static void open_factory_error (CORBA_Object interface, char
*interfacename) {
if (!interface) {
g_error ("Could not bind factory for interface %s", interfacename);
}
}
int Build_Factories (int argc, char *argv[]) {
FILE *ifp;
char *ior;
char filebuffer[1024];
CORBA_exception_init(&ev);
orb = CORBA_ORB_init(&argc, argv, "orbit-local-orb", &ev);
ifp = fopen("../server/dvd-server.ior,"r");
if( ifp == NULL ){
g_error("No dvd-server.ior file!");
exit(-1);
}
fgets(filebuffer,1024,ifp);
ior = g_strdup(filebuffer);
fcloseclient = CORBA_ORB_string_to_object(orb, ior, &ev);
if (!dvd_client) {
g_error("Cannot bind to %s\n", ior);
return 1;
}
/* Logiczne wydaje się wstawienie w tym miejscu jakiejś weryfikacji
bezpieczeństwa, aby udostępniać tylko te interfejsy,
do których dany użytkownik ma odpowiednie uprawnienia.
Dodamy "element kontroli autentyczności" jako argument każdej
z podanych tu funkcji. Egzemplarze interfejsów powinny więc
przestać działać, jeżeli serwer odmówi zwrotu odwołania
do danego interfejsu. */
membership = DVD_FACTORY_MEMBERSHIPFactory( dvd_client, &ev);
open_factory_error(membership, "membership");
titling = DVD_FACTORY_TITLINGFactory( dvd_client, &ev);
open_factory_error(titling, "titling");
disks = DVD_FACTORY_DISKFactory( dvd_client, &ev);
open_factory_error(disks, "disks");
rental = DVD_FACTORY_RENTALFactory( dvd_client, &ev);
open_factory_error(rental, "rental");
reservations = DVD_FACTORY_RESERVATIONSFactory(dvd_client, &ev);
open_factory_eror(reservations, "reservations");
utilities = DVD_FACTORY_UTILITIESFactory( dvd_client, &ev);
open_factory_error(utilities, "utilities");
return 1;
}
Wydaje się sensowne, aby różne funkcje klasy FACTORY dokonywały jakiegoś sprawdzania
autentyczności, by tylko użytkownikowi były udostępniane tylko te interfejsy, do których ma
uprawnienia. Zastosowany tu mechanizm zabezpieczeń polega na przekazywaniu do serwera
elementu kontrolującego autentyczność. Jeżeli uprawnienia użytkownika zdefiniowane w tym
elemencie nie będą wystarczające, to serwer nie zwróci żadnego odwołania do obiektu.
Szczegółowo omówimy tu tylko interfejs MEMBERSHIP. Kod pozostałych jest do niego bardzo
podobny.
Funkcja dvd_member_set używa operacji set z interfejsu MEMBERSHIP:
int dvd_member_set(dvd_store_member *member)
{
DVD_MEMBERSHIP_storemembers *memout:
printf("Set: dvd_store_member\n");
if(member == NULL) {
return DVD_ERR_NULL_POINTER;
} else {
memberid = member->member_id;
memout->memberno = member->member_no;
memout->title = member->title;
memout->fname = member->fname;
memout->lname = member->lname;
memout->houseflatref = member->house_flat_ref;
memout->address1 = member->address1;
memout->address2 = member->address2;
memout->town = member->town;
memout->state = member->state;
memout->phone = member->phone;
memout->zipcode = member->zipcode;
DVD_MEMBERSHIP_set (membership, memout, &ev);
if (ev._major != CORBA_NO_EXCEPTION) {
g_error("Got CORBA exception %d/%s", ev._major,
CORBA_exception_id(&ev));
return DVD_SUCCESS;
}
}
return DVD_SUCCESS;
}
Dane wejściowe o kliencie wpisywane są do struktury używanej przez DVD_MEMBERSHIP_set. W
takim przypadku musimy znać tylko odniesienia do wartości przekazywanych do serwera. Kod
dvdc-stubs.c zawiera funkcje odpowiedzialne za uszeregowanie danych, tworzące ich kopie
używane przez serwer. To działanie funkcji DVD_MEMBERSHIP_set można sprawdzić w pliku
zawierającym zręby.
Funkcja dvd_member_get() realizuje operację odwrotną, pobierając z serwera informację o
kliencie wypożyczalni.
int dvd_member_get(int member_id, dvd_store_member *member)
{
DVD_MEMBERSHIP_storemembers *cmember;
int rc;
if(member == NULL) {
return DVD_ERR_NULL_POINTER;
} else {
DVD_MEMBERSHIP_get (membership, member_id, &cmember, &ev);
}
/* Jeżeli nie można uzyskać informacji o kliencie, może to
oznaczać błąd danych lub to, że klient nie istnieje */
/* PRZYKAADOWY WYJTEK */
switch (ev._major) {
case CORBA_NO_EXCEPTION:
rc = 0;
break;
case CORBA_SYSTEM_EXCEPTION:
g_error("Got CORBA exception %d/%d from DVD_MEMBERSHIP_get",
ev._major, CORBA_exception_value(&ev));
return (int) DVD-ERR_NOT_FOUND;
case CORBA_USER_EXCEPTION:
printf("Error: %s\n", CORBA_exception_id(&ev));
return (int) DVD_ERR_NOT_FOUND;
}
if (rc != 0) /* If we had an exception above... */
return rc;
/* Jeżeli pobrany ID klienta nie jest taki, jak oczekiwano,
może oznaczać to błąd, lub że klient został usunięty */
if(member -> member_id == 0)
return (int) DVD_ERR_NOT_FOUND;
if(member_id != cmember->memberid)
return (int) DVD_ERR_BAD_MEMBER_TABLE;
member->member_id = cmember->memberid;
strncpy(member->member_no, cmember->memberno, 6);
strncpy(member->title, cmember->title, 4);
strncpy(member->fname, cmember->fname, 26);
strncpy(member->lname, cmember->lname, 26);
strncpy(member->house_flat_ref, cmember->houseflatref, 26);
strncpy(member->address1, cmember->address1, 51);
strncpy(member->address2, cmember->address2, 51);
strncpy(member->town, cmember->town, 51);
strncpy(member->state, cmember->state, 3);
strncpy(member->phone, cmember->phone, 31);
strncpy(member->zipcode, cmember->zipcode, 11);
printf("Member: %d %s %s %s %s, member->member_id,
member->member_no, member_fname, member->lname,
member->zipcode);
return DVD_SUCCESS;
}
Funkcja ta pokazuje bardzo obrazowo, że próba podjęcia jakiejś stosunkowo pełnej obsługi
błędów powoduje konieczność pisania znacznie większych fragmentów kodu, niż wymagają tego
same główne operacje.
Funkcje dvd_tilte_create i dvd_title_delete nie zawierają niczego nowego oprócz
dodatkowego kodu, a więc nie będziemy ich tutaj cytować.
Całkiem interesująca jest funkcja dvd_title_search, w której pokazano sposób odbioru na
żądanie struktury danych typu sequence i następnie przekształcania tej struktury do postaci
użytecznej w aplikacji GNOME:
int dvd_member_search(char *lname, int *result_ids[], int *count)
{
DVD_MEMBERSHIP_memberidList *CList;
int *results = NULL, i;
DVD_MEMBERSHIP_search(membership, lname, &CList, &ev);
printf("Found... %d\n", CList->_length);
if (ev._major != CORBA_NO_EXCEPTION) {
g_error("Got CORBA exception %d/%s\n", ev._major,
CORBA_exception_id(&ev));
return DVD_ERR_NOT_FOUND;
}
printf("No exception, got some results... Count: %d\n", CList->_length);
/* Alternatywnie, _były_ tu poprawne wyniki w CList->.. */
results=calloc(CList->_length, sizeof(int));
printf("Allocated memory...\n")'
if (results == NULL) {
return DVD_ERR_NO_MEMORY;
}
printf("Search results: ");
for (i = 0; i < CList->_length; i++) {
printf(" %d %d ", i, CList->_buffer[i]);
results[i] = CList->_buffer[i];
}
CList->_release = TRUE;
*result_ids = results;
*count = CList->_length;
CORBA_free(CList);
return DVD_SUCCESS;
}
W odróżnieniu od poprzedniej wersji napisanej w języku C, w której stosowano powtarzające się
wywołania realloc, wszystko odbywa się tutaj za jednym zamachem. Dzięki temu potrzebne jest
tylko jedno wywołanie calloc przydzielające pamięć dla całego zestawu wyników.
Powyższa funkcja jest jedną z tych, w których szczególnie ważne staje się użycie
CORBA_free(CList) do oczyszczania pamięci po przekazaniu wyników. Operator CORBA o
nazwie DVD_MEMBERSHIP_search będzie przydzielał odpowiednio duży obszar pamięci w
programie-kliencie, jeżeli zażądano wyszukania dużej liczby klientów wypożyczalni.
Montaż całości
Aby uruchomić aplikację obsługującą naszą wypożyczalnię płyt DVD, musimy wykonać kilka
czynności. Najpierw należy zainstalować pakiet ORBit-Python dostępny pod adresem http://orbit-
python.sault.org/. Archiwum tego pakietu rozpakowujemy w wygodnym miejscu, np. w katalogu
/usr/local:
$ tar zxfv orbitpython-0.1.3.tgz
$ cd orbit-python-0.1.3
$ ./configure
$ make all
$ su -
Password:
# make install
Teraz musimy zainstalować naszą aplikację. Plik zródłowy można pobrać ze strony wydawnictwa
Helion (ftp://ftp.helion.pl/przyklady/zaprli.zip) i skompilować interfejs graficzny:
$ cd
$ ./configure
$ make
Uzyskaliśmy teraz skompilowany plik src/dvdstore, który jest właśnie interfejsem graficznym
dla naszej aplikacji.
Jeżeli chcemy zmienić lokalizację pliku IOR, to należy zmodyfikować wartość IORLOCATION
występującą w pliku src/corbaclient.c.
Mając już skompilowane wszystkie elementy, możemy uruchomić aplikację. Wymaga to użycia
trzech programów po kolei.
Najpierw uruchamiamy serwer obsługujący logi, który znajduje się w katalogu
/usr/local/dvdcorba/server:
$ python log-server.py
Done initialization: Proceed!
Serwer ten zablokuje natychmiast terminal, czekając na żądania CORBA, a więc prawdopodobnie
należy uruchomić go w tle.
Potem trzeba uruchomić dvd-server, także w katalogu /usr/local/dvdcorba/server:
$ python dvd-server.py
IDL:LOG/LOG:1.0
Starting up DVD Server
Ponownie nastąpi blokada terminala, ponieważ serwer oczekuje na żądania CORBA, a więc należy
uruchamiać ten serwer jako proces działający w tle.
Można teraz spróbować uruchomić skrypt dvd-tester.py umieszczony w tym samym katalogu
co poprzednie programy. Skrypt ten próbuje wywołać kilka operacji CORBA z naszej aplikacji.
Na zakończenie uruchamiamy interfejs graficzny:
$ cd ../src
$ ./dvdstore
Zastosowanie libgnorba
W pakiecie GNOME znajduje się dodatkowa biblioteka o nazwie libgnorba ułatwiająca tworzenie
aplikacji GNOME, które mają działać jako serwery w architekturze CORBA. Usługi zawarte w tej
bibliotece są następujące:
Funkcja gnome_CORBA_init może być użyta jako zamiennik CORBA_ORB_init. Aączy
ona ze sobą pętle oczekiwania na zdarzenia ORBit i GTK, a więc nie trzeba już zarządzać
nimi oddzielnie i ręcznie przełączać się z jednej pętli do drugiej. Jeżeli serwer CORBA
nie będzie miał interfejsu graficznego, tak jak w aplikacji dla wypożyczalni DVD, to
użycie tej funkcji nie daje zbyt wielkich korzyści. Z drugiej strony, chcąc, aby aplikacje
GNOME takie jak np. Gnumeric były obsługiwane w architekturze CORBA, należy
zadbać o GUI. W takim wypadku użycie wspomnianej funkcji może przynieść korzyści w
przyszłości.
Funkcja ta może również odczytywać właściwości X w celu uzyskania informacji o
dostępnym serwerze nazw, a także może korzystać z elementów X do sprawdzania
uprawnień na podstawowym poziomie. Niestety, jeżeli serwer korzystający z ORBit jest
tak skonfigurowany, aby z tego skorzystać, to wszystkie klienty ORBit i wszystkie
serwery muszą sprawdzać uprawnienia w taki sam sposób. Może to utrudnić współpracę
serwerów z klientami korzystającymi z innych pośredników ORB.
Można to także potraktować jako całkiem dobry pomysł na ograniczenie dostępu tym
użytkownikom, którzy nie spełniają wymagań bezpieczeństwa i korzystają z aplikacji
ORBit na jednym komputerze. Wybór odpowiedniego rozwiązania bardzo mocno zależy
od środowiska, w którym pracują te programy.
Funkcja gnorba_CORBA_init jest odpowiednikiem gnome_CORBA_init, z tym
zastrzeżeniem, że nie zapewnia integracji z pętlą oczekiwania na zdarzenia GTK. Można
z niej korzystać w demonach pozbawionych GUI.
Funkcje goad_server_register i goad_server_unregister są wykorzystywane
do rejestracji i wyrejestrowywania pracującego serwera w tzw. katalogu aktywacji
obiektów GNOME (GOAD, skrót od Gnome Object Activation Directory). Rejestr ten
przechowuje informację o usługach dostępnych dla innych aplikacji.
Funkcja goad_server_list jest stosowana do wyszukiwania w GOAD listy
dostępnych serwerów.
Funkcje goad_server_activate, goad_server_activate_list_get oraz
goad_server_activate_with_id umożliwiają aktywację serwera GOAD. Jeżeli taki
serwer już działa, to przeważnie wysyła do nich komunikat, natomiast jeśli serwer nie
działa, nastąpi uruchomienie jego nowego procesu.
Konfiguracja ORBit do pracy w sieci
W ORBit wprowadzono zabezpieczenia przed nie troszczącymi się o bezpieczeństwo zwykłymi
użytkownikami. Te zabezpieczenia mogą stwarzać pewne niedogodności przy rozpraszaniu
aplikacji na wiele komputerów.
Istnieje plik konfiguracyjny umieszczony prawdopodobnie w /etc/orbitc, który określa rodzaje
połączeń przyjmowanych przez ORBit. Plik ten może zawierać np. następujące wpisy:
ORBIIOPUSock=1
ORBIIOPIPv4=0
ORBIIOPIPv6=0
Każdy z powyższych wierszy odpowiada za inny rodzaj zródła danych wejściowych: 0 oznacza
odrzucenie danego zródła, a 1 oznacza przyjmowanie danych z tego zródła.
Wpis ORBIIOPUSock oznacza, że ORBit zezwala na to, aby dane wejściowe pochodziły z
plikowych gniazd sieciowych systemu UNIX, czyli oznacza po prostu akceptację żądań
pochodzących tylko z procesów lokalnych. Działa to szybko i w miarę bezpiecznie, ale może
doprowadzić programistę do irytacji, jeśli chce zezwolić innym procesom na dostęp do usług.
Aby ORBit mógł obsługiwać wiele komputerów, należy w drugim wierszu wpisać
ORBIIOPIPv4=1.
GOAD rejestr aktywacji obiektów
GNOME
GOAD jest rejestrem używanym do powiązania serwerów z usługami, aby ORBit mógł
uruchamiać serwery CORBA na żądanie. Serwery te nie muszą wówczas niepotrzebnie działać
przez cały czas. Mówiąc ogólnie, rejestr ten jest repozytorium implementacji, które zawiera
informację pozwalającą pośrednikowi ORB zlokalizować i uaktywnić obiekt. Nie jest to w żaden
sposób unormowane, ponieważ konsorcjum OMG uchyla się przed definicją stosownego
standardu, podając jako przyczynę znaczną zmienność stosowanych tu mechanizmów i ich
uzależnienie od sprzętu.
Proces GOAD rozpoczyna się podczas inicjacji sesji GNOME. Następuje wówczas odczyt
konfiguracji z serii plików .gnorba umieszczonych w /etc/CORBA lub /usr/etc/CORBA.
Istnieje także interfejs CORBA umożliwiający uruchamianie GOAD przez aplikacje, lecz tym
zajmiemy się pózniej.
Aby dopisać serwery do rejestru GOAD, należy zmodyfikować zawartość plików .gnorba.
Każdy plik konfiguracyjny może zawierać definicje więcej niż jednego serwera. Na przykład panel
GNOME korzysta z następujących ustawień:
[gnome_panel]
Nazwa używana przez serwer nazw
type=exe
Rodzaj aplikacji, używany przez GOAD w celu
określenia, jak wywołać serwer. Używane są
także inne wartości: shlib dla bibliotek
współużytkowanych i factory oznaczające
ogólne obiekty implementacji Bonobo.
location_info=panel
Lokalizacja serwera używana łącznie z
rodzajem aplikacji w celu określenia, gdzie
można znalezć aplikację.
repo_id=IDL:GNOME/Panel:1.0
Identyfikator w repozytorium używany do
określenia, który IDL z repozytorium
interfejsów ma być używany.
description=GNOME Panel
Opis usługi.
Zwróćmy uwagę na to, że GOAD jest przewidziany do uruchamiania procesów na komputerze
lokalnym i nie zawiera mechanizmów, które można wykorzystać do uruchamiania usług na
oddalonych komputerach.
Zastosowanie CORBA w GNOME
Przy tym wszystkim, co powiedziano tutaj na temat architektury CORBA i jej zastosowania w
coraz bardziej złożonych aplikacjach rozproszonych, dosyć niespodziewany wydaje się fakt, że
większość interfejsów zadeklarowanych do tej pory w GNOME nie jest w rzeczywistości bardzo
skomplikowana.
Problem powstaje wówczas, gdy chcemy mieć bardzo rozbudowane, a więc i skomplikowane
interfejsy IDL. Wtedy zarówno serwery, jak i klienty są także odpowiednio skomplikowane i może
to zmniejszyć elastyczność całego systemu.
Rozpatrzmy jako przykład umiarkowanie złożony IDL wywołujący edytor tekstowy:
module EDITOR {
interface editing {
struct position {
long line;
short position;
};
struct screen {
short width;
short height;
};
struct font {
string family;
short pointsize;
enum { bold, italics, bolditalics, normal };
};
typedef sequence pathcomponents;
void edit_file ( in string host, in pathcomponents,
in position pos, in screen scrn, in font fontinfo ):
};
};
Takie definicje dają w wyniku bardzo dużą elastyczność działania, ponieważ umożliwiają
wywołanie edytora w określonym miejscu pliku i wyświetlanie jego zawartości w określony
sposób.
Niestety, osoby próbujące zastosować ten interfejs z konkretnym edytorem napotykają na
olbrzymie kłopoty, ponieważ muszą utworzyć bardzo długi kod zarządzający wszystkimi
atrybutami wywołania.
Zazwyczaj używa się więc znacznie prostszego interfejsu, którego IDL ma tylko podstawowe
elementy:
interface editing {
void editfile (in string fullfilename);
};
Wiele z interfejsów zadeklarowanych w aplikacjach GNOME ma takie proste elementy, dzięki
czemu można łatwo z nich skorzystać. Proste interfejsy są także częściej używane niż interfejsy
skomplikowane. Oprócz tego prosty interfejs można łatwo rozbudować bez potrzeby naruszania
istniejącego kodu.
W tzw. architekturze złożonego dokumentu (Compound Document Architecture) Bonobo
stosowane są interfejsy nieco bardziej rozbudowane. Istnieją aplikacje, w których planuje się
zastosowanie tego podejścia: Evolution (zintegrowany pakiet narzędzi komunikacyjnych
zawierający program pocztowy, kalendarz i system zarządzania kontaktami), Nautilus (menadżer
plików) oraz Gnumeric (arkusz kalkulacyjny). Gnumeric jest jedną z pierwszych aplikacji, w
której zastosowano zapis poprzez interfejs CORBA. Niezależnie od tego, że do tej pory aplikacja ta
nie jest jeszcze dobrze udokumentowana, to warto zapoznać się z jej kodem zródłowym.
Zaawansowane właściwości CORBA
W dwóch rozdziałach nie jest możliwe przedstawienie niczego więcej oprócz ogólnego przeglądu
architektury CORBA, więc chcąc pokazać przynajmniej jeszcze jeden działający przykład,
musieliśmy bardzo skrótowo omówić kilka najbardziej wydajnych właściwości CORBA.
Zagadnienia te są kluczowe w każdym średnim i większym projekcie.
Dynamiczne wezwania interfejsu
Jest to sposób dynamicznego przyjmowania żądań lub ich generacji stosowany podczas pracy
aplikacji, w którym wykorzystuje się informację o interfejsie odbieraną z repozytorium
implementacji (IR, skrót od Implementation Repository).
W takiej konfiguracji klient nie zna sygnatury obiektu i kieruje zapytania do repozytorium
implementacji, aby określić, jaki zestaw parametrów ma być następnie wysłany i co ma być
przyjęte w odpowiedzi. Oznacza to, że po stronie klienta potrzebny jest pewien mechanizm
interpretacji, który dokonuje przekształceń typów danych znanych klientowi na typy danych
wskazane w repozytorium implementacji.
Odwzorowanie tego w języku C jest dosyć zagmatwane. Znacznie łatwiej jest skorzystać z tej
metody w językach, które dysponują bardziej rozbudowanym mechanizmem dynamicznego
przydzielania danych oraz większą liczbą abstrakcji obiektowych.
Usługi CORBAServices
W ostatnich kilku latach zdefiniowano sporą liczbę usług związanych z abstrakcjami obiektowymi
pod ogólną nazwą CORBAServices. Oznacza to, że uzyskaliśmy rozszerzenia umożliwiające
wykonanie dodatkowych operacji na obiektach. Z usług tych korzysta coraz częściej wiele
różnorodnych aplikacji. Kontrastuje to z omawianymi ostatnio rodzimymi właściwościami
architektury CORBA, które umożliwiają korzystanie z interfejsów tylko w pewnych specyficznych
aplikacjach.
Standardy określane przez konsorcjum OMG są udostępniane w postaci specyfikacji pod adresem
http://cgi.omg.org/library/csindx.html. Należy pamiętać, że nie wszystkie usługi zostały
całkowicie wdrożone, np. ORBit zawiera na razie tylko usługi nazewnicze i obsługę zdarzeń.
Oznacza to, że aplikacje wykorzystujące ORBit mogą korzystać z usług dostępnych u innych
pośredników ORB. Wymaga to zarządzania dostępem do wielu pośredników i powoduje trudności
przy uruchamianiu takiej aplikacji. Przykład równoczesnego działania trzech pośredników ORB
można znalezć w artykule CORBA, GNOME, CMUCL, and other macabre tales dostępnym na
stronie http://www.telent.net/corba/gnome-lisp.html.
Usługi nazewnicze
Usługi nazewnicze (Naming Service) są pod wieloma względami podobne do internetowego
systemu nazw domenowych (DNS). Udostępniany jest tu rejestr, w którym odniesieniom do
obiektów mogą być przyporządkowywane symboliczne nazwy. Jest to prawdopodobnie usługa
najczęściej wykorzystywana, ponieważ właśnie za pomocą niej serwery zazwyczaj rejestrują się
same. Po rejestracji serwera klienty żądające informacji o lokalizacji jakichś usług muszą tylko
odwołać się do usługi nazewniczej. Dzięki takiemu mechanizmowi nie trzeba nieustannie
przekazywać plików zawierających IOR. ORBit zawiera serwer usług nazewniczych.
Usługi wymiany
Usługi wymiany (Trading Service) stanowią sprytne rozszerzenie usług nazewniczych. Serwer
może (podobnie jak w przypadku usług nazewniczych) sam zarejestrować usługę wymiany, w
której przeważnie zamiast dostarczania nazw oferowane są pewne zestawy parametrów
reprezentujących rodzaj i możliwości danej usługi.
Rozpatrzmy to na przykładzie serwera drukarkowego, który w takim przypadku mógłby
rejestrować w usłudze handlowej obsługiwane przez niego parametry druku (prędkość wyrażaną w
stronach na minutę, obsługę kolorów albo ich brak, fizyczną lokalizacje drukarek itp.). Proces
wyszukiwania drukarki polegałby wówczas na wysłaniu żądania zawierającego wymagane
właściwości i wyborze drukarki na podstawie opcji zwróconych przez tę usługę.
Usługi tego rodzaju są przeważnie potrzebne w systemach składających się z bardzo dużej liczby
obiektów i dynamicznie zarządzających kierowaniem żądań. Nie wydaje się, aby można było z
nich skorzystać w aplikacji osobistej pracującej w panelu GNOME, ale ich włączenie ułatwia
przeskalowanie aplikacji do większych rozmiarów. ORBit nie zawiera usług tego rodzaju.
Obsługa zdarzeń
W pośredniku ORBit występuje obsługa zdarzeń (Event Service). Dzięki niej można tworzyć
kolejkę zdarzeń działającą jako swoisty bufor, który uwalnia serwery CORBA od tradycyjnego
korzystania z synchronicznej wymiany komunikatów. Proces, który chciałby przyjmować lub
dostarczać w sposób asynchroniczny żądania dotyczące jakiegoś zdarzenia, wysyła je, korzystając
z funkcji connect_pull_consumer, connect_push_consumer, connect_pull_supplier
oraz connect_push_supplier. W ten sposób jest sygnalizowana gotowość procesu do
przyjmowania lub dostarczania zdarzeń i na tej podstawie są one obsługiwane.
Usługa powiadamiania
Usługa powiadamiania (Notification Service) rozszerza funkcjonowanie obsługi zdarzeń,
zapewniając komputerom sprytne sposoby ich samodzielnej rejestracji i odbiór powiadomień o
interesujących zdarzeniach. Działa to w podobny sposób, jak poszerzenie usługi nazewniczej za
pomocą usługi handlowej.
Usługa powiadamiania jest godna uwagi w systemach, w których istnieją różnorodne zestawy
obiektów wymagające monitorowania skomplikowanych ciągów zdarzeń. W innych systemach
mechanizm ten bywa nazywany publikowaniem i prenumeratą .
ORBit nie zawiera tej usługi i (jak dotychczas) nie korzysta z innych usług tego rodzaju.
Kontrola pracy równoczesnej
Ta usługa udostępnia interfejs do zarządzania równoczesną pracą za pomocą rozproszonych
blokad różnych rodzajów:
Blokady odczytu,
Blokady zapisu,
Modyfikacja blokad: przejście od blokady odczytu do blokady zapisu bez pośredniego
stanu blokady całkowitej,
Blokady celowe: zmienne stopniowanie tego, co ma być zablokowane.
Usługa wymiany komunikatów
Usługa wymiany komunikatów (Messaging Service) ma być zamiennikiem asynchronicznej
wymiany komunikatów wiązanej zwykle z pakietami do kolejkowania komunikatów, takimi jak
MQSeries firmy IBM, MSMQ firmy Microsoft lub TIB firmy Tibco. Dzięki usłudze wymiany
komunikatów możliwe jest skorzystanie z wymienionych programów.
Usługa ta rozwiązuje również problem składniowy przy jednostronnej deklaracji IDL, w której nie
można zagwarantować jakości usługi i która powinna zgodnie ze standardami (bez względu na ich
złą implementację) pomijać w ORB wszystkie jednostronne żądania.
Usługa ta jest szczególnie ważna w aplikacjach finansowych oraz innych wykorzystujących
przetwarzanie wsadowe. Na razie nie wdrożono jej na szerszą skalę, nawet w komercyjnych
pośrednikach ORB.
Usługi związane z czasem
Usługi te (Time Service) mają za zadanie dostarczenie użytkownikowi pracującemu w
architekturze CORBA informacji o bieżącym czasie łącznie z oszacowaniem błędów. Należą do
nich:
Określanie kolejności zajścia zdarzeń,
Generacja zdarzeń na podstawie czasu, np. odmierzania interwałów czasowych i
generacja alarmów,
Obliczanie odstępów czasowych między zdarzeniami.
Nie jest to prawdopodobnie zbyt ważna usługa w systemie Linux, w którym znacznie prościej
można osiągnąć podobne wyniki po instalacji klienta lub serwera NTP, dającego o wiele
dokładniejszą synchronizację czasową niż oferowana w usługach CORBA.
Usługę tę można jednak stosunkowo łatwo wprowadzić i istnieje jej bezpłatna wersja w
pośredniku ORB o nazwie MICO.
Obsługa cyklu życia obiektu
Usługa ta (Life Cycle Service) definiuje zasady tworzenia, usuwania i przenoszenia obiektów do
innych lokalizacji.
Usługi relacyjne
Usługi relacyjne (Relationship Service) tworzą dwa nowe rodzaje obiektów: relacje i role. Pozwala
to na bezpośrednią reprezentację i przenoszenie relacji między obiektami występującymi w
architekturze CORBA.
Usługi utrwalania obiektów
Zadaniem usług z tej grupy (Persistence Service) jest nadawanie obiektom takich wartości, które
pozostaną nawet po wyłączeniu komputera.
Historia tych usług jest dosyć dziwna. Najwcześniejsza z nich o nazwie Persistent Object Service
(POS) stała się nieaktualna jeszcze przed wdrożeniem jej na szerszą skalę. Wynikało to ze
skomplikowanego interfejsu, braku aktualnych i planowanych wdrożeń oraz niezgodności z
zapotrzebowaniem. Nowsza, o nazwie Persistant State Service (PSS), ma zapewniać obiektom
zdolność do zarządzania swoim własnym stanem. Niestety, przy wielkiej różnorodności środków
do przechowywania danych, włączając w to pliki oraz relacyjne i obiektowe bazy danych,
wdrożenie tej usługi jest dosyć skomplikowane. Obecnie nie wdrożono jej jeszcze w żadnym z
pakietów wolnego oprogramowania.
Usługi transakcyjne
Usługi te (Transaction Service) gwarantują poprawność transakcji. Dzięki swojej dwufazowej
składni zatwierdzeń komunikat dotyczący transakcji w architekturze CORBA zapewnia zgodność
systemu przetwarzania transakcji z charakterystyką nazywaną skrótem ACID (od wyjaśnionych
niżej pojęć Atomicity, Consitency, Isolation i Durability)
Atomowość
Wszystkie transakcje są przetwarzane albo do końca z zatwierdzeniem, albo wcale. Oznacza to, że
częściowo przetworzona i przerwana transakcja musi być wycofana.
Zgodność
Wynik transakcji musi spełniać wymagania systemu, nazywane często integralnością bazy danych.
Na przykład, jeżeli pomiędzy kontami bankowymi są przekazywane jakieś kwoty, to przychód i
rozchód musi zostać zatwierdzony podczas jednej operacji na bazie danych tak, aby były
zachowane salda (nawet wówczas, gdy wielokrotne modyfikacje mogą się chwilowo nie
bilansować).
Izolacja
Stany pośrednie nie mogą być widoczne dla innych transakcji. Oznacza to, że np. przy
dokonywaniu przelewów między kontami bankowymi obydwie strony systemu księgowego
(strona winien i strona ma ) muszą się zmieniać równocześnie. Wydaje się więc, że transakcje
odbywają się kolejno po sobie, nawet gdy są wykonywane równocześnie.
Odporność
Po zatwierdzeniu transakcji wprowadzone przez nią zmiany muszą pozostać na trwałe, nawet w
przypadku katastrofalnej awarii. Oprócz tego, jeżeli trwa proces zatwierdzania i wystąpi jakaś
mniej grozna awaria, to musi on zostać zakończony po podjęciu pracy przez system.
Usługi związane z bezpieczeństwem
Usługi związane z bezpieczeństwem (Security Service) tworzą podstawy dla budowy aplikacji,
które będą godne zaufania. Wykorzystuje się w nich szyfrowanie danych w celu ich
zabezpieczenia przed odczytem i wprowadzenia protokołów autoryzacji. Należą do nich:
identyfikacja i autoryzacja użytkowników,
autoryzacja i kontrola dostępu,
kontrola aktywności,
zabezpieczenie kanałów komunikacyjnych,
zabezpieczenie przed możliwością nieprzyznania się do wykonanych operacji,
zarządzanie zabezpieczeniami.
Niestety, zagadnienia bezpieczeństwa nie są sprawą prostą i nie można ich rozwiązać za pomocą
jednej nakładki na aplikację. Tworzą one cały system, o który należy zadbać w każdej fazie
projektu. W idealnym przypadku pośrednik ORB powinien sprawdzać autentyczność żądań,
porównując je z tożsamością żądającego i jego uprawnieniami. Niestety, nie można tu wskazać
ogólnie dostępnego pośrednika, który by działał w taki właśnie sposób. Wspomniany omniORB
ma jeszcze bardzo wiele niedokończonych fragmentów.
Trwają prace nad wdrożeniem szyfrowanej transmisji IIOP w pośredniku ORBit. Ma być tu
zastosowany protokół SSL, co wzmocni tylko bezpieczeństwo transmisji, ale nie wpłynie na inne
elementy systemu bezpieczeństwa.
Usługa uzewnętrzniania
Usługa uzewnętrzniania (Externalization Service) polega na tym, że stan obiektu jest
przekształcany na strumień danych i przenoszony do innej lokalizacji. Jest to potrzebne w takich
sytuacjach, gdy wymagane jest przenoszenie obiektów z jednego komputera na inny podczas
jednego cyklu ich życia.
Obsługa właściwości obiektów
Ta usługa (Object Properties Service) umożliwia dołączanie właściwości do obiektu, dzięki czemu
mogą one być wyszukiwane za pomocą usługi zapytań (Query Service).
Obsługa zapytań o obiekty
Usługa ta (Object Query Service) umożliwia tworzenie zapytań (podobnych do zapytań w języku
SQL) w celu wyszukania właściwości obiektów CORBA.
Obsługa licencjonowania
Usługa licencjonowania (Licensing Service) umożliwia utworzenie zasad kontroli dostępu do
innych usług. Ponieważ zasadniczym elementem korzystania z wolnego oprogramowania takiego
jak GNOME lub ORBit jest brak ograniczeń dostępu, to prawdopodobnie nie będzie ona nigdy
wdrożona w pośredniku ORBit.
Usługi CORBAFacilities
Należą do nich usługi związane z aplikacjami, znane też pod nazwą OMG Domain Technologies
(http://cgi.omg.org/library/cdomain.html). W skład tej grupy wchodzą:
CORBA Finance Specifications (http://cgi.omg.org/library/cfinindx.html) obejmuje
specyfikację księgi głównej, specyfikacje walutowe i zarządzanie stronami umów,
CORBA Manufacturing Specifications (http://cgi.omg.org/library/cmfgindx.html)
zarządzanie produkcją,
CORBAMed Specifications (http://cgi.omg.org/library/cmedindx.html) zawiera
specyfikację interfejsów dla systemów medycznych unormowanych przez OMG,
CORBA Telecoms Specifications (http://cgi.omg.org/library/ctelindx.html)
specyfikacje technologii telekomunikacyjnych (obecnie jest to transmisja głosu i filmów),
CORBA Business zarządzanie zadaniami i sesjami oraz harmonogramami prac,
CORBA E-Commerce opisuje właściwości negocjacji,
CORBA Life Science dotyczy map genetycznych i analiz sekwencji biomolekularnych,
CORBA Transportation opisuje właściwości kontroli ruchu lotniczego.
Usługi z grupy CORBAFacilities zostały wdrożone z różnym stopniem powodzenia, a wiele z nich
znajduje się dopiero w fazie rozwoju. Wspomniane specyfikacje mogą więc zawierać pewne idee i
przykłady wymyślnych zastosowań architektury CORBA, ale nie oznacza to, że podano w nich
użyteczny kod.
Jeżeli planujemy utworzenie aplikacji działającej w którejś z wyżej wymienionych dziedzin, to
opublikowane specyfikacje mogą pomóc w rozwiązywaniu problemów, z którymi już ktoś się
zetknął i je rozwiązał. Nawet wówczas, gdy nie mamy zamiaru tworzyć systemu kierowania
ruchem lotniczym, działającego w architekturze CORBA, to ta dokumentacja umożliwi zapoznanie
się z kilkoma realistycznymi przykładami.
Projektowanie i uruchamianie skalowalnych usług
CORBA
Usługi z grup CORBAServices i CORBAFacilities omówione w poprzednich podrozdziałach
pokazują, że mamy znaczną ilość dodatkowego materiału, który można wykorzystać w
zaawansowanych aplikacjach działających w architekturze CORBA.
Podczas rozwoju architektury CORBA zarysowały się dwa główne nurty:
Tworzenie stosunkowo prostych usług, takich jak serwery komunikatów pomocniczych,
serwery obsługi logów, serwery dokonujące autoryzacji lub serwery usług katalogowych.
Dzięki nim można uzyskać wspomaganie w zarządzaniu systemami, a jeżeli zadbano o
elementarną funkcjonalność umożliwiającą ich szersze użycie rozszerzają one
funkcjonalność pulpitu GNOME i aplikacji.
Tworzenie bardziej zaawansowanych interfejsów i usług przeznaczonych dla
specyficznych aplikacji, których przykładem może być np. aplikacja obsługująca
wypożyczalnię płyt DVD lub interfejsy GNOME Pilot używane w komunikacji z
komputerami podręcznymi z serii PalmPilot. Ten rodzaj interfejsów umożliwia budowę
bardzo zaawansowanych rozproszonych aplikacji, ale nie można go użyć gdzie indziej.
System GNOME Bonobo można potraktować jako trzecią opcję, która dzięki inteligentnej
obsłudze złożonych dokumentów umożliwia wzajemne wbudowywanie aplikacji i dokumentów
GNOME. Pozostało tylko czekać na sukces Bonobo. Ze względu na swój ogólny charakter
wielopoziomowość architektury GNOME stwarza olbrzymie możliwości, których często nie
rozumieją do końca nawet projektanci.
Im bardziej skomplikowany jest system, tym bardziej skomplikowane stają się okoliczności
występowania błędów i zatrzymań. Można to skrótowo pokazać na przykładzie funkcji
dvd_member_delete(int member_id) w naszej wiodącej aplikacji. W tej funkcji kod obsługi
wyjątków jest kilkakrotnie dłuższy niż kod wykonujący użyteczne czynności. Mogłoby się
wydawać, że lepiej będzie rozpocząć pracę od mniej ambitnego, ale prostszego rozwiązania,
przerzucając część zadań na serwer i program obsługujący logi.
Istnieje jeszcze kilka zagadnień, które nie zostały tu wcale omówione, a które stają się ważne przy
tworzeniu skalowalnych aplikacji w architekturze CORBA. Oto one:
Zarządzanie równoczesnym dostępem
Jeżeli przyjrzymy się dokładnie operacjom zdefiniowanym w podanych przykładach, to okaże się,
że wszystkie one dotyczą stosunkowo krótkotrwałych transakcji. Czas odpowiedzi tych transakcji
nie jest więc długi.
Wykonywane były następujące czynności:
Wyszukiwanie w tabeli jednej lub kilku wartości,
Aktualizacja wpisu w bazie danych,
Wysłanie krótkiego komunikatu.
Żadna z nich nie wymaga dużej mocy przetwarzania i dzięki temu żądania mogą być wysyłane po
kolei. Jeśli zatem nadejdzie jakieś żądanie podczas przetwarzania poprzedniego, można odczekać
kilka milisekund na zakończenie jego obsługi.
Jeśli jednak operacje wymagają dużej mocy obliczeniowej, to zarządzanie kolejnością obsługi
staje się poważnym problemem przy projektowaniu programu. Niektóre z rozwiązań
polepszających czas odpowiedzi aplikacji powinny zostać włączone do specyfikacji CORBA 3.0.
Wątki
Jeżeli serwery muszą obsługiwać równocześnie wiele operacji, to potrzebne jest tworzenie wielu
wątków (ang. threads).
Rozdzielanie wielu obiektów
Można żądać, aby fabryka obiektów błyskawicznie tworzyła wiele egzemplarzy obiektów
danego rodzaju, które będą działać na różnych komputerach. W takim przypadku potrzebny będzie
dyspozytor kierujący żądania do obiektu, który najlepiej spełnia wymagania lub jest najmniej
zajęty.
Pokazuje to przydatność usług wymiany: serwery mogą się w niej same zarejestrować, a nawet od
czasu do czasu mogą aktualizować rejestr, wpisując tam informacje o swoim obciążeniu. Klienty
mogą żądać od usługi wymiany podania listy dostępnych serwerów. Jeżeli jakaś grupa obiektów
ma być intensywnie używana, to można utworzyć związanego z nią bezpośrednio dyspozytora,
który dba o równomierne obciążenie serwerów.
Przetwarzanie asynchroniczne i wywołania zwrotne
Jeżeli wykonanie danej operacji zawsze trwa długo, można użyć przetwarzania asynchronicznego.
W takim wypadku klient przygotowuje argumenty w odpowiednim porządku i wysyła je albo do
programu buforującego, albo do odpowiednio zmodyfikowanego prostego serwera obsługującego
komunikaty, który został poprzednio opisany.
Procesy robocze mogą wtedy skierować się na obsługę kolejki komunikatów i wykonywać zadania
wówczas, gdy mają takie możliwości. Może być tu potrzebne zwracanie wyników, jeśli np. klient
zażądał generacji raportu, który ma być wyświetlony na ekranie.
Należy jednak stwierdzić, że takie sekwencyjne porządkowanie wartości zawsze kosztuje
kolejka nie jest obsługiwana za darmo. Jeżeli polepszy to wydajność, to być może warto się
zastanowić nad zastosowaniem takiego mechanizmu. Jeżeli kolejkowanie znacznie obniży liczbę
obiektów, które muszą być utrzymywane w stanie aktywności, to jednak warto je stosować.
Po obliczeniu wyników serwer musi w jakiś sposób je zwrócić. Mamy tu kilka możliwości.
Klient może mieć zarejestrowane w architekturze CORBA wywołanie zwrotne przekazujące
odwołanie do obiektu, a więc mamy tu do czynienia z odwróceniem ról: serwer będzie się
zachowywał jak klient. Przekazuje on operację oto_mamy_wyniki do czegoś, co było jego
klientem, a teraz zachowuje się jak serwer CORBA.
Wadą takiego rozwiązania jest to, że procesy serwera stają się zależne w jakiś sposób od statusu
klienta , co nieco obniża pewność działania systemu. Klient może rejestrować wywołanie
zwrotne w obsłudze zdarzeń CORBA (Event Service). W takim przypadku serwer sygnalizuje tej
usłudze pojawienie się wyników, a klient może je pobrać albo za pomocą mechanizmów
dostępnych w ramach obsługi zdarzeń, albo wysłać do serwera wywołanie
słyszałem_że_masz_wyniki , żądając przesłania wyników.
Ponieważ w takim przypadku za zarządzanie połączeniami jest odpowiedzialny serwer obsługi
zdarzeń, a nie robi tego serwer naszej aplikacji, to pewność działania systemu jest zapewne
większa.
Jako rozwiązanie alternatywne można zastosować jedną z postaci kolejkowania komunikatów
wykorzystującą abstrakcje podobne jak w MQSeries firmy IBM i zwracać wyniki w sposób
asynchroniczny. Serwer porządkuje wówczas wyniki, nadając im postać komunikatu; adresuje je
do klienta i wstawia do kolejki komunikatów. Klient może przeglądać kolejkę w poszukiwaniu
wyników, oczekując na komunikat o ich gotowości lub skorzystać z mechanizmu nasłuchu back-
off signal. Jest to trochę podobne do sposobu unikania blokad w sieci Ethernet (klient
powstrzymuje żądania obsługi i oczekuje przez podany okres przed wznowieniem żądań).
Dozwolone są tu wszystkie opcje. Pewność działania i zdolność do obsługi żądań może się
wprawdzie zmieniać, ale nie ma jednoznacznej odpowiedzi, który sposób jest najlepszy.
Zarządzanie odpornością
Systemy rozproszone narażone są na cały szereg nowych rodzajów awarii, ponieważ występują w
nich dodatkowe elementy w postaci interfejsów sieciowych, pośredników ORB oraz wielu
oddzielnie działających procesów i usług. Można tutaj wymienić następujące przyczyny awarii:
Brak dostępu do pliku IOR spowodowany brakiem uprawnień lub jego usunięciem,
Plik IOR może odwoływać się do usługodawcy , który już nie działa,
Przerwane połączenie z siecią,
Zapora ogniowa może nie przepuszczać wywołań,
Serwer może być zbyt mocno obciążony i zaczyna ignorować żądania,
Serwer może traktować swoją kolejkę roboczą jako pełną i odrzucać żądania z kolejki.
Oprogramowanie odporne na takie czynniki radzi sobie z nimi bez problemów albo przynajmniej
zapewnia narzędzia wspomagające diagnostykę. W takich przypadkach należy pomyśleć o
zastosowaniu narzędzi i metod dostępnych w interfejsach do zarządzania systemem.
Interfejsy do zarządzania systemem
W aplikacji posługującej się całym stadem obiektów szczególnie ważne staje się utworzenie
interfejsów i operatorów monitorujących te obiekty. Elementy te powinny także zapewniać pewien
zakres scentralizowanego sterowania całym systemem. Przykład z serwerem obsługującym
wymianę komunikatów zawiera np. scentralizowany zapis komunikatów o błędach do logu. Jest to
tak proste, że jeśli działa coś innego w architekturze CORBA, to tym bardziej powinien działać
wspomniany zapis komunikatów.
Jeżeli system sygnalizuje, co się dzieje zarówno w serwerach, jak i w klientach, to można sobie
znacznie łatwiej wyobrazić, co się stało tuż przed awarią serwera bazy danych.
Skomplikowane i dokładne interfejsy, w których wszystko ma przebiegać zgodnie z założonym
porządkiem, nie są najlepszym rozwiązaniem. O wiele lepiej pracuje się z kilkoma prostymi
interfejsami, które dają się łatwo ponownie uruchomić.
Wydajność może być czynnikiem zachęcającym do utrzymywania serwerów w stanie pracy przez
możliwie jak najdłuższy okres, ale użyteczne bywa także takie rozwiązanie, w którym elementy
systemu mogą wznawiać działanie bez potrzeby troszczenia się o klienty.
Pouczające jest przyjrzenie się typowemu działaniu systemu Linux, w którym można wyróżnić:
Start jądra systemu,
Aadowanie przez jądro procesu o nazwie init. Za pomocą polecenia ps aex | head
można sprawdzić, że prawdopodobnie identyfikator procesu init jest równy 1,
Proces init jest kluczowym procesem całego systemu jeśli przestaje on działać, to cały
system także się zatrzymuje,
Proces init inicjuje konfigurację sieci, posługując się danymi z pliku /etc/inittab i
uruchamia różne usługi (np. NFS, Sendmail, atd, cron, Apache, lpd, ntp, CFengine i
inne, które powinny być automatycznie uruchomione).
Prawie wszystkie z tych procesów mogą być ponownie uruchomione podczas pracy systemu, na
przykład:
# cd /etc/init.d
# ./nfs-server restart
# cd /etc/init.d
# ./pcmcia lpd start
Fakt, że tak wiele usług systemowych może wznowić działanie bez potrzeby ponownego rozruchu
systemu, jest wielką zaletą systemów UNIX. Można to wykorzystać we własnej pracy. Nasze
aplikacje będą zawierać kilka składników o kluczowym znaczeniu, podobnie jak jądro Linuksa lub
proces init, bez których reszta systemu nie może funkcjonować. W dokładnym projekcie może
się jednak okazać, że nie trzeba będzie traktować danego obiektu jako obiektu o krytycznym
znaczeniu dla pracy całego systemu.
Mogą istnieć jakieś obiekty mające krytyczne znaczenie, podobne do usług zdefiniowanych w
plikach /etc/rc.d lub /etc/ini7.d. Mogłyby one być uruchamiane automatycznie po
rozruchu, zaś jeśli z innych powodów zostaną zatrzymane, to w celu usunięcia problemu
prawdopodobnie potrzebna będzie interwencja operatora systemu.
Jako przykład można podać sytuację, gdy baza danych PostgreSQL wyczerpie zasoby dyskowe.
Wznowienie jej pracy bez wykonania czynności dodatkowych nie będzie zapewne dobrym
pomysłem. Jeśli wśród zestawu obiektów w systemie CORBA znajdują się takie, które zarządzają
danymi w bazie PostgreSQL, to na pewno nie będą one rozwiązywały problemów lepiej niż sam
PostgreSQL.
Na zakończenie wspomnimy jeszcze o obiektach pracujących równolegle z uniksowym getty,
które to obiekty obsługują logowanie się użytkowników. Obiekty tego rodzaju mogą i powinny
być obsługiwane przez fabryki obiektów , które tworzą je na żądanie.
Materiały zródłowe
Ogólnodostępne wersje zródłowe pośredników ORB dla systemu Linux:
ORBit (http://www.labs.redhat.com/orbit/),
ORBit-Python (http://projects.sault.org/orbit-python/),
MICO pośrednik rozpowszechniany na zasadach GNU (http://www.mico.org/),
Architektura CORBA dla czasu rzeczywistego z TAO (ACE ORB),
(http://www.cs.wustl.edu/~schmidt/TAO.html),
omniORB (http://www.uk.research.att.com/omniORB/),
ILU (Inter-Language Unification) z PARC (ftp://ftp.parc.xerox.com/pub/ilu/ilu.html),
JacORB pośrednik dla języka Java rozpowszechniany na zasadach licencji GPL
(http://jacorb.inf.fu-berlin.de//),
Kompilator IDL firmy Sun napisany w języku Java
(http://developer.java.sun.com/developer/earlyAccess/idlc/index.html).
Oprócz tego dostępne są dokumenty opisujące niektóre usługi omówione w tym rozdziale:
Specyfikacje CORBAservices (http://www.omg.org/),
NTP (Network Time Protocol) zaawansowany standard synchronizacji systemów
komputerowych (http://www.ntp.org/).
Podsumowanie
W tym rozdziale pokazaliśmy sposób użycia pośrednika ORBit w pakiecie GNOME. Następnie
omówiliśmy wdrożenie aplikacji obsługującej wypożyczalnię w postaci systemu klient-serwer w
architekturze CORBA. Pokazaliśmy także sposób instalacji elementów umożliwiających
korzystanie z ORBit. Na zakończenie dokonaliśmy krótkiego przeglądu właściwości CORBA i
związanych z tym aspektów projektowania skalowalnych aplikacji.
Wyszukiwarka
Podobne podstrony:
Pisemny egzamin pa pilota wycieczek 21 07 2006
21 07
21 Wiek 2010 07 spis tresci
2015 07 21 Dec nr 293 MON WKU Ostrołęka odznaka pamiątkowa
2015 07 21 Dec nr 294 MON WKU Olsztyn odznaka pamiątkowa
DCSR 07 05 21 p1 Wroblewski Niemiec
07 (21)
2015 07 21 Dec nr 289 MON St Zarz Infrastruktury odznana pamiątkowa
FIDE Trainers Surveys 2013 07 21, Susan Polgar Watch the Queen
21 Wstęp do temperacji 07 10 2009 An introduction to temperament
Dz U 07 21 124
07 Charakteryzowanie budowy pojazdów samochodowych
9 01 07 drzewa binarne
więcej podobnych podstron