. Programarea Orientata Obiect In Sisteme Distribuite

Programarea orientată obiect în sisteme distribuite. Studiu de caz DCOM

Introducere

Programarea orientată obiect s-a concentrat mulți ani asupra platformelor monoutilzator. Pe măsură ce complexitatea aplicațiilor a crescut și a apărut tehnologia client/server sa simțit nevoia de-a putea partaja obiecte intr-un context multiutilizator

Importanța rețelelor a crescut enorm în ultimi ani, mai ales de când rețeaua internet a înregistrat o dezvoltare explozivă. Calculatoarele trebuie să fie capabile să comunice intre ele într-un mod eficient, și aici e locul unde programarea distribuită își face apariția.

Prin sistem distribuit sau sistem de calcul distribuit înțelegem o colecție de noduri care pot fi: calculatoare, procesoare, procese autonome care sunt interconectate. Fiecare nod dispune de o memorie privată proprie, de asemenea trebuie sa fie capabil să schimbe informații cu restul nodurilor. În unele documentații definirea sistemului distribuit este mai restrictivă, adică existența nodurilor trebuie să fie transparentă față de utilizator.

Necesitatea proiectării unor sisteme distribuite este motivată de câteva aspecte practice, cum ar fi:

Schimbul de informații

Creșterea masivă a cantității de informație și necesitatea de a schimba rapid informații între diferite puncte aflate în locuri geografice depărtate fac necesară conectarea între calculatoare.

Partajarea resurselor

O organizație prefera să cumpere mai multe calculatoare mai ieftine și de puteri rezonabile decât sa cumpere unul singur, mult mai puternic și mai scump.

Siguranța mărită în funcționare

Dacă un sistem de calcul este format dintr-un singur calculator defectarea acestuia face imposibilă utilizarea sistemului de calcul. La proiectarea unui sistem distribuit de calcul se ține seama în foarte mare măsură de siguranța în funcționare a acestuia. Astfel căderea unui nod nu perturbă funcționarea celorlalte.

Performanțe mărite

Prezența mai multor procesoare într-un sistem distribuit face posibilă reducerea timpului de realizare a unui calcul masiv prin împărțirea sarcinilor.

Specializarea nodurilor

Proiectarea unui sistem de calcul autonom cu mai multe funcționalități poate fi dificilă și din motive practice. Această proiectare poate să fie simplificată prin împărțirea sistemului în module, fiecare modul implementând o parte din funcționalități și comunicând cu alte module.

Algoritmii utilizați în sistemele distribuite trebuie să fie corecți, flexibili și eficienți. Dezvoltarea unui algoritm distribuit diferă esențial de dezvoltarea unui algoritm nedistribuit. În ajutorul programatorului pentru a scrie algoritmi distribuiții a venit programarea orientată obiect în sisteme distribuite. În momentul de față exista mai multe tehnologii de programare în sisteme distribuite. Principalele sunt:

CORBA (Common Object Request Broker Architecture). Sistem propus de OMG (Object Management Group)

DCOM (Distributed Component Object Model) care este o extensie a COM (Component Object Model) pentru sisteme distribuite. Sistem creat și produs de Microsoft ca replică la tehnologia CORBA.

RMI (Remote Method Invocation) care este o extensie a limbajului Java, dar care nu a avut succesul (până acum) pe care la avut Java în lumea limbajelor de programare.

DCE (Distributed Computing Environment) elaborat de OSF (Open Software Foundation)

RPC (Remote Procedure Call), standard mai vechi dar pe care se bazează unele din cele enumerate mai sus.

Dintre acestea voi încerca să prezint în continuare DCOM .

DCOM (Distributed Component Object Model)

Introducere

DCOM este o extensie adusă tehnologiei COM (Component Object Model). COM definește modul în care clienții și componentele interacționează.

În sistemele de operare moderne, procesele sunt protejate unul față de celălalt. Un client care vrea să comunice cu o componentă din alt proces nu poate apela direct această componentă, ci trebuie să folosească o tehnică de comunicare interproces oferită de sistemul de operare. Tehnologia COM oferă această facilitate intr-o manieră elegantă și total transparentă: ea interceptează apelurile de la client și le transferă componentei aflate în alt proces.

Majoritatea aplicațiilor distribuite nu sunt dezvoltate de la zero. Infrastructura hardware existentă, programele și componentele deja existente trebuie să fie integrate pentru a reduce efortul și costurile de dezvoltare a unei noi aplicații. DCOM spre exemplu profită de investiția făcută in componentele si uneltele COM. Uriașa piața a componentelor COM facilitează reducerea substanțială a timpului alocat dezvoltării prin integrarea unor soluții și algoritmi standardizați intr-o nouă aplicație. Orice componentă dezvoltată ca parte a unei aplicații distribuite devine un virtual candidat pentru utilizări ulterioare. Organizarea procesului de dezvoltare pe baza paradigmei de componentă permite ridicarea continuă a nivelului de funcționalitate in aplicațiile noi și reutilizarea acestora reduce timpul de lansare pe piață.

Proiectarea pentru COM și DCOM asigură fapul că componentele obținute pot fi folosite acum cât și în viitor.

DCOM este un standard deschis, complet și puternic documentat la toate nivelele. DCOM este bazat pe modelul programării orientate obiect promovând interoperabilitatea soft-ului; aceasta înseamnă că două sau mai multe aplicații sau componente pot coopera una cu alta, chiar dacă au fost scrise de diferiți furnizori în perioade diferite, în limbaje diferite, sau chiar pe mașini diferite cu sisteme de operare diferite. Pentru a suporta aceasta trăsătura, DCOM definește și implementează mecanisme care permit aplicațiilor să se conecteze una cu cealaltă ca obiecte soft. Un obiect soft este o colecție de funcții legate semantic și stări asociate funcțiilor. Cu alte cuvinte, DCOM, asemănător cu sistemul tradițional de funcții API, furnizează operații prin care un client al unui serviciu se poate conecta la mai mulți furnizori de servicii. Dar, odată ce conexiunea s-a făcut, DCOM iese din scenă, lăsând clientul și serverul să comunice direct.

Independența fată de locație

Când se începe dezvoltarea unei aplicații distribuite într-o rețea obișnuită, unele constrângeri de proiectare devin evidente:

Componentele care sunt predispuse să interacționeze mai des ar trebui sa fie aproape una de cealaltă.

Unele componente pot fi rulate numai pe o anume mașină sau intr-o anumită locație.

Componentele mici cresc flexibilitatea repartizării pe mai multe mașini, dar măresc de asemenea și traficul din rețea.

Componentele mari reduc traficul de pe rețea dar reduc și posibilitatea repartizării.

Folosind DCOM aceste constrângeri de proiectare sunt ușor de abordat și evitat, deoarece detaliile legate repartizarea componentelor nu se specifică la nivelul codului sursă. DCOM ascunde complet locația unei componente, indiferent unde s-ar afla, în același proces sau pe o mașină situată la mare distanța. In toate cazurile, modul în care clientul se conectează la componentă și îi apelează metodele este identic. Nu numai că DCOM nu cere nici o modificare la nivel de cod sursă, dar nici nu necesită măcar ca programul să fie recompilat.

Independența de locație a modelului DCOM simplifică major sarcina componentelor aplicațiilor distribuite pentru a obține o performanță globală optimă. Să consideram de exemplu, că anumite componente trebuie plasate pe o anumită mașină sau intr-o anumită locație. Dacă aplicația are un număr mare de componente reduse ca dimensiune, încărcarea rețelei se poate reduce prin plasarea lor în același segment de rețea, pe aceeași mașina sau chiar în același proces. Dacă aplicația este compusă dintr-un număr mic de componente de dimensiuni mari, încărcarea rețelei nu mai constituie o problemă, iar acestea se pot pune pe cele mai rapide mașini disponibile, indiferent unde se află acestea.

Folosindu-se de independența fată de locație oferită de DCOM, aplicația poate plasa componentele predispuse să comunice des pe mașini apropiate una de alta. Chiar dacă un număr relativ mare de componete implementează funcționalitatea unui modul logic complex, acestea pot interacționa eficient una cu cealaltă. Componentele pot rula pe mașina cea mai potrivită raportat la funcționalitatea lor: cele de validare a datelor sau care interacționează cu interfața utilizator aproape sau pe mașina client, cele care descriu reguli de business pe server aproape de baza de date.

Independența fața de limbaj

Un aspect comun de-a lungul etapei de proiectare și implementare a unei aplicații distribuite este alegerea limbajului pentru implementarea unei anumite componente. Această decizie e de obicei un compromis între costurile de dezvoltare, experiența programatorilor cu limbajul în cauză și performanțele obținute. Ca o extensie a COM-ului, DCOM este complet independent de limbaj. Virtual orice limbaj poate fi utilizat pentru a crea componente COM, și aceste componente pot fi folosite din și mai multe limbaje și instrumente. Java, Microsoft Visual C++, Microsoft Visual Basic, Delphi, PowerBuider și Micro Focus COBOL sunt capabile să interacționeze bine cu DCOM.

Folosind independența de limbaj a DCOM-ului, dezvoltatorii aplicațiilor pot alege instrumentele și limbajul cu care sunt cel mai familiarizați. Independența fața de limbaj facilitează realizarea rapidă a unui prototip: componentele pot fi inițial dezvoltate intr-un limbaj de nivel înalt cum ar fi Visual Basic, iar mai târziu reimplementate intr-un alt limbaj ca C++ sau Java, care pot profita mai bine de caracteristici avansate cum ar fi DCOM’s free threading, free multithreading si thread pooling.

Arhitectura DCOM

În centrul DCOM stă mecanismul de stabilere a conexiuni cu componenta și creare de noi instanțe ale obiectului. Din figură se poate observa modul în care clientul obține o instanță la un obiect din sever și pe urmă el lucrează cu acea instanță ca și cum ar fi locală, arhitectura DCOM se ocupă de detalii.

Una dintre cerințele fundamentale ale unui sistem distribuit este abilitatea acestuia de a crea obiecte la distanță. In modelul COM unui obiect i se atașează un GUID (Globally Unique Identifier), care este de fapt un numar pe 128 biti, generat după un procedeu care garantează unicitatea acestuia pe plan mondial, eliminând pericolul apariției de coliziuni. Când un astfel de GUID este folosit pentru a referi o clasă particulară de obiecte se numeste Class ID.

DCOM furnizeză câteva funcții pentru a creea noi instanțe, aceste sunt:

CoCreateInstance(Ex) (<CLSID>…), Creează un pointer la o instanță neinițializată a clasei de obiecte.

CoGetInstanceFromFile. Creează o noua instanța și o inițializează dintr-un fișier.

CoGetInstanceFromIStorage. Creează o noua instanța și o inițializează dintr-un depozit.

CoGetClassObject (<CLSID>…). Returnează un pointer la “class factory object”, care poate fi utilizată pentru a crea una sau mai multe instanțe neinițializate ale clasei de obiecte.

Librăria COM caută librăria sau executabilul în care este încapsulat obiectul cerut în system registry, creează obiectul si întoarce un pointer la o interfață apelantului. În cazul DCOM, mecanismul de creare a obiectelor din librăria COM e extins pentru a putea crea obiecte pe alte mașini. Odată ce numele serverului și CLSID-ul sunt cunoscute, un modul al librăriei COM, anume SCM (Service Control Manager) de pe mașina client se conectează la SCM-ul de pe mașina server și cere crearea obiectului.

DCOM oferă două mecanisme care permit clienților să indice numele serverului unde se cere crearea obiectului, anume:

Ca un parametru de configurare în system registry sau în DCOM Class Store.

Ca un parametru explicit al funcțiilor CoCreateInstanceEx, CoGetInstanceFromFile, CoGetInstanceFromStorage, sau CoGetClassObject

Primul mecanism este util pentru a asigura transparența locației, clienții pot face abstracție de faptul că o componentă rulează local sau pe altă mașină. Făcând numele mașinii pe care rulează serverul parte din configurareae rulează serverul parte din configurarea componentelor serverului de pe mașina client, clientul nu trebuie să se concentreze asupra faptului de a obține și menține locația serverului, tot ce trebuie să știe este CLSID-ul componentei. El apelează CoCreateInstance, si librăria COM in mod transparent creează componenta corectă pe serverul preconfigurat. Chiar și clienți COM scriși înainte de apariția DCOM pot beneficia de acest mecanism de cerere si utilizare a componentelor la distanță.

O observație trebuie totuși făcută asupra acestui proces de creare a obiectelor la distanță. O mașină server nu poate pasa mai departe o cerere de instanțiere a unui obiect unei alte mașini folosind RemoteServerName. Daca mașina X folosește RemoteServerName pentru a indica că obiectul cu CLSID C trebuie creat pe mașina Y, și dacă mașina Y are RemoteServerName specificat pentru CLSID C ca referind mașina Z, obiectele cerute de mașina X pot fi create doar pe mașina Y.

Pentru majoritatea aplicațiilor, specificarea externă a unui nume de server pentru fiecare componentă utilizată e suficient. Această strategie păstrează codul aplicației client independent de configurarea locației componentelor, iar dacă locația serverului se schimbă, se schimbă și registry-ul iar aplicația continuă să funcționeze fără nici o altă intervenție. Numele serverului pe care se instanțiază un obiect la distanța este păstrat in registry intr-o cheie in HKEY_CLASSES_ROOT (HKCR):

[HKEY_CLASSES_ROOȚAPPID\{<appid-guid>}]

"RemoteServerName"="<DNS name>"

Totuși unele aplicați necesită specificarea dinamică a serverului la care se conectează. Exemple de astfel de aplicații ar fi: aplicațiile de tip chat, jocurile in rețea, si uneltele administrative care trebuie să îndeplinească sarcini administrative pe o anumită mașina.

Următoarele fragmente de program exemplifica procesul de creare a obiectelor COM atât local cât și la distanța.

Crearea unui instanțe local

HRESULT hr=CoCreateInstance(

CLSID_AuctionService, // Cere o instanță a clasei CLSID_AuctionService

NULL, // Fără agregare

CLSCTX_SERVER, // Orice tip de server găsit.

IID_IAuction, // Întreabă dacă implementeză interfața IID_IAuction

(void**) &pAuction); // Returnează pointer la interfața.

Crearea instanțelor la obiecte remote, se vor cere doua interfețe.

MULTI_QI mqi[]={

{&IID_IBackupAdmin, NULL , 0},

{&IID_IBackupConfig, NULL, 0} };

// Ne vom conecta la serverul "marius.ubbcluj.ro"

COSERVERINFO srvinfo = {0, L"marius.ubbcluj.ro", NULL, 0};

// Crează obiectul și întreabă dacă implementează cele două interfețe.

HRESULT hr=CoCreateInstanceEx(

CLSID_MyBackupService, // Cere o instanță a clasei CLSID_MyBackupService

NULL, // Fără agregare

CLSCTX_SERVER, // Orice server găsit

&srvinfo, // Conține informații despre serverul aflat la distanță

sizeof(mqi)/sizeof(mqi[0]), // numărul de instanțe pe care vrem să le obținem (2)

&mqi); // structura care indică ID de interfețe și pointeri le

interfețe care se vor obține

if (SUCCEEDED(hr))

{

if (SUCCEEDED(mqi[0].hr))

{

IBackupAdmin* pBackupAdmin=mqi[0].pItf;// Se obține primul pointer la interfață

hr=pBackupAdmin->StartBackup(); //se utilizează

pBackupAdmin->Release(); //se eliberează

}

if (SUCCEEDED(mqi[1].hr))

{

LPWSTR pStatus=NULL;

IBackupConfig* pBackupConfig=mqi[1].pItf; //se obține al doilea pointer la interfață

hr=pBackupConfig->GetCurrentBackupStatus(&pStatus); //se utilizează

pBackupConfig->Release(); // se eliberează

}

}

Apelul metodelor la distanță: Marshaling și Unmarshaling

Când un client vrea să apeleze un obiect aflat în alt spațiu de adrese, trebuie găsită o modalitate ca parametri metodei să fie transmiși corect de la procesul client la procesul obiectului. Clientul punea parametri pe stiva în cazul apelului direct (în același proces), obiectul lua parametri de pe stiva și rezultatul îl punea din nou în stivă. Pentru apelul metodelor la distanță, cineva (de obicei librăria DCOM) trebuie să ia parametri de pe stivă și să-i scrie într-un buffer de memorie pentru a putea fi transmiși mai departe prin rețea. Procesul prin care parametri de pe stivă se scriu în buffer-ul de memorie se numește marshaling. Acest proces nu este trivial, uneori este chiar foarte complex: parametrii pot să fie pointeri la tablouri sau pointeri la structuri. Structuri care la rândul lor pot conține alți pointeri, și alte structuri. În acest caz pentru a apela o metoda codul marshaling trebuie să parcurgă corect ierarhia de pointeri a fiecărui parametru și să transfere toate datele astfel încât să poată apoi reconstrui corect parametrii in procesul server. Corespondentul lui marshaling este unmarshaling, procesul care ia parametri dintr-un buffer de memorie și îi pune pe stivă intr-o configurație identică celei din stiva procesului client. În momentul în care apelul se termină orice valoare care este returnată sau parametrii transmiși prin referința trebuie să fie luați de pe stivă, împachetați, trimiși procesului client unde trebuie despachetați în stiva acestuia.

Tehnologia COM pune la dispoziție mecanisme sofisticate pentru procesele de marshaling si unmarshaling a parametrilor metodelor, care au la bază RPC, această infrastructură e definită ca parte a DCE (Distributed Computing Environment). DCE RPC definește o reprezentare standard a datelor, NDR (Network Data Representation), pentru toate tipurile de date reprezentative. Pentru ca COM să fie capabil să împacheteze și despacheteze corect parametri trebuie să știe semnătura exactă a metodelor, incluzând toate tipurile de date, tipurile membrilor structurilor, și lungimea tuturor tablourilor din lista parametrilor. Această informație este specificată folosind IDL (Interface Definition Language). Fișierele IDL sunt compilate folosind un compilator specializat, de obicei compilatorul Microsoft IDL(MIDL). Acesta generează surse C care conțin codul necesar pentru a performa operațiile de marshaling și unmarshaling pentru interfața descrisă in fișierul IDL. Partea de cod din client care îndeplinește această operație este numită proxy iar cea de pe server (componentă) se numește stub.

O schița a procesului de apel al unei metode este exemplificată de următoarea figură.

Clientul invocă o metodă suportata de obiectul server prin intermediul serverului proxy.

Proxy-ul împachetează parametrii intr-un buffer, pus la dispoziție de sistemul RPC. Apoi acesta gestionează transportul datelor, poate opera si pe memoria partajată dintre două procese de pe aceeași mașina sau transmite datele pe rețea pe o mașină diferită.

Datele ajung in stub-ul interfeței pe care clientul a invocat-o

Stub-ul interfeței despachetează datele

Stub-ul invocă metoda din interfața obiectului server

Valorile de retur sunt transmise înapoi proxy-ului clientului.

Proxy-ul intoarce valorile clientului.

Gestionarea conexiunilor

Conexiunile dintr-o rețea sunt inerent mai fragile decât conexiunile stabilite în interiorul unei mașini. Componentele dintr-o aplicație trebuie să fie notificate dacă un client nu mai este activ sau in cazul unei defecțiuni a rețelei sau a hardware-lui.

DCOM gestionează conexiunile atât la componentele care sunt dedicate unui singur client cât și componente partajate de mai mulți clienți menținând numărul de referințe la fiecare componentă. Când un client stabilește o conexiune cu o componentă, DCOM incrementează acest număr, iar când clientul închide această conexiune, DCOM decrementează numărul. Dacă numărul de referințe ajunge la zero componenta se poate descărca singură.

În multe cazuri, fluxul de informații între o componentă și clienții săi nu este unidirecțional. Componenta trebuie să poată iniția o serie de operații pe client, cum ar fi o notificare că un proces consumator de timp s-a încheiat, modificări in structura datelor pe care utilizatorul le vizualizează, sau următorul mesaj intre-un mediu colaborativ cum ar fi o teleconferință sau un joc multiutilazator. Multe protocoale fac dificilă unui astfel tip de comunicație simetrică. Folosind DCOM orice componentă poate fi atât furnizor cât și consumator de servicii.

Un server trebuie să dispună de un mecanism prin care să poată stabili dacă un client care are o conexiune la una dintre instanțele unui obiect de-al său se termină anormal și nu semnalează faptul că a încheiat utilizarea obiectului. Acest lucru este realizat prin mecanismul de ping. Prima dată când un pointer la un obiect la distanța e obținut, clientul adaugă obiectul la o listă, și periodic trimite un ping la fiecare server din listă. Când serverul primește un ping de la un client presupune că aplicația client funcționează normal. Dacă serverul nu primește un anumit număr de ping-uri el trage concluzia că execuția clientului a fost întreruptă anormal, apoi dealocă pointerul interfeței folosită de clientul respectiv. Partea din server care contorizează ping-urile clienților se numește OXID (Object Exporter Identifier) Resolver.

Apartamente, Tipuri de apartamente,

Creerea unui apartament

Apartamentul nu este nici proces nici thread; oarecum, apartamentele împărtășesc proprietățile ambelor. Fiecare proces care folosește COM are unul sau mai multe apartamente; un apartament este conținut într-un singur proces. Aceasta înseamnă că fiecare proces ce folosește COM are cel puțin un grup de obiecte care partajează cerințe de concurență si reentranță; este posibil ca doua obiecte din același proces sa poată aparține la două apartamente diferite și de aceea pot avea constrângeri diferite de concurență și reentranță. Acest principiu permite bibliotecilor cu modele diferite de concurență să interacționeze cu un singur proces.

Un thread se execută într-un singur apartament la un moment dat. Înainte ca thread-ul să poată folosi COM, el trebuie mai întâi să creeze un apartament. După ce thread-ul a creat apartamentul, COM păstrează informații despre apartament in zona de memorie locală a thread-ului TSL (Thread Local Storage), și această informație rămâne asociată cu thread-ul până când acesta iese din apartament. COM specifică că obiectele pot fi accesate doar din thread-urile aparținând aceluiași apartament. Această înseamnă că dacă thread-ul se execută în același proces cu obiectul, îi poate fi interzis să acceseze obiectul chiar dacă memoria ocupată de obiect este vizibilă si accesibilă. COM definește o constantă de eroare RPC_E_WRONG_THREAD care este returnată în cazul în care obiectul este accesat direct din alt apartament. Este legal, de asemenea, pentru obiectele definite de utilizator să returneze această valoare; oricum, puțini programatori sunt dornici să meargă atât de departe pentru a asigura o folosire corectă a obiectelor.

Tipuri de apartamente.

Versiunea de COM a sistemul de operare Windows NT 4.0 definește două tipuri de apartamente: apartament cu mai multe thread-uri MTA (MultiThreaded Apartament) și apartamente cu un singur thread STA (Single Thread Apartments). Fiecare proces poate avea cel mult un MTA si oricâte STAs. Așa cum ne sugerează și numele, mai multe thread-uri se pot executa în MTA concurent, iar un singur thread se poate executa într-un STA. Mai precis, numai un singur thread se poate executa intr-un STA dat, ceea ce înseamnă nu numai că obiectele care există în STA nu vor fi niciodată accesate concurent dar și că doar un anume thread va executa vreodată metodele obiectului respectiv. Această afinitate a thread-urilor permite implementatorilor de obiecte să memoreze stări intermediare in TLS între apelurile de metode, precum și să dețină blocări care au afinitate față de thread (ex., secțiunile critice și mutex-urile – Win32) de-a lungul invocărilor de metode.

Aceste practici conduc la dezastre când sunt folosite de obiecte bazate pe MTA pentru că nu există garanții în legătură cu thread-ul care va executa orice invocare de metodă dată. Dezavantajul STA este că permite un singur apel de metoda care să se execute la un moment dat, indiferent de numărul de obiecte care aparțin apartamentului. Într-un MTA, thread-urile pot fi alocate dinamic datorită cerinței curente fără legătură cu numărul de obiecte din apartament. Pentru a construi procese server concurente folosind doar apartamente cu un singur thread (STA), sunt necesare apartamente multiple, care pot genera costuri suplimentare. De asemenea, gradul de concurență într-un server STA nu poate depăși numărul total de obiecte din proces. Dacă procesul serverului conține un număr mic de obiecte, atunci doar un număr mic de thread-uri pot fi utilizate, chiar daca fiecare obiect trăiește in propriul STA.

Următoarea versiune de COM va introduce un al treilea tip de apartament, denumit apartament “închiriat” de un thread (RentalThreaded Apartment). La fel ca MTA, RTA permite mai multor thread-uri să intre în apartament.; însă spre deosebire de MTA, când thread-ul intră în RTA, cere apartamentului să fie blocat, ceea ce împiedică alte thread-uri să intre în apartament concurent. Apartamentul este deblocat când thread-ul părăsește RTA-ul, permițând următorului thread să intre. De aceea, un RTA este asemănător cu un MTA cu excepția că toarte apelurile de metode sunt serializate. Aceasta face ca RTA-urile să fie mult mai aproape de obiectele care nu pot fi accesate concurent. Deși apelurile metodelor obiectelor dintr-un STA sunt serializate, obiectele dintr-un RTA diferă față de cele din STA prin faptul că au afinitate la un thread; aceasta înseamnă că, thread-uri arbitrare se pot executa in interiorul unui RTA, nu numai thread-ul care a creat apartamentul. Această lipsă de afinitate a thread-ului face ca obiectele din interiorul unui RTA să fie mult mai flexibile și eficiente decât obiectele dintr-un STA, astfel că fiecare thread poate apela convenabil un obiect simplu prin intrarea in RTA-ul obiectului.

Creerea unui apartament.

Când primul thread este creat de sistemul de operare ca urmare a apelurilor CreatePrecess sau CreateThread, el nu are asociat un apartament. Înainte de a folosi COM, un thread trebuie mai întâi sa intre într-un apartament prin apelul uneia din următoarele trei funcții API:

HRESULT CoInitializeEx(void *pvReserved, DWORD dwFlags);

HRESULT CoInitialize(void *pvReserved);

HRESULT OleInitialize(void *pvReserved);

Pentru toate cele trei funcții primul parametru este rezervat și trebuie să fie NULL. CoInitializeEx poate specifica în ce tip de apartament să intre thread-ul care o apelează. Pentru a intra într-un MTA, al doilea parametru trebuie să aibe valoare COINIT_MULTITHREADED, iar pentru a intra într-un STA trebuie să aibe valoarea COINIT_APRTAMENTTHREADED. Fiecare thread dintr-un proces care apelează CoInitializeEx cu COINIT_MULTITHREADED se execută în același apartament. Fiecare thread care apelează CoInitializeEx cu COINIT_APRTMENTTHREADE se execută într-un apartament privat în care nici un alt thread nu poate intra. CoInitialize nu face altceva decât să apelez funcția CoInitializeEx cu COINIT_APARTMENTTHREADED. OleInitialize apelează CoInitialize iar apoi inițializează câteva subsisteme folosite în aplicațiile OLE, cum ar fi “drag & drop” și “clipboard”, dar este de preferabil să se apeleze CoInitialize sau CoInitializeEx dacă aceste servicii nu vor fi utilizate.

Fiecare dintre cele trei funcții API poate fi apelată de mai multe ori într-un thread; primul apel întoarce S_OK iar următoarele apeluri returnează S_FALSE. Pentru fiecare apel cu succes al funcțiilor CoInitialize sau CoInitializeEx trebuie făcut un apel al funcției CoUninitialize, iar pentru OleInitialize trebuie OleUninitialize din același thread. Odată ce un thread a intrat într-un apartament este ilegal să schimbe apartamentul până când nu iese din apartamentul curent folosind CoUnitialize; abia odată ieșit poate intra în altul.

Obiecte, interfețe și apartamente

Obiectele pun la dispoziția clienților metode pe care aceștia le pot apela. Faptul că un obiect poate avea alte constrângeri de concurență decât cele induse de apartamentul clientului este un detaliu de implementare care nu ar trebui să intereseze clientul.

Din perspectiva programatorului, apartenența la un anumit apartament este un atribut al pointerului la o interfață, nu un atribut al obiectului. Când un pointer la o interfață este returnat de un apel COM API sau de invocarea unei metode, thread-ul care lansează cererea API sau apelează metoda determină cărui apartament aparține pointerul la interfață rezultat. Dacă apelul returnează un pointer la obiectul curent, atunci obiectul însuși rezidă in apartamentul thread-ului apelant. Deseori, obiectul nu poate aparține apartamentului apelatorului, fie pentru că obiectul există deja într-un proces diferit sau pe o altă mașină, fie deoarece cerințele de concurență ale obiectului sunt incompatibile cu apartamentul clientului. In fiecare dintre aceste cazuri clientul primește un pointer la un proxy.

În COM, un proxy este un obiect identic din punct de vedere semantic cu un obiect din alt apartament. Intr-un anumit sens, un proxy reprezintă altă identitate a unui obiect într-un apartament diferit. Un proxy pune la dispoziție același set de interfețe pe care le are și obiectul, transmițând de fapt apelurile mai departe obiectului, asigurându-se ca metodele să se execute întotdeauna în apartamentul obiectului. Independent de faptul că un client primește un pointer la un obiect sau la un proxy, orice pointer la o interfață care este primita de client este valid pentru toate thread-urile din apartamentul apelatorului.

Modele de concurență.

Implementatorii obiectului decid tipul apartamentului în care obiectele se pot executa. Cum am văzut anterior, serverele out-of-process decid explicit tipul apartamentului apelând CoInitializeEx cu un parametru corespunzător. Pentru un proces in-process server este necesară abordarea dintr-un alt unghi, deoarece clienți au apelat deja CoInitializeEx în momentul creării lor. Pentru a permite unui server in-process controlul tipului apartamentului, COM permite fiecărui CLSID să aibă un model de threading propriu, care este disponibil din local registry folosind valoarea ThreadingModel:

[HKCR\CLSID\{96556310-D779-11d0-8C4F-0080C73925BA}ÎnprocServer32]

@="C:\racer.dll"

ThreadingModel="Free"

Fiecare CLSID dintr-un DLL poate avea propriul său ThreadingModel distinct. Sub WindowsNT 4.0, COM permite patru ThreadingModels pentru un CLSID. ThreadingModel=“Both” indică faptul ca avem o clasa care poate executa fie MTA fie STA. ThreadingModel=“Free” indică faptul ca avem o clasă care se poate executa doar MTA. ThreadingModel=“Apartment” indică faptul ca se poate executa doar în STA. Absența valorii ThreadingModel implică rularea doar în STA principal. Principalul STA este definit ca primul STA inițializat de către proces.

Dacă apartamentul clientului este compatibil cu threading model-ul CLSID-ului, atunci toate cererile de activare in-process pentru acel CLSID vor instanția obiectul direct în apartamentul clientului. Aceasta este cel mai eficient scenariu, deoarece nu este necesar nici un proxy intermediar. Dacă apartamentul clientului este incompatibil cu threading model-ul CLSID-ului, atunci cererile de activare in-process pentru acel CLSID forțează COM să instanțieze obiectul într-un apartament distinct, iar proxy-ul va fi întors clientului. În cazul unui client STA care activează clase ThreadingModel=“Free”, obiectul (și instanțele sale) se vor executa în MTA. În cazul clienților MTA, activând clase ThreadingModel=“Apartment” obiectul (și instanțele sale) se vor executa într-un STA creat de COM. În cazul unui client de orice tip care activează clase bazate pe STA, obiectul clasă (și instanțele sale) trebuie sa se execute în principala STA a procesului. Dacă clientul este chiar principalul thread STA, obiectul va fi accesat direct. În caz contrar clientul va primi un proxy. Dacă nu exista nici un STA în proces (adică dacă clientul nu a apelat CoInitializeEx cu flag-ul COINIT_APARTMENTTHREADED), COM va crea un nou STA care se va comporta ca principala STA a procesului.

Implementarea claselor care nu specifică un model de threading poate ignora în general soluțiile de threading, DLL-ul lor fiind accesat doar dintr-un singur thread, principalul thread STA. Implementatorii care marchează clasele ca suportând modele de threading explicite indică implicit că fiecare din apartamente multiple dintr-un proces (ceea ce poate presupune mai multe thread-uri) poate conține fiecare instanțe ale clasei. Din această cauză implementatorul trebuie să protejeze orice resurse care sunt împărțite de mai multe instanțe ale clasei, cum ar fi variabile globale sau statice, împotriva accesului concurent.. Pentru un server COM in-process, variabila globală care păstrează urma timpului de viață a serverului trebuie protejată folosind InterlockedIncrement/InterlockedDecrement. Orice alta stare specifică serverului trebuie protejată de asemenea.

Implementatorii care marchează clasele lor ca ThreadingModel=“Apartment” necesită ca instanțele lor trebuie să fie accesate doar de un singur thread pe durata de viață a obiectului. Aceasta implică faptul că nu este nevoie să protejăm starea unei instanțe, ci doar acele stări care sunt partajate de mai multe instanțe, cum am amintit anterior. Implementatorii care marchează clasele fie cu ThreadingModel=“Free” sau ThreadingModel=“Both” fac ca instanțele clasei să ruleze în MTA, ceea ce înseamnă că o singură instanță a unei clase poate fi accesată concurent. Din această cauză, implementatorii trebuie să protejeze toate resursele care sunt utilizate de o singură instanță împotriva accesului concurent. Aceasta se aplică nu numai variabilelor statice partajate, ci și datelor membre ale instanțelor. Pentru obiectele din heap, aceasta implică ca numărul de referințe la o dată membru să fie protejat folosind InterlockedIncrement/InterlockedDecrement. Orice altă stare specifică clasei trebuie să fie protejată de asemenea.

La o primă privire, este mai puțin evident de ce există ThreadingModel=“Free”, pentru că cerințele pentru a rula în MTA sunt deseori văzute ca un superset al cerințelor pentru compatibilitatea cu STA. Dacă implementatorul unui obiect intenționează să creeze un thread de lucru care va necesita accesul la obiect, este avantajos sa se prevină ca obiectul să fie creat in STA. Aceasta deoarece thread-ul de lucru nu poate intra în STA unde obiectul există și de aceea trebuie să ruleze într-un apartament diferit. Dacă o clasă care este marcată cu ThreadingModel=“Both” și activarea unei cereri este făcută dintr-un thread STA, obiectul va exista în STA.

Aceasta înseamnă că thread-ul de lucru (care va rula în MTA) trebuie să acceseze obiectul folosind apeluri de metode interapartament, care sunt mult mai ineficiente decât invocarea metodelor intraapartament. Oricum, dacă clasa este marcată cu ThreadingModel=“Free”, atunci orice cerere de activare bazată pe STA va forța noua instanță să fie creată în MTA, unde orice thread în lucru poate accesa obiectul direct. Aceasta înseamnă ca atunci când un client bazat pe STA invocă o metodă a obiectului, performanța va fi diminuată, în orice caz, thread-ul în lucru va avea performanță net superioară. Aceasta este un compromis acceptabil în cazul în care obiectul va fi accesat de thread-ul de lucru mai des decât clientul bazat pe STA. Este uneori tentant să relaxăm regulile COM și sa accesăm obiectele direct din mai mult decât un singur apartament, fără a cauza o eroare. În cazul general însă nu este adevărat, mai ales pentru obiectele care utilizează și alte obiecte pentru a-și desfășura activitatea.

Accesul între apartamente

Pentru a permite obiectelor să existe în apartamente care sunt diferite de apartamentul clientului, COM permite interfețelor să fie exportate dintr-un apartament și importat în altul. Pentru a face interfața unui obiect vizibilă în alt apartament este necesar exportul interfeței. Pentru a o face vizibilă în interiorul unui alt apartament este necesar un import. Când o interfață este importată, pointerul la interfață rezultat referă un proxy care poate fi accesat în mod legal de orice thread care importă apartamentul. Deci funcția proxy-ului este să paseze controlul înapoi apartamentului obiectului, asigurându-se că toate apelurile de funcție se execute în apartamentul corect. Trecerea controlului dintr-un apartament intr-altul este referită ca Method remoting și este modul în care toate comunicațiile COM între thread-uri, între procese și între hosturi au loc.

Implicit, Method remoting utilizează protocolul de comunicații COM Object RPM. ORPC este un protocol care este suprapus peste MS-RPC, un derivat al DCE. MS-RPC este un protocol cu un mecanism independent de comunicații care poate fi extins pe un suport de protocol de transport (utilizând librării DLL încărcabile la distanță) și noi pachete de autentificare (via Security Support Provider DLLs). COM utilizează cele mai eficiente protocoale de transport disponibile bazate pe similitudinea tipurilor și importarea sau exportarea apartamentelor. Când comunicarea are loc off-host, COM utilizează User Datagram Protocol (UDP), deși și cele mai comune protocoale de rețea sunt suportate. Când comunicarea are loc local, COM utilizează una din câteva transporturi, fiecare optimizat pentru un anumit tip de apartament.

Împachetarea pointerilor la interfețe.

COM permite pointerilor la interfețe să fie transmiși între limitele apartamentelor folosind tehnica numită împachetare. Împachetarea unui pointer la o interfață transformă pur și simplu pointerul la interfață într-un șir de octeți al cărui conținut identifică în mod unic obiectul și propriul său apartament. Acest byte stream este împachetarea stării unei interfețe care permite oricărui apartament să importe un pointer și sa apeleze metode ale obiectului. Deoarece COM se ocupă exclusiv cu pointeri la interfețe, nu cu obiectele însele, această împachetare nu reprezintă starea obiectului, ci starea serializată a unei referințe independente la un apartament al obiectului. Aceste referințe împachetate ale obiectului conțin informații despre stabilirea legăturii care este complet independentă de starea obiectului.

În mod normal, pointerii la interfețe sunt împachetați implicit ca o parte normală a operațiunilor COM. Când intervine o cerere de activare in-process pentru o clasă cu un model de threading incompatibil, COM împachetează automat interfața dintr-un apartament al unui obiect și despachetează un proxy în apartamentul clientului. Când o cerere de activare out-of-process sau off-host este făcută, COM împachetează de asemenea rezultatul pointerul provenit din apartamentul obiectului și despachetează proxy-ul pentru client. Când o metodă este apelată asupra proxy-ului, orice interfață care este pasată ca parametru al unei metode va fi împachetată pentru a face referințele la obiect disponibile atât în client, cât și în apartamentele obiectului. Ocazional, este necesar să se împacheteze explicit interfețele dintr-un apartament din afara contextului sau din activarea cererii sau a metodei. Pentru a suporta acestea, COM pune la dispoziție o funcție API low-level, CoMarshalInterface, pentru a împacheta pointerii la interfețe în mod explicit.

CoMarshalInterface preia un pointer la o interfață la intrare și scrie o reprezentare serializată a pointerului într-un byte stream pus la dispoziție de către apelator. Acest byte stream poate fi transmis unui alt apartament, unde va fi apelată funcția API CoUnmarshalInterface utilizată pentru a returna un pointer la un pointer la o interfață, semantic echivalent cu obiectul original și care poate fi accesat în apartamentul care executa apelul la CoUnmarshalInterface. Când se apelează CoMarshalInterface, apelatorul trebuie să indice cât de departe apartamentul care realizează importul va fi. COM definește o enumerare pentru a enunța distanța:

typedef enum tagMSHCTX {

MSHCTX_INPROC = 4, // in-process/same host

MSHCTX_LOCAL = 0, // out-of-process/same host

MSHCTX_NOSHAREDMEM = 1, // 16/32 bit/same host

MSHCTX_DIFFERENTMACHINE = 2, // off-host

} MSHCTX;

Este legală specificarea unei distanțe mai mari decât cea necesară, dar este mult mai eficient să se folosească MSHCTX-ul corect când este posibil. CoMarshalInterface permite de asemenea ca apelatorul să specifice semantica împachetării folosind următoarele flag-uri de împachetare:

typedef enum tagMSHLFLAGS {

MSHLFLAGS_NORMAL, // marshal once, unmarshal once

MSHLFLAGS_TABLESTRONG, // marshal once, unmarshal many

MSHLFLAGS_TABLEWEAK, // marshal once, unmarshal many

MSHLFLAGS_NOPING = 4, // suppress dist. garbage collection

} MSHLFLAGS;

Împachetarea normală indică faptul că referința împachetată la obiect trebuie să fie despachetată o singură dată și, daca sunt necesare proxy-uri adiționale, sunt necesare apeluri suplimentare la CoMarshalInterface. Tabela împachetată indică faptul că referința obiectului împachetat trebuie să fie despachetată de 0 sau mai multe ori, fără a fi necesare apeluri adiționale la CoMarshalInterface. Detaliile tabelei de împachetare vor fi descrise mai târziu.

Pentru a permite pointerilor la interfață să fie împachetați pe diverse suporturi, CoMarshalInterface serializează pointerul interfață printr-o interfață pusă la dispoziție de către apelator, de tip IStream. Interfața IStream modelează un device I/O arbitrar și pune la dispoziție metodele Read și Write. CoMarshalInterface apelează metoda Write a interfeței IStream pusă la dispoziție de către apelator, fără a fi interesată unde sunt păstrate informațiile de fapt. Apelatorii pot obține un wrapper peste IStream pentru a accesa memoria direct, folosind funcția CreateStreamOnHGlobal:

HRESULT CreateStreamOnHGlobal(

[in] HGLOBAL hglobal,  // pass null to autoalloc

[in] BOOL bFreeMemoryOnRelease,

[out] IStream **ppStm);

Fiind dată semantica lui IStream, următorul cod:

void UseRawMemoryToPrintString(void) {

void *pv = 0;

// alloc memory

pv = malloc(13);

if (pv != 0) {

// write a string to the underlying memory

memcpy(pv, "Hello, World", 13);

printf((const char*)pv);

// free all resources

free (pv);

}

}

este echivalent acestui cod care utilizează interfața IStream în locul lui memcpy:

void UseStreamToPrintString(void) {

IStream *pStm = 0;

// alloc memory and wrap behind an IStream interface

HRESULT hr = CreateStreamOnHGlobal(0, TRUE, &pStm);

if (SUCCEEDED(hr)) {

// write a string to the underlying memory

hr = pStm->Write("Hello, World", 13, 0);

assert(SUCCEEDED(hr));

HGLOBAL hglobal = 0;

hr = GetHGlobalFromStream(pStm, &hglobal);

assert(SUCCEEDED(hr));

printf((const char*)GlobalLock(hr));

// free all resources

GlobalUnlock(hglobal);

pStm->Release();

}

}

Apelul funcției API GetHGlobalFromStream permite apelatorului să obțină un handle la memoria care a fost alocată în CreateStreamOnHGlobal. Utilizarea lui HGLOBAL nu mai este permisă și nu implică memoria partajată.

Sintaxa unui apel al funcției API CoMarshalInterface este următoarea

HRESULT CoMarshalInterface(

[in] IStream *pStm, // where to write marshaled state

[in] REFIID riid, // type of ptr being marshaled

[in,iid_is(riid)] IUnknown *pItf,// pointer being marshaled

[in] DWORD dwDestCtx, // MSHCTX for destination apt.

[in] void *pvDestCtx, // reserved, must be zero

[in] DWORD dwMshlFlags // normal vs. table marshal

);

Următorul cod împachetează interfața într-un bloc de memorie potrivit pentru a fi transmisă în orice apartament din rețea:

HRESULT WritePtr(IRacer *pRacer, HGLOBAL& rhglobal) {

IStream *pStm = 0; rhglobal = 0;

// alloc and wrap block of memory

HRESULT hr = CreateStreamOnHGlobal(0, FALSE, &pStm);

if (SUCCEEDED(hr)) {

// write marshaled object reference to memory

hr = CoMarshalInterface(pStm, IID_IRacer, pRacer,

MSHCTX_DIFFERENTMACHINE, 0,

MSHLFLAGS_NORMAL);

// extract handle to underlying memory

if (SUCCEEDED(hr))

hr = GetHGlobalFromStream(pStm, &rhglobal);

pStm->Release();

}

return hr;

}

Despachetarea pointerilor la interfețe.

Pentru a decodifica o referință la un obiect împachetat creat de secvența de cod anterioară într-un pointer la interfață valid, apartamentul care o importă va trebui să apeleze funcția API CoUnmarshalInterface:

HRESULT CoUnmarshalInterface(

[in] IStream *pStm, // where to read marshaled state

[in] REFIID riid, // type of ptr being unmarshaled

CoUnmarshalInterface citește pur și simplu un obiect serializat și întoarce un pointer la obiectul original care poate fi accesat în mod legal din apartamentul thread-ului. Daca apartamentul care importa este diferit de apartamentul originar care a exportat interfața, atunci pointerul rezultat va fi un pointer la un proxy. Dacă din anumite motive apelul la CoUnmarshalInterface este făcut din apartamentul originar unde obiectul rezidă, atunci pointerul întors va fi chiar la obiect însuși și nu va mai fi creat nici un proxy. Următorul cod transformă o referință împachetată la obiect într-un interface pointer valid:

HRESULT ReadPtr(HGLOBAL hglobal, IRacer * &rpRacer) {

IStream *pStm = 0; rpRacer = 0;

// wrap block of existing memory passed on input

HRESULT hr = CreateStreamOnHGlobal(hglobal, FALSE, &pStm);

if (SUCCEEDED(hr)) {

// get a pointer to the object that is legal in this apt.

hr = CoUnmarshalInterface(pStm,

IID_IRacer, (void**)&rpRacer,

pStm->Release();

}

return hr;

}

Proxy-ul rezultat va implementa fiecare interfața pe care obiectul o importă, transferând mai departe cererile făcute prin intermediul metodelor în apartamentul obiectului.

Formatul împachetat al pointerilor la interfețe.

Anterior versiunii de COM din Windows NT 4.0, formatul împachetat al referințelor la obiecte a fost nedocumentat. Pentru a permite terților să construiască produse de rețea care folosesc COM, formatul a fost publicat de către Microsoft în 1996 și a apărut pe Internet ca un standard inițial. Antetul unei referințe la un obiect împachetat începe cu o semnătură ("MEOW") și un flag care indică tehnica de împachetare folosită (standard marshaling, custom marshaling etc.). De asemenea apare un IID la interfața conținută în referința. Presupunând că este folosită împachetarea standard, urmează un subheader al referinței la obiect, care indică cât de multe referințe externe reprezintă această împachetare. Numărul acestor referințe externe sunt o parte a protocolului distribuit COM și nu sunt legate în mod direct de numărul de referințe gestionat de obiect prin AddRef/Release. Elementele interesante ale object reference-ului sunt triplul OXID/OID/IPID care identifică în mod unic un pointer la o interfață. Fiecare apartament din rețea este asignat unui singur OXID (Object Exporter Identifier) la momentul creării apartamentului. Acest OXID este utilizat pentru a afla informații despre rețea/IPC când un proxy se conectează pentru prima dată la obiect. OID (Object Identifier) identifică în mod unic un obiect COM în rețea și este folosit de CoUnmarshalInterface pentru a menține legile de identitate pentru proxy-uri. IPID (Interface Pointer Identifier) definește în mod unic un pointer la interfață într-un apartament și este plasat în header-ul fiecărei cereri făcute de un apel care urmează. IPID este folosit pentru a transmite în mod eficient cererile ORPC pointerului la interfață corespunzător în cadrul apartamentului obiectului.

Deși OXID sunt identificatori logici importanți, prin ei înșiși sunt nefolositori, pentru că proxy-urile au nevoie de un mecanism IPC sau un protocol de rețea pentru a contacta apartamentul obiectului. Pentru a transforma OXID în adrese IPC sau în protocol de rețea complet fiecare host care are cunoștință de COM pune la dispoziție un serviciu de rezolvare a OXID (OR). Sub Windows NT 4.0, OR este implementat ca o parte a serviciului RPCSS. Când un apartament este inițializat pentru prima dată, COM alocă un OXID și se înregistrează cu ajutorul serviciului OR local. Aceasta înseamnă că fiecare OR are cunoștință de toate apartamentele care rulează pe sistemul local. OR reține de asemenea porturile IPC pentru fiecare apartament. Când CoUnmarshalInterface necesită contactarea unui nou proxy la apartamentul obiectului, serviciul OR local este consultat pentru a rezolva OXID într-un IPC valid sau o adresă de rețea. Dacă despachetarea intervine pe aceeași mașină pe care se află apartamentul obiectului, atunci OR pur și simplu verifică OXID în tabela sa locală și returnează adresa IPC. Dacă despachetarea intervine pe un host diferit de cel pe care este obiectul, serviciul OR local verifică întâi daca întâlnește OXID în referințele recente, consultând cache-ul OXID-urilor remote rezolvate recent. Dacă OXID nu este găsit în această listă, atunci transmite cererea mai departe serviciului OR de pe mașina pe care rulează obiectul, folosind RPC. Referința la obiect despachetată conține adresa gazdei obiectului într-un format potrivit pentru o varietate de protocoale de rețea, în așa fel încât serviciul OR care dorește să realizeze despachetarea știe unde să transmită cererea mai departe.

Pentru a permite rezoluția distribuită a OXID, serviciul OR de pe fiecare host ascultă pentru cereri remote de rezolvare (pe portul 135 pentru TCP și UDP) pentru fiecare protocol de rețea suportat. Când o cerere de rezolvare a unui OXID este primită din rețea, este consultată tabela OXID locală. Cererea de rezolvare va indica ce tip de protocol de rețea suportă mașina client. Dacă cererea procesului apartamentului nu este încă pornit pentru a folosi unul dintre protocolurile cerute, serviciul OR va contacta librăria COM în apartamentul obiectului folosind IPC local și provocând procesul de creare a obiectului să pornească folosind protocolul cerut. Odată ce aceasta are loc, serviciul OR va nota noua adresă de rețea a apartamentului în tabela OXID locală. După ce noua adresă de rețea este înregistrată, aceasta este returnată serviciului OR care realizează despachetarea, unde este reținută în cache pentru a evita cereri suplimentare de informații în rețea pentru apartamentele utilizate mai des. Deși ar părea ciudat că nu este reținută o adresă de rețea completă în referința împachetată la obiect, nivelul de indirectare permis de ID-urile independente de protocol la apartament (OXID) dau voie proceselor bazate pe COM să amâne rezolvarea acestora până când este necesară. Acest aspect este important în special datorită faptului că COM operează pe o varietate de protocoale (nu doar TCP) și cerând fiecărui proces să asculte pentru cereri utilizând toate protocoalele ar fi foarte ineficient. De fapt, dacă un proces bazat pe COM nu exportă niciodată pointeri clienților off-host, nu va consuma deloc resurse ale rețelei.

Scalabilitatea

Un factor critic al succesului unei aplicații distribuite este abilitatea acesteia de ași mări odată cu numărul de utilizatori cantitatea de date și funcționalitatea. Aplicația trebuie sa fie mică și rapidă unde cererile sunt minimale, dar trebuie să fie capabilă a gestiona cereri auxiliare fără ași diminua performanțele sau siguranța in funcționare.

Multiprocesarea simetrică

DCOM profită de suportul de multiprocesare pus la dispoziție de Windows NT. Pentru aplicațiile care folosesc un model freethreading, DCOM gestionează un set de fire de execuție reutilizabile. Pe o mașină multiprocesor acest set este optimizat in funcție de numărul de procesoare disponibile. Prea multe threaduri conduc la prea multe schimbări de context pe când prea puține pot lăsa unele procesoare nefolosite. DCOM protejează programatorul de detaliile gestionării thread-urilor și oferă performanța optimă pe care doar dezvoltarea costisitoare a unui manager de thread-uri o poate oferii. Aplicațiile DCOM pot ușor migra de pe o mașina monoprocesor pe un sistem multiprocesor, profitând de suportul Windows NT pentru multiprocesarea simetrică.

Repartizarea flexibilă a componentelor

Pe măsură ce încărcarea unei aplicații creste, chiar și o mașină multiprocesor foarte rapidă poate ajunge în situația să nu mai poată onora cererile.
Independența fată de locație a simplifică distribuirea componentelor pe alte calculatoare, oferind o cale mai ușoară și mai puțin costisitoare de obținere a scalabilității. Distribuirea pe diferite mașini este mai ușoară pentru componentele stateless și pentru acele care nu își partajează starea cu alte componente. Pentru componente ca acestea e posibilă rularea mai multor copii pe mașini diferite. Î Încărcarea aplicației poate fi distribuit in mod egal pe mașinile disponibile, și pot fi luate în calcul criterii cum ar fi performanțele mașinii, sau solicitarea curentă. Aceeași componentă poate fi dinamic repartizată, fără nici un efort de programare sau chiar fără recompilare. Tot ce trebuie făcut este modificarea regitry-ului, a fișierului sistem sau a bazei de date unde sunt păstrate informațiile despre locația componentei.

Un exemplu de folosire a acestei tehnici ar fi următorul: să considerăm că o organizație cu birouri in diferite zone, cum ar fi New York, Londra, San Francisco si Sydney își instalează componentele pe servere. Două sute de utilizatori accesează simultan cincizeci de componente de business pe un server și obțin performanțele așteptate. Pe măsură ce noi aplicații sunt livrate utilizatorilor, aplicații care folosesc componentele de business existente sau altele noi, încărcarea de pe server crește la șase sute de utilizatori, și numărul componentelor de business crește la șapte zeci. Cu aceste noi aplicații și utilizatori, timpul de răspuns al serverului devine inacceptabil în orele de vârf. Administratorul adaugă un al doilea server și repartizează exclusiv pe acesta treizeci de componente. Douăzeci de componente rămân exclusiv pe vechiul server, iar celelalte douăzeci rămase sunt rulate pe ambele mașini.

Repartizarea paralelă a componentelor

Multe aplicații comerciale au una sau mai multe componente critice, implicate in majoritatea operațiilor. Acestea pot fi componente care implementează reguli de business sau folosesc baze de date care trebuie să fie accesate serial pentru a respecta principiul primul venit primul servit. Aceste tipuri de componente nu pot fi duplicate, deoarece principalul lor rol este de a asigura puncte de sincronizare utilizatorilor aplicației. Pentru a spori performanța globală a unei aplicații distribuite aceste componente care canalizează fluxul de date trebuie repartizate pe un server dedicat și puternic. Din nou, DCOM intră în scenă lăsând programatorul să izoleze aceste componente în procesul de proiectare, repartizând multiple componente pe o singură mașina inițial și mutând componentele critice pe mașini dedicate mai târziu. Figura următoare ilustrează izolarea componentelor critice.

Pentru aceste componente, care au un impact major asupra performanțelor aplicației (bottlenecks components), DCOM poate face task-urile globale mult mai rapide. Asemenea componente sunt de obicei secvențe ale unui proces, cum ar fi operațiunile de cumpărare, comenzi de vânzare în comerțul electronic, și aceste cereri trebuie procesate în ordinea in care sau primit. O soluție e de a descompune task-ul în mai multe componente și de a repartiza aceste componete fiecare pe o altă mașina. Efectul obținut e similar cu pipelining-ul utilizat in microprocesoarele moderne: vine prima cerere, prima componentă o procesează (face de exemplu o verificare a consistenței) și pasează cererea următoarei componente (care de exemplu ar putea modifica baza de date ). De îndată ce prima componentă a pasat cererea celei de a doua, devine disponibilă să proceseze următoarea cerere. Ca efect, obținem doua mașini care lucrează in paralel la multiple cereri, si ordinea de procesare a cererilor e garantată a fi ordine în care au sosit. Aceleași rezultate se pot obține folosind DCOM pe o singură mașina, multiple componente putând rula in thread-uri diferite sau în procese diferite. Această abordare simplifică problema scalabilității mai târziu, când thread-urile pot fi repartizate pe o mașină multiprocesor sau procesele pot fi mutate pe alte mașini. Următoarea figură ilustrează un model de pipelining.

Independența fată de locație În modelul de programare DCOM face posibilă schimbarea schemelor de repartizare pe măsura ce aplicația crește: o singură mașină server poate găzdui inițial toate componentele, conectându-le in mod foarte eficient ca in-process servere. Efectiv, aplicația se comportă ca una monolitică. Pe măsură ce cerințele cresc, alte mașini pot fi adăugate și componentele pot fi repartizate pe toate acestea fără modificări.

Performanța modelului DCOM

Scalabilitatea nu e de prea mare folos dacă performanța obținută inițial nu este satisfăcătoare. Este întotdeauna bine a se știi că un hardware mai rapid și mai mare pot duce o aplicație în următoarea sa fază de evoluție.

Referitor la DCOM se poate naște următoarea întrebare, oare facilitatea de a de a rula și folosi o componentă aflată în alt colț al lumii nu diminuează eficiența folosirii acesteia în același proces cu clientul ?

În cazul modelului COM sau DCOM, clientul nu are acces la obiectul server în sine, dar clientul nu este niciodată separat de server de o componentă sistem decât dacă acest lucru este absolut necesar. Această transparența este obținută printr-un mecanism simplu: singurul mod în care un client poate conversa cu o componentă este prin invocarea metodelor acesteia. Clientul obține adresa acestor metode dintr-o tabelă cu adresele metodelor (vtable). Când clientul vrea să apeleze o metodă a unei componente, obține adresa acesteia și o apelează direct. Singurul overhead generat de modelul de programarea COM fața de un apel de funcție tradițional C sau în limbajul de asamblare este simpla regăsire a adresei metodei (apelul direct versus apelul indirect). Dacă componenta este una in-process rulând în același thread ca și clientul, apelurile de metode se transmit direct acesteia. Nici o porțiune de cod COM sau sistem nu e executată, COM doar definește un standard pentru a impune structura tabelei cu adresele metodelor.

Dar ce se întâmplă când clientul și componenta nu sunt așa de aproape, de exemplu în alt thread, în alt proces sau pe o altă mașină? COM își pune propria infrastructură de apeluri RPC în tabela de adrese a metodelor și apoi împachetează fiecare apel de metodă intr-un buffer, care este transmis în contextul componentei, despachetat, și apoi după executarea metodei procedeul se reia in sens invers. COM pune la dispoziție mecanism RPC orientat obiect.

Cât de rapid este acest mecanism? Sunt câteva aspecte de performanță care trebuie analizate, cum ar fi:

Cat de rapid este apelul unei metode vide (fără parametrii de intrare sau ieșire ) ?

Cât de rapide sunt apelurile de metode de tipul care se întâlnesc în practică care trimit și primesc date?

Care este intervalul în care un pachet pe rețea transmis de client la server se întoarce ?

Tabelul următor conține o serie de teste care dau o idee asupra performanțelor DCOM.

Prima coloana reprezintă un apel al unei metode care ia si returnează un întreg de patru octeți. Ultimele două coloane reprezintă rezultatul apelului unei metode apropiate de cele din practică, care ia parametrii cu o lungime totală de cincizeci de octeți.

Repartizarea încărcării

Cu cât o aplicație distribuită înregistrează un succes mai mare cu atât încărcarea la care sunt supuse componentele acesteia de numărul în creștere de utilizatori se amplifică. Deseori, chiar si puterea de calcul a unui supercalculator nu este îndeajuns pentru a ține pasul cu cerințele utilizatorilor. O soluție de dorit într-o asemenea situație este distribuirea cererilor pe mai multe mașini server.

Balansarea încărcări (load balancing) este un termen care încorporează un întreg set de tehnici prin care se poate realiza acest obiectiv. DCOM nu oferă in mod transparent un mecanism de balansare a încărcări in adevăratul sens al cuvântului dar facilitează implementarea diferitelor modele de balansare a încărcării.

Balansarea încărcării static

O metoda de a asigura balansarea încărcării este de a asocia in mod permanent anumiți utilizatori cu anumite servere rulând aceeași aplicație. Deoarece această asociere nu se schimbă in funcție de condițiile din rețea sau alți factori, acest procedeu se numește static.

Aplicațiile care au la bază DCOM pot fi ușor configurate să folosească un anumit server, acest fapt implicând doar schimbarea unei întrări in registry. Bazându-se pe acest aspect diferite instrumente de configurare pot folosi funcțiile Win32 de accesare de la distanță a registry-ului pentru a schimba aceste opțiuni pe fiecare client. Pe sistemul de operare Windows2000, DCOM poate folosi „extended directory service” pentru a implementa o clasă distribuită de stocare, care face posibilă centralizarea acestor schimbări de configurație.

Pe Windows NT 4.0, aplicațiile pot folosi o tehnică simplă pentru a obține același rezultat. O abordare a acestei probleme este de a păstra numele serverului intr-o locație cunoscută de toți clienții, cum ar fi o baza de date sau un simplu fișier. Componenta client obține numele serverului de câte ori vrea să stabilească o conexiune. Schimbând conținutul bazei de date sau a fișierului se asociază clienți sau grupuri albitrare dintre aceștia cu alte servere simultan.

O abordare mult mai flexibilă este utilizarea unei componente care are rolul de-a distribui apelurile clienților. Această componentă trebuie sa se afle pe o mașină cunoscută de către toți clienții. Aplicația client se conectează mai întâi la această componentă, și cere accesul la serviciul de care are nevoie. Această componentă poate folosi mecanismul de securitate al DCOM-ului pentru a identifica utilizatorul care a făcut respectiva cerere si apoi să aleagă un server in funcție de identitatea acestuia. In loc sa-i întoarcă doar numele serverului această componentă poate stabili o conexiune cu acel server, pe care să o paseze clientului, apoi DCOM conectează in mod transparent clientul și serverul, si componenta care a realizat inițial conexiunea iasă complet din scenă. Este de asemenea posibil de a ascunde acest mecanism complet clientului implementând un class-factory propriu în componenta care realizează conexiunea cu serverul.

Pe măsură ce numărul de utilizatori crește, administratorul poate schimba componenta să aleagă in mod transparent diferite servere pentru diferiți utilizatori. Partea client rămâne in întregime neschimbată, si aplicația poate migra de la un model cu administrare descentralizată la unul centralizat. Independență față de locație și suportul eficient pentru redirectarea conexiunilor fac acest tip de abordare foarte flexibil.

Balansarea încărcări dinamic

Balansarea încărcări static este o tehnică care se pretează în situațiile când avem de-a face creșterea cerințelor utilizatorilor, dar necesită intervenția unui administrator si funcționează bine doar pentru nivele de încărcare previzibile.

Ideea componentei care distribuie clienții la diferite severe prestabilite, poate fi folosită pentru a furniza un tip mult mai inteligent de balansare a nivelului de încărcare. In loc sa facă alegerea serverului pe baza ID-ului utilizatorului, această componentă poate folosi informați despre nivelul de solicitare al serverelor, topografia rețelei dintre client si serverele disponibile și statisticile despre cererile anterioare ale respectivului utilizator. De fiecare dată când un client vrea face o cerere de conexiune la un serviciu, această componentă, îl poate pune în legătură cu cel mai potrivit server disponibil la acel moment. Din nou, din perspectiva clientului toate acestea se produc in mod transparent. Această metodă este numita balansarea încărcării dinamic.

Pentru unele aplicații, balansarea dinamică a încărcării în momentul conectări se poate dovedi a nu fi suficient. Clienți pot în mod obișnuit să nu se deconecteze pentru perioade lungi de timp, sau cererile pot fi onorate în mod inegal. DCOM nu furnizează de la sine suport pentru reconectarea dinamică și distribuirea apelurilor de metode deoarece o asemenea facilitate ar necesita cunoașterea unor detalii intime dintre interacțiunea dintre client și componente. Componenta păstrează de obicei informații specifice stării între apelul metodelor. Dacă DCOM ar reconecta clientul la o altă componentă sau la o altă mașină, aceste informații sar pierde.

Totuși DCOM facilitează dezvoltatorilor de aplicații introducerea explicită a acestei logici în protocolul client componentă. Clientul și componenta pot avea interfețe speciale pentru a putea decide când o conexiune poate fi mutată într-un mod nedeunător pe o altă mașină fără pierderea vre-unei informații de stare critică. In acel moment fie clientul fie componenta pot iniția o reconexiune la o altă componentă sau la o altă mașină înainte de invocaea următoarei metode. DCOM oferă aceste posibilități de extindere a protocolului, necesare pentru a implementa aceste caracteristici specifice protocolului unei anumite aplicații.

DCOM permite de asemenea inserarea de cod specific componentei in procesul client. Astfel când clientul invocă o metodă, o componenta proxy pusă la dispoziție de adevărata componentă interceptează acest apel în procesul clientului și-l redirectează spre server. Clientul nu trebuie să conștientizeze acest aspect în nici un fel.

Prin intermediul acestei facilități, DCOM face posibilă dezvoltarea unor infrastructuri generice care să se ocupe de problema balansării încărcării și redirectarea dinamică a apelurilor de metode. O astfel de infrastructură poate defini un set de interfețe care pot decide prezența sau absența unor informații de stare intre componenta și client. Oricând clientul sau componenta detectează absența informațiilor de stare, poate reconecta dinamic clientul la un alt server, mai puțin solicitat.

MTS (Microsoft Transaction Server) folosește acest mecanism de extindere a modelului DCOM. Cerând un set simplu de interfețe standardizate care oferă informații de stare, MTS poate obține informațiile necesare pentru a oferi un model sofisticat de balansare a încărcării. În acest nou model de programare interacțiunile dintre client și componentă sunt grupate in tranzacții care indică când o secvență de apeluri de metode a ajuns la punctul în care nici o informație de stare nu mai unește cele două componente.

DCOM pune la dispoziție infrastructură puternică pentru realizarea balansării dinamice a încărcării, componente de genul celor descrise anterior pentru a implementa dinamic și transparent repartizarea serverelor la momentul conexiunii. Alte metode sofisticate pentru redirectarea apelurilor unor metode pe alte servere poate fi de asemenea implementate, dar acest lucru necesită detalii interne dintre interacțiunea client server. MTS care este construit în întregime pe baza DCOM-ului, oferă un model standardizat de programare care transmite aceste informații de natura internă a aplicației infrastructurii MTS, care poate îndeplini operații complexe de reconfigurare statică și dinamică precum și balansarea încărcării.

Securitatea în cadrul modelului DCOM

În prezent, crearea aplicațiilor distribuite generează o serie de probleme dezvoltatorilor, una dintre probleme fiind cea a securității componentelor, a obiectelor utilizate de aplicație.

Se pun următoarele întrebări:

Cine poate accesa obiectele?

Ce operații îi sunt permise unui obiect să execute?

Cum poate un administrator să administreze accesul la obiecte?

Ce informație de protecție trebuie să conțină mesajele dacă traversează rețeaua?

Platformele de azi oferă diverse mecanisme de securitate pentru a fi utilizate după diferite scenarii sau pentru interoperabilitatea cu alte platforme. DCOM și RPC au fost construite în așa fel încât se pot simultan acomoda cu aceste mecanisme ale securității. Toate aceste facilități asigură: identificarea unui cont de utilizator, identificarea printr-o parolă sau cheie privată și o autoritate centralizată care administrează parolele sau cheile.

Dacă cineva vrea să acceseze o resursă protejată va transmite parola sa resursei care o autentifică și care interoghează sistemul de securitate în scopul autorizării clientului, acesta apelând nivelele inferioare ale protocoalelor în cadrul acestei interacțiuni.

În DCOM se disting patru aspecte (politici) fundamentale ale securității:

Securitatea accesului – cu rol de a permite/interzice apelul unui obiect;

Securitatea generării – cu rol de a permite/interzice crearea unui nou obiect într-un nou proces;

Identitatea – care se referă la securitatea obiectului în sine;

Politica conexiunilor – care se referă la integritatea mesajelor care pot fi alterate, "izolarea" mesajelor pentru a nu fi interceptate de alte persoane, autentificarea celui ce le apelează .

Securitatea accesului: protecția obiectului

Aplicațiile distribuite trebuie să protejeze obiectele de accesul neautorizat, existând cazuri când numai utilizatorii autorizați se pot conecta la un obiect. În alte cazuri, utilizatorii neautorizați se pot conecta la un obiect, dar au acces limitat. Implementarea actuală a DCOM asigură controlul accesului la nivel de proces. Componentele existente se pot integra într-o aplicație distribuită prin configurarea politicilor de securitate, iar noile componente pot fi dezvoltate fără specificații explicite ale securității , ca parte a aplicației. Dacă o aplicație solicită mai multă flexibilitate, obiectele pot face validări intermediare, per-obiect, per-metode sau pe parametrii metodei. Obiectele pot efectua diferite alte acțiuni în funcție de cine le apelează, ce drepturi are apelantul sau cărui grup aparține.

Securitatea generării: protecția mașinii server

O altă cerință a infrastructurii sistemelor distribuite este de a menține controlul asupra celor care au drepturi de a crea obiectele. Pe o mașină, în care toate obiectele COM sunt accesibile prin DCOM, este important să împiedicăm utilizatorii neautorizați să creeze instanțe la aceste obiecte. În acest scop, protecția a fost executată fără implicarea obiectului, actul de lansare al procesului server putând fi considerat o posibilitate de anihilare a securității și de acces în sistem. Bibliotecile COM având rolul executării validărilor în momentul activării obiectului, validează operațiunea de activare doar dacă utilizatorul are drepturi suficiente pe baza unor informații regăsite în regiștrii, care sunt deci externe obiectului.

Identitatea: controlul obiectelor

Un alt aspect al securității distribuite este autocontrolul obiectelor. Când un obiect execută operații în numele unor operatori arbitrari este necesar, să își limiteze singur capacitățile. Una din aceste capacități face ca obiectele să își asume identitatea apelantului. Când un obiect accesează rețeaua sau un fișier, activitatea sa este limitată la dreptul apelantului. În cazul aplicațiilor cu un număr mare de utilizatori, asumarea de către obiect a identității apelantului poate duce uneori la probleme. Toate resursele care sunt utilizate de un obiect trebuie să fie configurate așa încât să aibă drepturi asemeni clientului. Dacă drepturile sunt prea restrictive, unele operații vor eșua, iar dacă sunt prea generoase, pot să apară violări ale securității. Acest lucru poate fi simplificat prin utilizarea grupelor de utilizatori sau prin rularea obiectului cu o securitate dedicată, independent de cea a apelantului. Alte aplicații nu determină identitatea apelantului cum ar fi de exemplu aplicațiile Internet, caz în care aplicațiile pot fi utilizate de către orice utilizator iar obiectele activate de aplicație prezintă un sistem de securitate care se activează doar în momentul accesării resurselor.

Politica Conexiunilor: protecția datelor

Dacă distanța dintre apelant și obiect devine mare, datele care sunt transportate ca parte a invocării metodelor pot fi alterate sau interceptate de o a treia parte. DCOM permite apelanților și obiectelor să determine cum sunt securizate datele pe conexiune, oferind posibilitatea configurării dinamice a securității. Integritatea fizică a datelor este uzual garantată de nivelele joase ale rețelei. Dacă o eroare de rețea alterează datele, nivelul transport automat o detectează și le retransmite. Pentru securitatea aplicațiilor distribuite, integritatea datelor înseamnă abilitatea de a determina care sunt datele originale care provin de la un apelant legitim și unde au fost alterate logic. Procesul de autentificare a apelantului poate fi costisitor depinzând de protocoalele de securitate și de nivelul securității. DCOM lasă aplicațiile să aleagă unde se face autentificarea și cum va decurge acest proces. Modelul oferă două modalități de protecție a datelor: integritatea și respectiv izolarea datelor.

Clienții sau obiectele pot cere ca datele să fie transferate cu informații adiționale care să asigure integritatea datelor. Dacă o porțiune a datelor este alterată în drumul dintre client și obiect, DCOM detectează și automat informează apelantul.

Integritatea datelor se referă la faptul că fiecare pachet de date trebuie să conțină informații de autentificare. Ea nu se referă doar la interceptarea și citirea datelor în transfer. Clienții și obiectele pot cere ca datele să fie criptate în transfer, lucru care implică autentificarea informației per-pachet. Ambele modalități, "izolarea" și integritatea, cer autentificarea. Mecanismele de specificare a "izolării" și autentificării sunt unificate într-o enumerare a autentificării nivelelor, descrisă mai jos.

Identificarea apelantului: autentificarea

Mecanismele pentru verificarea dreptului de acces, de generare(lansare) și protecție a datelor necesită determinarea identității clientului. Autentificarea clientului este realizată de unul din serviciile securității, care întoarce un identificator (token) unic al sesiunii utilizat pentru autentificare odată ce conexiunea inițială a fost stabilită. Autentificarea inițială cere uneori schimbul mai multor mesaje între apelant și obiect. De exemplu, securitatea prin serviciul NTLM oferă autentificarea prin somarea apelantului: mecanismul securității cunoaște parola utilizatorului, o decriptează și dacă este corectă confirmă autenticitatea clientului, altfel transmite un mesaj de somare către apelant. În cazul autentificării serviciul, NTLM generează un token, care este întors clientului pentru utilizări ulterioare. Pentru autentificări viitoare, clientul poate transmite doar tokenul și serviciul NTLM nu mai execută secvența operațiilor descrise anterior.

DCOM utilizează tokenul de acces pentru a mări viteza securității la apeluri. Dacă conexiunea între două mașini este stabilită, DCOM cere implicit numai autentificarea. Apoi se stochează pe server doar tokenul de acces – identificator care va fi utilizat automat când se detectează un apel al unui client. Pentru multe aplicații nivelul de autentificare este un bun compromis între performanță și securitate, unele aplicații putând cere autentificări adiționale pe fiecare apel.

Depinzând de transportul utilizat și mărimea datelor transmise, invocarea unei metode poate cere multiple pachete de date în rețea. DCOM oferă aplicațiilor posibilitatea să aleagă fie ca numai primul pachet al metodei invocate să dețină informațiile de autentificare (RPC_C_AUTHN_LEVEL_CALL), fie ca toate pachetele să se identifice individual (RPC_C_AUTHN_LEVEL_PKT).

DCOM definește un singur set de constante care transmit nivelul de autentificare. Acestea sunt:

De când DCOM o aplicat politica de autentificare în numele obiectului, obiectul indică neapărat nivelul de securitate care îl dorește. Aceasta poate fi făcută de fiecare programator sau prin configurări exterioare.

Protecția Apelantului,

O problemă mare, în cadrul aplicațiilor distribuite este protejarea apelantului de obiectele malițioase, fiind o condiție critică pentru aplicațiile Internet. DCOM permite obiectelor să întruchipeze clienții, obiectele având posibilitatea realizării unor operații pe care nu au dreptul să le execute singure. Pentru a preveni utilizarea drepturilor apelantului de către obiectele malițioase acesta poate indica ce le este permis obiectelor să facă cu tokenul securității obținut.

În acest scop s-au definit opțiunile:

Anonimatul – obiectul nu poate obține identitatea clientului. Această opțiune este cea mai sigură setare din punct de vedere al clientului însă oferă facilități minime obiectului;

Identitatea obiectului – are rol de a detecta identitatea apelantului (cerere nume), dar nu poate întruchipa apelantul. Acest apel este sigur pentru client de aceea obiectul nu va efectua operații utilizând drepturile clientului, numele clienților fiind cunoscut obiectului

Întruchiparea – în acest caz obiectul poate întruchipa clientul și poate cere și executa operații locale, dar nu poate apela alte obiecte în numele apelantului. Acest mod este nesigur pentru apelant deoarece el permite obiectului să utilizeze drepturile clientului și să facă operații arbitrare pe mașina pe care obiectul rulează.

Delegarea – obiectul poate întruchipa apelantul și poate executa alte invocări de metodelor în numele clientului. În acest mod, clientul deleagă drepturile și identitatea sa obiectului. Deci obiectul poate executa operații arbitrare incluzând chiar operațiuni remote, utilizând identitatea clientului. Sistemul de operare NT 4.0 nu asigură delegare.

Informațiile sunt păstrate în regiștrii precum și în DACL (Discretionary Access Control List) .

Proiectarea securități

DCOM oferă multiple posibilități de a securiza o aplicație. Înainte de toate, DCOM poate aplica o politica implicită de securitate fără a fi nevoie de fi stabilită în interiorul obiectelor sau a celui care le utilizează, această setare de securitate pentru obiect poate fi configurată extern și DCOM o aplică aceasta automat. Pe de alta parte, DCOM expune o întreagă infrastructura de securitate pentru programator astfel încât clienții și obiectele pot obține prin programare controlul complet peste politica de securitate.

Proiectanții de aplicații pot alege oricare mecanism care este mai apropiat de specificația aplicației pe care o proiectează. Păstrând politica de securitate externă configurabilă se oferă mai multă flexibilitate, deoarece aceeași componentă binară poate fi folosită în aplicații diferite sau în diferite cadre care cer o politică de securitate diferită. Totuși, configurarea securității trebuie să fie furnizată de la început și trebuie să fie corectă pentru ca aplicația să ruleze cum se cuvine. Politica de control a securității prin programare oferă o flexibilitate în plus programatorului, dar codul este dificil de scris în componente sau în clienții lor.

Configurarea securității

DCOM oferă mecanisme pentru a configura din exterior setările pentru obiecte și clienți. În implementarea curentă a DCOM, toate politicile de securitate sunt încapsulate la nivel de proces. Toate obiectele din proces beneficiază de aceeași politică de securitate, doar dacă se programează altfel. Pentru a se folosi de această securitate procedurală, DCOM introduce conceptul de ApplictionID sau AppID.

AppID-urile grupează opțiunile de configurare pentru unul sau mai multe obiecte într-o singură locație din registry. Obiectele COM, găzduite de același executabil, trebuie să se înregistreze în același AppID. Obiectele COM care rulează într-un pseudo proces pot fi legate de același proces prin atribuirea aceluiași AppID intrărilor CLSID. AppId-urile sunt pe 128 de biți GUID. Clasele individuale sunt înregistrate în AppID prin adăugarea unei variabile, „AppId” în cheia CLSID din regiștrii.

[HKEY_CLASSES_ROOȚCLSID\{<clsid>}]

“AppID” = “{<appid>}”

Variabila AppID de tipul REG_SZ conține reprezentarea stringului AppID. Această înregistrare este utilizată în timpul activării pentru a obține drepturile implicite de lansare. Configurația inițiala pentru un AppID este păstrată într-o nouă cheie din regiștri.

[HKEY_CLASSES_ROOȚAppID\{<AppID>}]

Aplicația sau programul de instalare pot modifica direct cheile din regiștri inferioare cheii AppID. Administratori pot folosi utilitarul de configurare DCOM ( dcomcnfg.exe ) pentru a configura manual setările implicite de securitate.

Securitatea accesului

DCOM aplica securitatea accesului asupra fiecărei metode apelate a obiectului. Daca procesul nu a suprascris setările de securitate, DCOM citește descrierea se securitate din regiștrii. Când apelul sosește, DCOM autentifica apelantul și obținerea token-lui de acces. Conținutul valorii din registry este o simpla copie a reprezentării in memorie a descriptorului de securitate relativ:

[HKEY_CLASSES_ROOT \AppId\{<Appid>}]

“AccessPermission” = hex: [self-relative security descriptor]

Securitatea la lansare in execuție

Securitatea la lansare este aplicată oricând un proces nou creează o parte a unui obiect activ. DCOM găsește AppID corespunzător cererii activări si citește descriptorul de fișier din registry. Apoi el autentifică activatorul si verifică dacă token-ul activatorul corespunde în descriptorul de fișier. Securitatea la lansare este păstrată în următoarea cheie:

[HKEY_CLASSES_ROOT \AppId\{<Appid>}]

“LaunchPermission” = hex: [self-relative security descriptor]

Securitate la lansare controlează care clase a clientului permit crearea de obiecte și de a obține referințe la obiecte. Prin definiție, securitatea de lansare este aplicată neapărat de librăriile COM deoarece obiectul nu poate verifica aceasta dacă obiectul nu a fost instanțiat încă. Cu aceasta observație, e evident că securitatea la lansare poate fi configurată numai extern și nu poate fi controlată prin program. Securitatea la lansare este aplicată automat de către SCM pentru un calculator particular. După primirea cereri de la clientul remote de a activa un obiect, SCM-ul calculatorului verifica cererea comparând cu cheia HKEY_CLASSES_ROOȚAppID\{…}\LaunchPermission, care conține date descrise de ACL. Dacă utilizatorul ACE nu este conținut în ACL, accesul este interzis. Daca AppID nu are cheia LaunchPermission, atunci SCM verifică cererea cu cheia DefaultLaunchPermission din HKEY_LOCAL_MACHINE\Software\MicrosofțOLE.

Securitatea la lansare oferă administratorilor un control bogat asupra celor care pot să instanțieze obiecte DCOM pentru a fi utilizate de alți și, furnizează protecție a proceselor de inițializarea accidentala sau rău intenționată.

Identitatea

DCOM alege identitatea unui obiect în timpul lansări procesului care conține obiectul. Din nou el determină AppID a procesului care a fost creat și obține setările de identitate din registry. Trei variante sunt disponibile:

Rulează ca și activator. Procesul este creat într-un Window Station nou cu toate drepturile a celui care a creat obiectul pe acel calculator. Deși aceasta opțiune este implicită, nu este recomandată. Dacă mai mulți utilizatori creează obiecte oarecare pe o mașină atunci sistemul creează un Window Station independent pentru procesele create de fiecare utilizator. Window Station consumă o parte semnificativă de resurse și numărul lor este limitat în versiunea curentă a lui Windows NT.

Rulează ca și utilizator interactiv. Procesul este creat în Window Station-ul utilizatorului local intrat în sistem (logat). Daca nici un utilizator nu este logat, crearea obiectului eșuează. Această opțiune este foarte utilă pentru aplicațiile distribuite care necesită depanare.

Rulează într-un cont de utilizator fixat. Procesul este creat în Window Station neinteractiv corespunzător contului utilizatorului specificat. Dacă Window Station neinteractiv pentru acest cont există, atunci acesta este folosit. Pentru a obține drepturile de securitate, DCOM are nevoie de parola curentă a contului utilizat. Această parola este păstrată separat într-o parte securizată a registry-ului.

Securitatea de identitate este indicată utilizând următoarea intrare în registry:

[HKEY_CLASSES_ROOT \AppId\{<Appid>}]

“RunAs” = “InteractiveUser”

[HKEY_CLASSES_ROOT \AppId\{<Appid>}]

“RunAs” = “mydomain\myaccount”

În absența lui ”RunAs” valoarea indica ”Run as Activator”.

Securitatea de identitate este configurată extern de obicei, deși se poate schimba prin programare utilizând procesele standard Win32 și API.

Securitatea implicită a calculatorului.

Daca un obiect nu este configurat în mod explicit, DCOM aplică setările implicite ale calculatorului pentru toți parametri de securitate. Aceste setări sunt păstrate în regiștri ca următoarele:

[HKEY_LOCAL_MACHINE\SOFTWARE\MicrosofțOle]

“EnableDCOM”=“Y”

“DefaultAccessPermission” = hex: [self-relative security descriptor]

“DefaultLaunchPermission” = hex: [self-relative security descriptor]

“LegacyImpersonationLevel” = dword:2

“LegacyAuthenticationLevel” = dword:2

Utilitarul de configurare DCOM (DCOMCNFG.EXE) furnizează acces ușor la aceste setări de registry. Administratorul poate controla securitatea activări (Launch Security)la amândouă nivelele, nivelul calculatorului și nivelul obiectului. În adăugare la securitatea la lansare, administratorul poate controla cine poate avea acces la un obiect particular (Access Security) și cine poate modifica setările din regiștri pentru un obiect particular (Configuration Access).

Programarea securități

Un proces trebuie să inițializeze nivelul de securitate DCOM prin apelarea funcției CoInitializeSecurity pentru a stabili nivelul de autentificare și personalizare. Serverele DCOM trebuie să apeleze această funcție API înainte de a înregistra clasa de obiecte (CoRegisterClassObject) dacă doresc ca autentificarea conectări să nu fie cea implicită. Clienți trebuie să apeleze această funcție API înainte de a primi sau a scoate afară primul pointer la interfață. Utilizând CoInitializeSecurity, și prin aceasta se stabilește securitatea apelării implicite pentru proces, clientul evită să folosească IClientSecurity pe proxy individual. Implementarea lui QueryInterface nu trebuie niciodată să facă verificarea ACL până când COM salvează pointeri la interfețe și nu va apela QueryInterface pe server de fiecare dată când clientul face o întrebare.

Din cele prezentate mai sus se observă că acest model oferă multiple modalități de realizare a securității aplicațiilor. El poate impune realizarea securității fără a acționa în numele unui obiect sau al apelantului obiectului; setările corespunzătoare securității unui obiect putând fi configurate extern și impuse automat.

Din punctul de vedere al dezvoltatorilor modelul expune în întregime infrastructura sistemului de securitate, oferind clienților și obiectelor posibilitatea obținerii controlului prin intermediul codului la politicile de securitate.

Prezentarea aplicației

Problema pe care încearcă să o rezolve aplicația este una cu rol demonstrativ, principalul ei scop fiind să exemplifice modul de implementare a unui server DCOM și accesarea acestuia dintr-un limbaj de programare de nivel înalt. Aplicația se compune din trei parți: un server DCOM (ChannelMannager) al cărui scop este de a gestiona o listă de canale pe care se publică informații și de a transmite clienților interesați aceste informații, două tipuri de programe client, unul (Publisher) care după ce se conectează la server poate crea un canal și publica diverse informați pe acesta, iar al doilea tip de aplicație client (Subscriber) permite înscrierea la diverse canalele de informații disponibile pe server și vizualizarea acestora, în tip real.

Serverul este scris în Microsoft Visual C++ și folosește drept cadru de aplicație ATL. Cum am specificat anterior rolul acestuia este de a gestiona o listă de canale de informații și de a distribui aceste informații.

Interfețele implementate de obiectele din interiorul serverului sunt urmatoarele: IChannelManager, IPublisher și ISubscriber.

Interfața IChannelManager oferă urmatoarele funcționalități:

Obținerea unei liste cu canalele de informații disponibile pe server

Creerea unui nou canal (sau accesul la unul deja existent), această operație returnând un pointer la o interfață IPublisher prin intermediul căruia se pot publica mesaje.

Înscrierea la un canal deja existent, care are ca efect obținerea unui pointer la o interfața ISubscriber prin intermediul căruia se primesc mesajele de pe acest canal.

Interfața IPublisher permite publicarea de mesaje iar ISubsriber recepționarea acestora.

Un canal este disponibil atâta timp cât exista cel puțin un client de tip Publisher conectat la acesta. În momentul când un canal rămâne fară nici un Publisher acesta este distrus și toți clienți sunt notificați de acest fapt.

Cele doua programe client sunt scrise în Visual Basic 6.0, și scopul acestora este de a juca rolul de Publisher respectiv Subscriber. Subscriber-ul permite conectarea la un server local sau pe o anumită mașina specificată la începutul execuției, crearea unui canal si publicarea de informații.

Interfața oferită utilizatorului este cea din figura următoare.

Programul client Subscriber este unul de tip MDI (Multi Document Interface) și permite conectarea la un server, atât local cât și la distanța, vizualizarea canalelor disponibile pe server, și conectarea selectivă la acestea.

Urmatoarea figură surprinde funcționalitatea programului.

Lista cu canalele disponibile este întreținută auomat, orice operație de creare sau distrugere a unui canal fiind imediat vizibilă.

BIBLIOGRAFIE

DCOM Technical Overview http://www.microsoft.com/ntserver/library/dcomtec.exe

Richard Grimes „Begining ATL COM programming”

Richard Grimes „Professional ATL COM programming”

Florian Mircea Boian, „Programarea distribuită în internet, metode și aplicați”, Editura Albastră 1997

COM Spec http://www.microsoft.com/com/comdocs.htm

DCOM Business Overview http://www.microsoft.com/ntserver/guide/dcom.asp?A=2&B=2

DCOM Solutions in Action http://www.microsoft.com/com/wpaper/dcomsol.zip

Similar Posts