Szeretném egy személyes "vallomással" kezdeni: meggyõzõdésem, hogy a programozásban a jövõ, legalábbis a közeljövõ az elosztott objektumorientált rendszereké. Kisebb-nagyobb felhasználói és fejlesztõi csoportosulások évek óta foglalkoznak ilyen rendszerek fejlesztésével, legismertebb, legelterjedtebb és sok szempontból legkiforrottabb közülük talán az Object Management Group (OMG) konzorcium által szabványosított CORBA (Common Object Request Broker Architecture) rendszer.
Az objektumorientált rendszerek világába egyszer csak berobbant a Jáva, fenekestül felforgatva ezt a világot. Az idén januárban megjelent JDK 1.1-es verziója komoly lépéseket tett az elosztott objektumorientált rendszerek felé. Ennek az iránynak az egyik képviselõje a JDK 1.1-beli RMI (távoli módszerhívás) könyvtár, amelyet múlt hónapban mutattam be. Sokat töprengtem, mirõl is írhatnék ebben a hónapban, tervezgettem, hogy a Jáva és a CORBA technológiák alakuló összefonódásáról írok, hiszen ez is nagyon érdekes és várhatóan széles körben elterjedõ rendszer, de jelenleg az RMI és a Jáva-CORBA hívek között éles hitvita dúl és én egyelõre nem látok tisztán, nem tudok állást foglalni.
Viszont már néhány hónapja olvastam egy igen érdekes új elképzelésrõl, az "utazó" ügynökökrõl (mobile agent). Az IBM egyik kutatócsoportja (http://www.trl.ibm.co.jp/aglets) igen érdekes eredményeket ért el az ún. aglet-ek (Jávában implementált utazó ügynökök) létrehozását támogató könyvtárak kifejlesztésével. A rendszer izgalmas, igéretes, úgyhogy csaknem belefogtam az ismertetésébe, amikor néhány napja (1997. április 8-án) egyszer csak felbukkant az ObjectSpace (http://www.objectspace.com/) programház Voyager nevû, JDK 1.1-en alapuló ORB környezetének próba változata. Elolvastam a dokumentációkat és alig akartam elhinni, hogy ilyen hatékony, egyszerû rendszert Jávában meg lehet valósítani. Mintha programozástechnológiai témájú sci-fi könyvet olvasnék! De minden igaz, a technológia létezik! Csak még annyit a cégrõl, hogy õk alkották az eddig - méltatlanul - kevés publicitást kapott JGL (Java General Library) adatszerkezet- és algoritmuskönyvtárat. A JGL és mostani Voyager rendszer egyrészt "tiszta Jáva" (követi a Sun által meghirdetett "100% pure Java" ajánlást), másrészt még üzleti felhasználáshoz is teljesen ingyenes! Kemény versenytársa akadt az RMI-nek!
Megjegyzés: nem szeretném elhamarkodottan összehasonlítani az ObjectSpace és az IBM megoldásait, nem azért írok a Voyager-rõl, mert az egyértelmûen jobb, hanem azért, mert beleszerettem!
A fent vázolt rendszer jól modellezi a postai levéltovábbítást, legalábbis a posta ügyfelei, a feladó és címzett szempontjából. Azonban az igazi posta "utazó ügynököket", a postásokat alkalmaz a levelek kézbesítéséhez. A postás fizikailag is "odamegy" a címzetthez és maga helyezi el a postaládában a levelet. Miért jó egy ilyen megoldás? Az ügynök elég intelligens lehet ahhoz, hogy helyben a levéllel különbözõ kiegészítõ tevékenységeket hajtson végre. Például ajánlott levélnél aláírathatja a kézbesítési könyvet a címzettel, ha nincs senki otthon, a csomagot beadhatja a szomszédoknak, esetleg megkérdezi a házmestert, tudja-e az elköltözött lakók új címét vagy begyûjtheti a küldendõ postát.
Kissé szakmaibb zsargont használva, az ügynök a levélben tárol információval együtt eljárásokat, algoritmusokat is "hordoz". Persze ezeket az algoritmusokat elhelyezhetnénk a helyi rendszerben tanyázó, statikus, helyhez kötött ügynökökben is, ám így a rendszerünk nagyon nehezen bõvíthetõ, hiszen minden egyes új algoritmust minden lehetséges címzett ügynökkel meg kellene taníttatnunk. Ennél egyszerûbb, ha az algoritmus is utazik. Persze az utazó ügynök ettõl még szóba állhat a helyi ügynökökkel, sõt rendes elosztott rendszerhez méltóan távoli ügynökökkel is kommunikálhat.
Ezen túl a JDK 1.1-ben megjelent objektum sorosítás (object serialization) eszközt nyújt Jáva objektumok hálózaton átküldésére. A szintén most megjelent Reflection könyvtár pedig lehetõséget adott ahhoz, hogy egy program - itt például az ún. bróker - futás közben is felderíthesse egy másik objektum, illetve osztályának szerkezetét.
A Voyager érdekessége, hogy a virtuális objektumokat reprezentáló osztályt egy program (vc) hozza létre vagy a .java forráskódból, vagy akár ennek hiányában a már lefordított .class állományból. Nem kell külön interfészeket készítenünk. A virtuális osztály mindent tud, amit az eredeti (legalábbis annak nyilvános felülete), beleértve a konstruktorait is. Ám az összes meglévõ konstruktor kibõvül egy paraméterrel, amelyik a virtuális objektum valós párjának helyét adja meg. Ez lehet akár egy távoli gép is, ilyenkor a helyi virtuális objektum létrehozásával együtt elkészül a távoli párja is. Lehet, hogy azon a gépen nincs is ott az osztály kódja? Sebaj, a bróker ezt automatikusan áttölti, mi észre sem vesszük. Ezen túl lehetõségünk van egy már létrehozott objektumot a move módszerrel bármikor másik gépre átmozgatni, ilyenkor az objektum teljes állapotával együtt átkerül az új címre.
A távoli objektumok ugyanúgy a referenciákat figyelõ szemétgyûjtés hatáskörébe tartozhatnak, mint helyi társaik, de lehetõségünk van az objektumokat idõhöz kötött feltételek alapján automatikusan is megszüntetni.
A Voyager-ben többlet, hogy a - módszerhívásokkal szintaktikailag teljesen azonos - ún. szinkron üzenetátadáson túl - amikor az aktiváló bevárja a távoli módszer lefutását - használható egyirányú (Oneway) illetve aszinkron (Future) üzenetküldés is.
Az üzenetátadás további érdekessége, hogy megtalálja az esetleg tovább vándorolt objektumokat is, mert ezek vándorlásuk közben "titkárokat" hagynak maguk után, akik megszervezik az üzenetek átadását, sõt biztosítják azt, hogy a következõ üzenetváltás már közvetlenül az új helyen lévõ objektummal történjen.
A másik lényeges különbség az ügynökök mozgásakor látszik. Mivel egy ügynök aktív objektum, áthelyezés után folytatni szeretné a tevékenységet. Sajnos - néhányak szerint szerencsére - a Jáva sorosító könyvtára csak a kazalon (heap) tárolt információkat (objektumokat) tudja átvinni, a verem tartalmát és ezzel a futás aktuális állapotát nem. Így az ügynöknek a move parancs kiadása elõtt minden fontos információt a kazalon kell tárolnia - ha még nem lenne ott -, valamint a move-nak meg kell adni annak a módszernek a nevét, ahol a futását folytatni akarja, miután az új helyre megérkezett.
A bróker implementáció a teljesítményt összehasonlító adatok alapján nagyon jó, az üzenetátadások távoli objektumok esetén 20-30%-kal gyorsabbak, mint pl. az RMI-ben, azonos gépen elhelyezkedõ virtuális objektumok között pedig néha 3 nagyságrenddel (nem tévedés!) is gyorsabb lehet.
Svejkünk (egy Person típusú objektum) otthonról (egy Home) indul el, egyesével bejárva minden egyes, a listáján (Vector pubs) szereplõ kocsmát (Pub-ok). A "bejárás" azt jelenti, hogy az ügynök átkerül a kocsmát futtató számítógépre, mûködését ott folytatja. A kocsmákban bejelentkezik (visit) és hazaszól (message), majd vár egy kicsit és továbbmegy, végezetül hazamegy (returnHome), ha már az összes kocsmát végigjárta.
A példa terjedelmi okok és a könnyen érthetõség érdekében szándékosan egyszerûsített. A hibakezeléssel nem foglalkoztam, de a Voyager rendszer erre kevésbé is érzékeny, mint az RMI, lévén, hogy az itt elõálló hibák a Jáva futási hibáinak kategóriájába tartoznak, azaz nem kötelezik a programozót hibakezelõk írására, csak lehetõvé teszik azt.
public class Home { public Home () { System.out.println("Home created."); } public void message (String msg) { System.out.println(msg); } } public class Pub { private String name; public Pub (String name) { this.name = name; System.out.println("Pub " + name + " created."); } public String getName() { return name; } public void visit (String visitor){ System.out.println("Pub " + name + " visited by " + visitor + "."); } }
import java.util.*; import COM.objectspace.voyager.core.*;Ne lepõdjünk meg az importált könyvtár nevének hosszán, végre egy cég betartja a Sun által kitalált, de be nem tartott elnevezési konvenciót. A név eleje a cég Internetes neve.
public class Person extends Agent { private Vector pubs; private int index = 0;A fenti dinamikus tömb tárolja majd a meglátogatásra váró kocsmák listáját, index jelzi, hogy éppen hol tartunk.
private String name = null; private VHome home = null; public Person (String name, VHome home) { this.name = name; this.home = home; pubs = new Vector; home.message(name + " is born."); };A konstruktor tárolja a személyünk nevét és otthonát. VHome egy olyan osztályt jelöl, amelyet a vc generátor program a Home osztályból állított elõ. Itt virtuális objektumot kell használnunk, hiszen személyünk a vándorlása során távol kerülhet az otthonától, visszaszólni csak egy virtuális segéd-objektumon keresztül tud majd.
A kocsmák dinamikus tömbjét az addPub-bal lehet bõvíteni. Svejk soha nem feledkezik meg egy kocsmáról, ezért a tömbbõl törlés mûveletét nem valósítottuk meg.
public void addPub (VPub pub) { pubs.addElement(pub); }Az ügynökök vándorlásának trükkjét a nextPub módszer belseje rejti. Elõször kivesszük a tömb soron következõ elemét, már ha akad ilyen. A move módszer segítségével az ügynök objektum "átugrik" az elsõ paraméterként megadott kocsma objektum mellé. Az ügynök a tevékenységét az új helyen a proceed eljárással folytatja. Amennyiben nincs több kocsma, az ügynök, hazamegy, azaz a home objektumból megkapott címre költözik, ahol is a returnHome módszert hajtja majd végre.
A módszer az ügynökök vándorlását lehetõvé tevõ mindkét move módszerre példát mutat, az egyiknél egy távoli objektumot, másikban egy kiszolgáló címét adjuk meg.
private void nextPub () { if (index < pubs.size()) move((VPub) pubs.elementAt(index++), "proceed"); else move (home.getApplicationAddress(), "returnHome"); } public void leaveHome () { index = 0; if (pubs.size() > 0) nextPub(); else { home.message ("No pub to go!"); die(); } }Az otthonról elindulás csak akkor sikeres, ha van egyáltalán kocsma a listánkon. Ha nincs, kiírunk egy figyelmeztetõ üzenetet és megállítjuk az ügynök futását.
A proceed eljárás törzse definiálja a kocsmában zajló tevékenységet. Mivel ide úgy kerülünk, hogy egy objektum mellé ugrunk, a Voyager rendszer a lokális objektumot paraméterként átadja a módszernek.
public void proceed (Pub current) { current.visit(name); home.message(name + " is at pub called " + current.getName());Elõször beköszönünk az aktuális kocsmába (visit), majd hazaszólunk, hogy hol is vagyunk (message). Ne feledjük, hogy az elsõ helyi-, a második viszont egy távoli módszerhívás!
try { Thread.sleep (5000); } catch (InterruptedException e) {}; nextPub(); }Várakozunk egy kicsit - 5 másodperc elég egy számítógépes Svejknek, hogy 2 sört legurítson -, majd továbbállunk.
Hazatérve a returnHome módszerre kerül a vezérlés. Ennek a módszernek a proceed-del ellentétben nincs paramétere. Itthon kiírunk egy üzenetet és csendben jobblétre szenderülünk. Persze "valósághûbb" szimulációban inkább várakoznánk egy keveset és a leaveHome-mal kezdhetnénk elölrõl a kocsmázást!
public void returnHome () { home.message(name + " returned home!"); die(); } }
import COM.objectspace.voyager.core.*; public class PubDemo { public static void main (String[] args) { VPub pub; VHome home = new VHome("localhost:4000/Home"); VPerson svejk = new VPerson ("Svejk", home, "localhost/Svejk");Figyeljük meg az egyes konstruktorok hívását. Ezek elsõ paraméterei megegyeznek a megfelelõ konkrét osztály (Home illetve Person) konstruktorainak, az utolsó viszont egy gép:kapu/becenév stílusú címdefiníció, ahol a gép és a kapu a brókert azonosítja, a becenév pedig a brókeren belül nevezi el az objektumunkat. (Megjegyzés: a 4000-s helyett használhatnánk csaknem tetszõleges más kaput is.)
for (int i = 0; i < args.length; i++) { pub = new VPub(args[i], args[i] + ":4000"); svejk.addPub(pub); };A "távoli" kocsmák létrehozására hasonlóan történik. A virtuális osztály konstruktorának utolsó, kiegészítõ paramétereként itt a kocsmákat tartalmazó, a program paramétereként megadott gép nevét adjuk meg, kiegészítve egy szabadon választott kapu számával. Ezen a kapun a Voyager brókere kell, hogy várakozzon, viszont így egy távoli gépen hoztunk létre új objektumot. Gondoljunk csak bele, az RMI nem támogatja a távoli objektum létrehozást! Az elkészült kocsmát fel is vesszük Svejkünk listájára.
Az résztvevõ objektumokat elindítva meglökjük Svejket, hogy ideje útra kelni. Eztán a programunkat akár le is állíthatjuk, objektumaink önálló életet élnek
svejk.leaveHome(); Voyager.shutdown(); } }
vc Home vc Person vc PubMegjegyzés: itt a vc fordító a .java forrásállományokból dolgozik, de ezek hiányában hajlandó a .class állományt is használni. Eztán már lefordíthatjuk az összes forráskódunkat
java Home.java Person.java Pub.java PubDemo.javaFuttatás elõtt az összes részvevõ gépen (ahol a Home illetve a Pub példányok lesznek), el kell indítanunk a brókert a 4000-s kapun:
Voyager 4000Eztán már csak a program indítása marad hátra:
java PubDemo egyik.gép másik.gép harmadik.gépEnnyi az egész, kezdõdhet a kocsmába járás!
updated: 97/05/09, http://www.eunet.hu/infopen/cikkek/java/agent.html