Linux Programming Professional, r-21-01, PLP_Rozdział_21


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ł całkiem 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:

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 — czyli 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, 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, oraz dodano 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 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”. 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. Łatwo 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ą niezadłużeni wobec niej. 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 źró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 nadano 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, w celu np. usunięcia starszych zapisów, to serwer nie musi zajmować tą czynnością.

Kod serwera DVD

Serwer DVD został napisany w języku Python, z zastosowaniem odwzorowania ORBit-Python (http://project.ault.org/orbit-python/).

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, to 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óźniej.

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, aby 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 <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <time.h>

#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, aby tylko użytkownikowi były udostępniane tylko 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 uzyskac informacji o kliencie, to może to

oznaczać błąd danych lub to, że klient nie istnieje */

/* PRZYKŁADOWY WYJĄTEK */

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,

to 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://projects.sault.org/orbit-python/. 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 źródłowy dvdcorba.tar.gz można pobrać ze strony wydawnictwa Wrox, a następnie rozpakować go także np. do katalogu /usr/local 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 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.

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 źródła danych wejściowych: 0 oznacza odrzucenie danego źródła, a 1 oznacza przyjmowanie danych z tego źródła.

Wpis ORBIIOPUSock oznacza, że ORBit zezwala na to, aby dane wejściowe pochodziły z plikowych gniazd sieciowych systemu UNIX, czyli po prostu akceptację żądań pochodzących tylko z procesów lokalnych. Działa to szybko i w miarę bezpiecznie, ale może doprowadzić do programistę 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, tak 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óźniej.

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 znaleźć 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<string> 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 źró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, a 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 można 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 znaleźć 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 są oferowane 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:

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:

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 groźna 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:

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ą:

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 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:

System GNOME Bonobo można potraktować jako trzecią opcję, która dzięki inteligentnej obsłudze złożonych dokumentów pozwala wbudowywać w siebie nawzajem aplikacje i dokumenty 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, ale 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:

Żadna z nich nie wymaga dużej mocy przetwarzania i dzięki temu żądania mogą być wysyłane po kolei. Jeśli więc nadejdzie jakieś żądanie podczas przetwarzania poprzedniego, to 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 czasy 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.

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 mogą czasu 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, to 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 lub 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:

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. taki 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 np. 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ć:

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 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 źródłowe

Ogólnodostępne wersje źródłowe pośredników ORB dla systemu Linux:

Oprócz tego dostępne są dokumenty opisujące niektóre usługi omówione w tym rozdziale:

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.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 F:\helion\oryginaly\R-21-t.doc

adres nieaktualny

http://www.inf.fu-berlin.de/~brose/jacorb - nieaktualny, uaktualniłem



Wyszukiwarka