Nastal čas na kakao - Trocha magie, aneb distribuované objekty - MujMAC.cz - Apple, Mac OS X, Apple iPod

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:

Soutěž

Sponzorem soutěže je:

IDIF

 

Jaký fotograf/ka získal/a cenu za nejpopulárnější příspěvek v Nikon Photo Contest?

V dnešní soutěži hrajeme o:

Seriály

Více seriálů



Začínáme s

Nastal čas na kakao - Trocha magie, aneb distribuované objekty

22. března 2005, 00.00 | Dnes nám v přehledu konkrétních tříd Foundation Kitu vyšel čas na skutečnou lahůdku: přeskočíme-li totiž velmi zřídka užívané zámky NS...Lock, je na řadě třída NSConnection (a třídy, jež s ní spolupracují) – budeme se tedy zabývat distribuovanými objekty.

Dnes nám v přehledu konkrétních tříd Foundation Kitu vyšel čas na skutečnou lahůdku: přeskočíme-li totiž velmi zřídka užívané zámky NS...Lock, je na řadě třída NSConnection (a třídy, jež s ní spolupracují) – budeme se tedy zabývat distribuovanými objekty.

Na rozdíl od minulých článků tentokrát ale neskočíme rovnýma nohama do popisu konkrétních tříd a ukázek příkladů; vzhledem k tomu, že distribuované objekty jsou dodnes poměrně málo známý prostředek (ačkoli jsou programátorsky nesmírně pohodlné, efektivní, a programátoři je mají k dispozici nejméně od roku 1992), popíšeme si nejprve samotný princip; až potom si ukážeme konkrétní prostředky a nějaké příklady.

O co vůbec jde?

Zjednodušeně řečeno, distribuované objekty umožňují komunikaci mezi objekty, jež nejsou součástí jednoho a téhož adresového prostoru (a potenciálně ani téhož programu, téhož systému, téhož počítače...). Na rozdíl od klasických služeb pro komunikaci mezi různými procesy – jakými jsou třeba BSD sockety – distribuované objekty využívají nesmírně silnou vlastnost objektového programování, která se nazývá polymorfismus: jde o to, že s "cizími" objekty komunikujeme přesně stejně, jako s "vlastními".

Podívejme se na naprosto triviální funkci, jež spočte průměrnou hodnotu objektů, jež dostane v poli (předpokládá se přitom, že objekty dokáží své číselné hodnoty vrátit po přijetí standardní zprávy intValue):

int stupidAverage(NSArray *a) {
  NSEnumerator *en=[a objectEnumerator];
  id o;
  int sum=0;
  while (o=[en nextObject]) sum+=[o intValue];
  return sum/[a count];
} 

Představte si, že máte již dávno tuto službu hotovou, a nyní z nějakého důvodu je zapotřebí spočíst průměrnou hodnotu pole, jež není součástí aplikace, ale je uloženo na serveru. V klasickém prostředí bychom měli jen dvě možnosti:

  • přenést kompletní pole dat ze serveru do klienta, a tam jeho lokální kopii předat jako argument službě stupidAverage. To je samozřejmě možné (a v praxi se to také dělá i v systému s distribuovanými objekty), pokud jsou skutečná data, uložená v poli, rozumně malá. Je však stejně dobře možné, že každý objekt v poli sám zabere několik megabytů; pak by tento přístup byl krajně neefektivní. Navíc bychom si stejně museli napsat službu, jež přes sockety (nebo jiný klasický interface) data přenese, a vytvoří na klientu jejich kopii;
  • napsat speciální rutinu stupidServerAverage, jež bude přímo spolupracovat se serverem a namísto NSEnumeratoru či zprávy intValue využívat jeho speciálních služeb. To samozřejmě není tak těžké, ovšem znamená to prakticky každou rutinu znovu psát a ladit – v celkovém součtu to programování nesmírně komplikuje.

My však přece pracujeme v objektovém prostředí, a proto bychom chtěli jednou napsaný a odladěný kód jen znovu využít, a nic znovu nepsat. S distribuovanými objekty je to snadné: jestliže již dávno hotové a odladěné rutině stupidAverage předáme jako argument pole, které leží na serveru, bude vše fungovat zcela korektně: NSEnumerator bez obtíží zpřístupní jednotlivé prvky pole (jež samy dokonce mohou třeba každý ležet na jiném serveru, aniž by to přineslo jakékoli komplikace!), a zpráva intValue korektně zjistí jejich číselné hodnoty.

Jak to může fungovat?

Překvapivě snadno; jediné, co potřebujeme, je dobře navržený objektový systém, podporující korektně polymorfismus a zapouzdření a (tedy také) přesměrování zpráv. Nejprve si připomeneme několik základních principů takového objektového systému:

  • objekt je "něco v paměti", identifikován je tedy svou adresou;
  • objekty spolu komunikují výhradně prostřednictvím zpráv;
  • zpráva je "balíček", obsahující jak vlastní identifikaci zprávy (např. "intValue"), tak i její případné argumenty.

Je asi na první pohled víceméně zřejmé, že je-li tomu přesně tak (a v Objective C tomu tak v podstatě je, jakkoli identifikace zprávy a její argumenty jsou kódovány – kvůli efektivitě – velmi "strojovým" způsobem), lze snadno vyřešit i předávání zpráv mezi různými počítači. Celý "balíček" reprezentující zprávu můžeme zakódovat (vzpomeňme si na archivační služby, jimiž jsme se zabývali minule: jejich prostřednictvím snadno zakódujeme do binárního streamu libovolný objekt, který může být argumentem zprávy), výsledek odeslat – pomocí standardních služeb TCP/IP – na jiný počítač; tam pak se zpráva opět dekóduje a předá cílovému objektu. Vyřešíme-li několik technických problémů (především s identifikací objektů a s navazováním spojení), můžeme tento mechanismus implementovat – a máme vlastně funkční distribuované objekty.

Zástupné objekty (proxy)

V předcházejícím textu jsme abstrahovali od řady detailů, které naši krásně jednoduchou představu o DO malinko komplikují. Prvnímu z nich věnujeme tento odstavec; jedná se o problém s identifikací objektu: je-li k tomu využita adresa – a tak tomu, jak víme, skutečně je – nemáme žádnou možnost identifikovat objekt, který neleží v "našem" adresovém prostoru.

Nejběžnějším řešením je využití tzv. zástupného objektu (v anglické terminologii "proxy"). Chceme-li komunikovat s objektem, který leží v jiném adresovém prostoru, vytvoříme si v našem adresovém prostoru zástupný objekt – jednoduchý objekt, který nedokáže nic jiného, než vzít kteroukoli zprávu kterou dostane, zakódovat ji, doplnit cílovou adresou a odeslat ji po síti cílovému procesu (který ji dekóduje a předá objektu na cílové adrese ve svém adresovém prostoru). Na obrázku je zástupný objekt označen P podle anglické terminologie (proxy):

Zastavme se na chvilku: schopnost zakódování zprávy a odeslání po síti by jistě mohla být naprogramována přímo jako součást objektu O1; proč tedy komplikovat věci a vytvářet nějaké zástupné objekty? Kdo četl pozorně, již odpověď zná – právě kvůli transparentnosti služeb systému DO. Objekt O1 je docela obyčejný standardní objekt, jehož programátor vůbec nemusel s nějakou komunikací po síti počítat; z hlediska objektu O1 probíhá naprosto standardní komunikace mezi ním a jeho partnerem (který má vlastnosti objektu O2 a adresu objektu P). Za chvíli se může situace změnit – uživatel např. uzavře dokument ležící na jiném počítači a otevře jiný, lokální dokument – a objekt O1 bude přesně stejně, bez jakékoli změny, komunikovat s lokálním partnerem O3:

Na tomto místě je také vhodné si uvědomit, že a proč plnohodnotný systém distribuovaných objektů nutně vyžaduje plně objektový programovací jazyk (typu Objective C): jde o to, že aby mohl objekt O1 bez obtíží stejným způsobem komunikovat přímo s objektem O3 jako se zástupným objektem P, musíme mít k dispozici korektní a neomezený polymorfismus: programovací jazyk musí umožňovat zaslání libovolné zprávy libovolnému objektu, s tím, že až za běhu se zprávě přiřadí odpovídající implementace. Jazyky typu C++, jež polymorfismus nemají (přesně řečeno, mají jej jen ve velmi omezené míře, pro objekty, jež mají společnou nadtřídu), to zajistit nemohou: abychom např. v C++ mohli takový program vůbec napsat, musel by být zástupný objekt P objektem téže třídy, jako objekty O2 a O3 – a to je na první pohled zřejmý nesmysl.

ORB

Vraťme se však od jazyků k distribuovaným objektům: na straně příjemce se "někdo" musí postarat o dekódování přijaté zprávy a o její předání cílovému objektu. Na straně odesilatele je zase třeba zprávy kódovat a odesílat; zde by se sice v principu o vše mohly postarat zástupné objekty, ale komunikace je jen málokdy jednosměrná – objekt, který odeslal zprávu, chce obvykle dostat zpět výsledek jejího zpracování; často se sám cílový objekt obrací na odesilatele původní zprávy s vlastními zprávami, požadujícími další data a údaje...

Na obou stranách je proto ještě navíc zapotřebí programový modul, který fakticky zajišťuje všechny potřebné úlohy:

  • navázání spojení s partnerským procesem (k tomu se podrobněji vrátíme za chviličku);
  • odesílání a příjem zpráv;
  • kódování zpráv, které mají být odeslány;
  • dekódování přijatých zpráv;
  • předávání přijatých zpráv patřičným objektům.

Pro tento programový modul se vžilo označení Object Request Broker – ORB. V prostředí Cocoa je reprezentován objektem třídy NSConnection (v ilustraci označeným "Conn"); celkovou situaci si můžeme prohlédnout na dalším obrázku – postupné kroky, ve kterých probíhá předání zprávy, jsou zde očíslovány:

  1. objekt O1 odesílá standardním způsobem zprávu objektu P, s nímž komunikuje (a sám obvykle vůbec "netuší", jinými slovy, jeho programátor vůbec nemusel počítat s tím, že se jedná o zástupný objekt);
  2. zástupný objekt zprávu doplní adresou cílového objektu v cílovém adresovém prostoru a celek předá ORBu;
  3. ORB zprávu i cílovou adresu zakóduje a odešle (nejspíše protokolem TCP/IP) ORBu v cílovém procesu;
  4. ORB v cílovém procesu zprávu dekóduje a předá ji objektu O2 na cílové adrese.

Pokud by zpráva vracela nějaké výsledky, probíhalo by jejich předání přesně opačným způsobem: objekt O2 by je zcela standardně předal "volajícímu", tedy ORBu (přičemž by vůbec nemusel "vědět", že nekomunikuje přímo s objektem O1), ten by je zakódoval a odeslal po síti, ORB v procesu 1 by je dekódoval a prostřednictvím zástupného objektu předal zpět objektu O1.

ORB obvykle zajišťuje řadu dalších úkolů – udržuje např. statistiku přijatých a odeslaných zpráv, stará se o kódování a dekódování obecných neobjektových dat, která mohou být také argumenty odesílané zprávy či návratové hodnoty, a udržuje si přehled o zástupných objektech (v příštím odstavci uvidíme proč). Úkolem ORBu je také řešit problémy, které nastanou jestliže se spojení přeruší, předávat případné výjimky (hlášení o chybových stavech) a podobně.

Vyhledání partnerů

Jak jsme se zmínili v minulém odstavci – a jak si pozorný čtenář jistě už sám dávno uvědomil – musí vůbec prvním krokem v distribuovaném systému být vyhledání objektu, se kterým chceme komunikovat. Vazba mezi objekty uvnitř jednoho adresového prostoru může (ale samozřejmě nemusí) být vytvořena staticky již při programování objektové aplikace; vazba mezi objekty uloženými v různých adresových prostorech ale musí nutně být navázána dynamicky: je zapotřebí nějak vyhledat cílový objekt, zjistit jeho adresu, identifikaci procesu ve kterém leží a identifikaci počítače, na kterém tento proces běží, a na základě těchto údajů vytvořit jeho zástupný objekt.

Poměrně jednoduchá je situace v případě, že již existuje spojení mezi některými dvěma objekty, a je zapotřebí navázat další spojení mezi jinou dvojicí objektů. V takovém případě prostě jeden objekt pošle druhému zprávu, obsahující odkaz na 'nový' objekt (tj. jeho adresu – uvědomme si znovu, že odesílající objekt vůbec neví, že komunikace není lokální). ORB v cílovém procesu musí takový odkaz zpracovat speciálním způsobem – namísto toho, aby jej přímo předal cílovému objektu, vytvoří zástupný objekt, reprezentující předávaný objekt; cílovému objektu předá adresu zástupného objektu.

Obrázek ukazuje konkrétní příklad: objekt O1 odeslal (skrze proxy) objektu O2 zprávu, jejímž parametrem je objekt O3 (ve skutečnosti tedy jeho adresa – např. 0x12E0004). Zástupný objekt tuto zprávu předá ORBu, ten ji standardně zakóduje a předá ORBu v cílovém procesu. Ten zprávu dekóduje a přitom zjistí, že její součástí je odkaz na objekt. Vytvoří tedy zástupný objekt P3, a uloží do něj identifikaci procesu 1 (a počítače, na kterém proces 1 běží), a adresu 0x12E0004. Pak vezme adresu nově vytvořeného zástupného objektu – dejme tomu 0x341F01 – a předá ji objektu O2 na místě původního parametru zprávy.

Kdykoli potom bude chtít objekt O2 odeslat zprávu "objektu O3", odešle ji ve skutečnosti zástupnému objektu P3. To je ale v naprostém pořádku, protože zástupný objekt P3 jakoukoli zprávu, kterou dostane, předá již známým mechanismem na správnou adresu – totiž objektu O3 v procesu 1.

ORB přitom musí mít přehled o již vytvořených zástupných objektech. Je totiž snadno možné, že zanedlouho znovu některý objekt odešle z procesu 1 do procesu 2 zprávu, jejímž parametrem bude opět objekt O3. Nebylo by efektivní (a ani zcela korektní kvůli možnosti porovnávání objektů na identitu) pokaždé vytvářet nový zástupný objekt; namísto toho ORB musí poznat, že zástupný objekt pro "objekt v procesu 1 s adresou 0x12E0004" již existuje, a že se jedná právě o P3; nebude proto vytvářet nový zástupný objekt, ale pouze nahradí původní adresu ve zprávě adresou již existujícího zástupného objektu P3.

Jak vidíme, systém distribuovaných objektů není z hlediska praktické implementace zase až tak úplně jednoduchý (a to jsme zatím zdaleka nevyřešili všechny problémy; některým z nich – jako je např. řešení problémů při přerušeném spojení – se v tomto článku ani věnovat nebudeme). Důležité ale je, že celá složitost leží v samostatném programovém modulu (v našem případě tedy v implementaci tříd NSConnection a dvojici NSProxy a NSDistantObject), a vůbec nijak se nepromítá do implementace objektů, které služeb DO využívají – pro ty je stále celý systém naprosto transparentní a komunikují spolu přesně stejně, jako kdyby všechny ležely v jediném adresovém prostoru. Jinými slovy, z hlediska objektů O1, O2 a O3 – přesně řečeno, z hlediska toho, čím se jejich programátor musí zabývat a na co se musí soustředit – celá situace z minulého obrázku vypadá docela prostě a jednoduše takto:

Jediné, co musí programátor explicitně vyřešit, je prvotní navázání vůbec prvého spojení mezi oběma procesy. Zde je nutné využít služeb nějakého globálního systému (globálního z hlediska počítačové sítě), který umožní objektům v různých procesech se navzájem vyhledat. To je prakticky jediné místo, ve kterém se distribuovaný systém z hlediska programátora liší od monolitického: zatímco v monolitickém jsou základní vazby mezi objekty vytvořeny při překladu staticky (v Cocoa spíše v rámci objektových sítí, sestavovaných v aplikaci InterfaceBuilder), v distribuovaném je třeba pro jejich vytvoření použít služeb třídy NSConnection (za chvilku si ukážeme příklad, ve kterém uvidíme jak se to dělá).

Nejprve se ale ještě chvilku zdržíme u řešení praktických problémů, o něž se musí postarat middleware: řekli jsme si, že pro předávání dat můžeme použít mechanismus archivace, který známe od minula (a to je pravda); později jsme si ale ukázali, že objekty se vlastně nemusí předávat vůbec, neboť jsou automaticky nahrazeny zástupnými objekty; jak to tedy ve skutečnosti vlastně je? A co s neobjektovými daty?

Předávání neobjektových dat

Součástí zpráv předávaných po síti mohou být i obecná data, uložená jako jejich parametry (připomeňme, že součástí "balíčku", reprezentujícího zprávu, je obojí – jak identifikace samotné zprávy, tak i její argumenty). ORB na straně odesilatele musí proto data zakódovat do formátu, vhodného k přenosu; ORB na straně příjemce musí data opět dekódovat. Je třeba si uvědomit, že se nejedná o triviální úlohu. Jen pro rámcovou ilustraci problémů se podívejme na řešení pro některé základní datové typy:

Celočíselné hodnoty se kódují prostě jako řada bytů, tak dlouhá, kolik je zapotřebí na odpovídající číslo (tj. jeden byte pro typ char jazyka C, čtyři byty pro int apod.). Důležité však je pořadí bytů – není nikde zaručeno, že vysílající i přijímající procesy běží na počítačích s touž architekturou, takže jeden z nich může pracovat v little-endian módu a druhý v big-endian módu. ORBy se proto musí domluvit a v případě potřeby číselné hodnoty automaticky převádět.

Adresy objektů přímo předávat nelze – jak jsme si ukázali, musí ORB na straně příjemce vyhledat nebo vytvořit zástupný objekt a předat jej namísto původního objektu. Systém distribuovaných objektů, který je součástí API Cocoa, navíc umožňuje kromě odkazu na objekt předávat objekt jako celek ("hodnotou"); na tuto možnost se podíváme v příštím odstavci (jde o velmi luxusní službu, např. známý systém distribuovaných objektů CORBA ji nemá).

Největší problém je s ukazateli (a tedy také s poli, jež v C nejsou ničím jiným, než opět ukazateli). Ukazatel sám ovšem není nic jiného než prostá adresa; je tedy zřejmé, že předat jej přímo do jiného adresového prostoru by nemělo žádný smysl. Triviálním řešením by bylo ukazatele prostě zakázat; to by ale nepříjemně omezilo polymorfismus a transparentnost DO (protože objekt, který má využívat komunikaci, by pak musel být psán speciálním způsobem – nesměl by využívat ukazatelů). Podívejme se na způsob jakým tento problém řeší Cocoa:

  1. ORB na straně odesilatele zjistí velikost dat, na které ukazatel míří;
  2. ORB na straně příjemce alokuje ve svém adresovém prostoru odpovídající blok;
  3. kompletní data se přenesou k příjemci a tam se uloží do alokovaného bloku (a pokud snad šlo o čísla, ještě se navíc může "po cestě" měnit little-endian na big-endian či naopak);
  4. teprve nyní předá ORB na straně příjemce zprávu cílovému objektu; adresu v ukazateli přitom nahradí adresou nově alokovaného bloku;
  5. když je objekt se zprávou hotov, přenesou se data z bloku zpět do adresového prostoru původního odesilatele a uloží se na místo původních dat (takže případné změny, které v datech cílový objekt provedl, nebudou ztraceny);
  6. teprve nyní je pomocný blok na straně příjemce uvolněn.

Chceme-li, v Objective C máme navíc možnost tento mechanismus omezit explicitním určením je-li blok dat pouze vstupní (v tom případě se vynechá bod 5) nebo pouze výstupní (pak se vynechá bod 3) pomocí modifikátorů in a out:

-(void)vstup:(int in*)vstup vystup:(int out*)vystup oba:(int*)oba;

Určili jsme tak, že argument vstup je pouze vstupní (takže lze pro něj vynechat bod 5) a že argument vystup je pouze výstupní (takže lze pro něj vynechat bod 3). Argument oba je vstupní i výstupní, takže se provedou všechny body.

Předávání objektů

Standardní mechanismus předávání objektů jsme si již důkladně vysvětlili – cílový ORB vytvoří nebo vyhledá zástupný objekt pro předávaný objekt, a cílovému objektu předá namísto původního parametru adresu nového zástupného objektu. Představme si ale situaci, ve které je "předaný objekt" (tj. ve skutečnosti zástupný objekt) velmi intenzivně a často využíván; pak v tomto modelu dojde ke značnému zatížení sítě a také k nepříjemnému zpomalení aplikace (protože síť je obvykle poměrně pomalá, rozhodně – byť i to byl gigabitový Ethernet – daleko pomalejší, než přímý přístup k datům rovnou v operační paměti, resp. cache procesoru).

Špičkové systémy distribuovaných objektů proto umožňují v takovýchto případech – pokud si to programátor vyžádá – namísto zástupného objektu předat skutečný objekt. Přesněji řečeno, ORB v cílovém procesu namísto proxy vytvoří přesnou kopii předávaného objektu, a cílovému objektu předá adresu této kopie. Připomeňme si příklad s vytvořením zástupného objektu P3 pro objekt O3; pokud bychom využili předání objektu, vypadala by situace tak, že objekt O3 v procesu 2 není zástupný, ale namísto toho je přesnou kopií objektu O3 v procesu 1:

V Objective C zajišťuje tuto službu modifikátor bycopy, použitý v definici objektového argumentu nebo návratové hodnoty:

-(void)foo:(bycopy id)obj1 bar:(id)obj2;
-(bycopy)thisMethodReturnsAnObjectByCopy;

První argument prvé zprávy – obj1 – bude předáván "hodnotou", tj. vytvoří se jeho kopie. Druhý argument obj2 bude předán normálně (tj. vytvoří se pro něj zástupný objekt). Druhá zpráva vrací "hodnotou" objekt.

Možnost předávání objektů namísto zástupců je v některých případech velmi šikovná a může být obtížné (nebo aspoň krajně neefektivní) se bez ní obejít; na druhou stranu si musíme uvědomit, že klade nemalé nároky na objektový systém, nad kterým DO pracují. Aniž bychom zacházeli do podrobností, ukážeme si nejzásadnější problémy které je třeba vyřešit a jako příklad velmi stručně nastíníme řešení, které nabízí Cocoa:

  • jestliže se stav objektu O3 může v průběhu času měnit, mohlo by snadno dojít k situaci, kdy bude stav objektu O3 v procesu 1 odlišný od stavu objektu O3 v procesu 2. To je obecně velmi nežádoucí, protože to ruší transparentnost DO: systém by se choval jinak při komunikaci po síti, než kdyby všechny objekty byly lokální. Objektový systém proto musí nabízet aparát, který tento problém řeší (Cocoa 'ví' zda se objekt může měnit nebo ne, vzpomeňme si na měnitelné a neměnné objekty);
  • objekt je zapotřebí zakódovat pro jeho odeslání po síti; takové kódování ale musí podporovat sám objekt – obecně není možné prostě vzít data objektu a mechanicky je zkopírovat (Cocoa podporuje obecné kódování objektů prostřednictvím třídy NSCoder a jejích podtříd, jak jsme se s nimi seznámili v minulém dílu);
  • v cílovém procesu musí být k dispozici kód, svázaný s objektem, tj. jeho třída (v Objective C lze dynamicky zjistit třídu objektu a vyhledat ji nebo ji zavést z knihovny).

V Cocoa se pro vyšší efektivitu automaticky předávají bycopy argumenty standardních datových tříd NSArray, NSDictionary či NSString. Chceme-li v některých speciálních případech předepsat i jejich předávání prostřednictvím proxy, můžeme použít alternativní modifikátor byref.

Konkrétní třídy pro distribuované objekty v Cocoa

Máme-li konečně jasno v tom, co to vlastně systém distribuovaných objektů je a jak funguje, můžeme se podrobněji podívat na konkrétní třídy API Cocoa, jež jeho služby zajišťují.

NSConnection

Objekty třídy NSConnection reprezentují ORB – každé spojení mezi dvěma procesy je tedy zajišťováno dvojicí objektů NSConnection, na každé straně spojení jedním. Z hlediska navazování spojení je vždy jeden proces "serverem":

  • vytvoří svůj objekt NSConnection a zaregistruje jej u name serveru sítě pod nějakým jménem;
  • určí "hlavní objekt" (root object), jehož proxy automaticky dostane každý, kdo se k serveru připojí.

Libovolný počet "klientů" si pak může vyžádat vytvoření vlastního objektu NSConnection pro spojení se "serverem" se zadaným jménem; pokud takové jméno existuje, vytvoří se na straně klienta automaticky objekt NSConnection, naváže se spojení mezi ním a serverem, a vytvoří se proxy, reprezentující u klienta "hlavní objekt" serveru. Všechny ostatní zástupné objekty jsou pak vytvářeny podle potřeby automatickým mechanismem, popsaným výše.

Je důležité si uvědomit, že rozlišení klient/server je podstatné pouze z hlediska navazování spojení: zde je serverem jednoznačně ten, kdo své jméno registruje, klientem je pak ten, kdo si vyžádá spojení se serverem na základě jím registrovaného jména. Konkrétní komunikace mezi oběma procesy pak již není nijak omezena, a závisí jen na tom, jak celý systém navrhneme. Nejsme tedy nikterak omezeni na systémy klient/server; spíše jde o plnohodnotnou komunikaci typu peer-to-peer.

Typický kód serveru pro určení hlavního objektu a registraci jména vypadá ve Foundation Kitu nějak takto:

// vytvoříme hlavní objekt
id root=[[MujHlavniObjekt alloc] init];
// každý thread má v Cocoa svůj standardní connection
id myconn=[NSConnection defaultConnection];
// zaregistrujeme hlavní objekt
[myconn setRootObject:root];
// zaregistrujeme jméno
if (![myconn registerName:@"Jméno serveru..."]) {
  // nelze registrovat ... buď již je toto
  // jméno zaregistrováno, nebo nastala
  // nějaká chyba v operačním systému
} 

Kód klienta je ještě mnohem jednodušší – jediným příkazem přímo získáme zástupný objekt, který reprezentuje hlavní objekt serveru se zadaným jménem; odpovídající objekt NSConnection se vytvoří automaticky a programátor se jím nemusí vůbec zabývat:

id server=[[NSConnection
  rootProxyForConnectionWithRegisteredName:@"Jméno serveru..."
  host:nil] retain]; // nebo doménové jméno počítače
if (server==nil) {
  // server se zadaným jménem není k dispozici
} 

Jestliže nyní "klient" pošle jakoukoli zprávu "serveru" – například

[server haloJsiTam]; 

dostane tuto zprávu postupem, který jsme popsali v minulém odstavci, objekt root v "serveru", právě ten, který jsme určili pomocí zprávy setRootObject:.

Třída NSConnection nabízí předlouhou řadu dalších služeb, které usnadňují řízení komunikace mezi oběma procesy. Pomocí metody allConnections např. můžeme získat seznam všech objektů NSConnection, které jsou v tomto procesu otevřeny – tj. seznam všech aktivních spojení do jiných procesů či threadů. Metody setReplyTimeout: a setRequestTimeout: určují, jak dlouho má ORB čekat na reakci druhého procesu, než prostřednictvím notifikací (nesmírně flexibilního mechanismu pro předávání informací mezi objekty, se kterým se seznámíme později) informuje každého, koho to zajímá, o tom, že spojení bylo přerušeno. Řada dalších zpráv umožňuje řídit spojení, zjišťovat statistiky ohledně nevázaného spojení, dynamicky rozhodovat zda se konkrétní spojení smí navázat nebo ne, a tak dále.

Za samostatnou zmínku snad už stojí jen to, že u multithreadových aplikací můžeme někdy chtít navazovat spojení DO uvnitř aplikace, mezi jejími jednotlivými thready. Pro tento případ nabízí NSConnection službu connectionWithReceivePort:sendPort:, jež vytvoří lokální spojení mezi dvojicí portů (reprezentovaných objekty třídy NSPort, ale tu již, vzhledem k rozsahu tohoto článku, opravdu popisovat nemůžeme).

NSProxy a NSDistantObject

Objekty třídy NSProxy reprezentují zcela obecné zástupné objekty; z programátorského hlediska tedy na nich vlastně dohromady není co popisovat, protože proxy se vždy polymorfně chová jako objekt, na který odkazuje.

Přesto existuje jedna specifická zpráva, kterou interpretuje právě proxy (přesněji řečeno, jeho podtřída NSDistantObject, sloužící speciálně pro zástupné objekty v rámci DO – to je z hlediska našeho orientačního popisu lhostejné, a rozlišovat NSProxy a NSDistantObject nemusíme). Musíme si totiž uvědomit, že odesílání zprávy je ve skutečnosti ještě o něco složitější, než jak jsme je dosud popsali: uvedli jsme, že systém distribuovaných objektů "zabalí nejprve zprávu a její parametry do balíčku, a ten předá cílovému procesu". Aby však bylo vůbec možné zprávu a její parametry zabalit, musíme znát nejprve typy parametrů, které skutečný objekt ve zprávě očekává (připomeňme rozbor komplikovaného předávání parametrů různých typů), a na ty se musí nejprve zeptat procesu, ve kterém skutečný objekt – a tedy i informace o něm – leží. Při odeslání jediné docela jednoduché zprávy prostřednictvím systému DO tedy po síti ve skutečnosti musí proběhnout (nejméně) čtyři síťové pakety:

  1. dotaz na typy parametrů dané zprávy;
  2. odpověď, obsahující popis parametrů a jejich typů (vzpomínáte si ještě na ukázku přesměrování zpráv, v níž jsme se seznámili s metodou methodSignatureForSelector: a řekli jsme si "existují i jiné příležitosti než přesměrování neznámé zprávy, při nichž se může runtime systém zeptat objektu na signaturu pro některý selektor"? Toto je právě takový případ);
  3. vlastní zpráva (zakódovaná na základě popisu z minulého bodu);
  4. výsledek zprávy (tento krok lze vynechat u zpráv typu oneway, viz níže).

Pro zvýšení efektivity můžeme ušetřit první dva kroky jednoduchým způsobem: informujeme zástupný objekt jednorázově o typech parametrů všech zpráv, které je cílový objekt schopen zpracovat (samozřejmě to není možné v případech, kdy cílový objekt zprávy dynamicky přesměrovává; to je ale dost výjimečný případ). Objective C nabízí prostředek pro zápis seznamu zpráv a typů jejich parametrů; dávno jej známe, a jmenuje se protokol. Každému proxy pak můžeme – chceme-li – zprávou setProtocolForProxy: přidělit protokol, který popisuje zprávy, zpracovávané objektem, jenž je proxy objektem reprezentován. Tím si uspoříme odesílání prvních dvou paketů: není třeba se na atributy zprávy ptát, protože jsou známy přímo v proxy díky protokolu.

Jazykové prostředky Objective C

Díky flexibilitě objektového systému založeného na Objective C není zapotřebí rozšiřovat programátorský model při práci s distribuovanými objekty téměř o nic (pro srovnání stojí za to se podívat např. na systém distribuovaných objektů CORBA, který se neobejde bez vlastního, a velmi komplikovaného, API).

Pro kompletní podporu velice bohatých a flexibilních distribuovaných objektů stačí do Objective C přidat pouhých šest modifikátorů (z nichž jeden – inout – je navíc zbytečný a je v jazyce jen kvůli ortogonalitě s variantami in a out). S většinou z nich – in, out, bycopy, byref – jsme se již seznámili v odstavcích o předávání dat a objektů; jediný zbývající modifikátor je oneway, který se používá takto:

-(oneway void)foobar;

Určili jsme tak, že po odeslání zprávy foobar není třeba čekat na její ukončení. Kód, který zprávu odeslal, tedy ihned pokračuje dále, paralelně s odesíláním zprávy do jiného procesu či threadu a s jejím zpracováním.

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Začínáme s  

 

 

 

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: