MokaByte
Numero 16 - Febbraio 1998
|
|||
|
multiagente |
||
Massimiliano Baldinelli |
|
||
1.Introduzione
La grande novità
degli anni 90 nel campo dello sviluppo software è stata la programmazione
a oggetti (OOP, Object Oriented Programming), il cui principale vantaggio
consisteva nella possibilità di definire delle CLASSI che implementavano
una certa funzionalità e/o struttura dati. Il programmatore che
utilizzava una certa classe doveva semplicemente richiamare i suoi metodi
senza preoccuparsi di come questi implementassero al proprio interno le
funzionalità della classe. Questo rendeva un programma modulare,
e una classe poteva anche essere interamente riprogrammata (per esempio
con un'implementazione più efficiente) senza toccare una sola riga
del resto del programma, purché la nuova versione mantenesse gli
stessi nomi per metodi e proprietà pubblici della vecchia. Dal punto
di vista opposto, una stessa classe poteva essere riusata in più
programmi senza modifica alcuna, e questo ha portato alla nascita di una
filosofia di programmazione per componenti, e quello dello sviluppo
di classi specializzate nella soluzione di certi problemi o nell'implementazione
di certe funzionalità è diventato un mercato vero e proprio.
Nuovi linguaggi di programmazione sono nati, alcuni come estensione di
linguaggi tradizionali (come il C++, derivato dal C e che permette anche
una programmazione «mista», o il CLOS, estensione a oggetti
del Lisp), altri ex-novo (come Java, anch'esso derivato dal C ma impostato
totalmente sulla filosofia OOP).
Ora, a qualche anno di distanza, una nuova filosofia di programmazione sta emergendo, grazie soprattutto alla crescente diffusione di macchine sempre più potenti e di sistemi operativi multitasking, e soprattutto di ambienti distribuiti (reti locali, Internet). Questo scenario, infatti, permette di superare sia la programmazione tradizionale, in cui un programma gira su una macchina, sia la programmazione con approccio client-server, in cui un'applicazione è suddivisa in due o più moduli, che possono risiedere su macchine diverse e dialogano tra loro conservando però una gerarchia ben definita. Infatti, avendo a disposizione un ambiente di rete (sia locale che globale), si possono concepire applicazioni distribuite, formate da una comunità di componenti detti agenti che non solo comunicano fra loro, ma possono anche essere in grado di spostarsi da un computer a un'altro insieme con i propri dati.
La programmazione ad agenti, in effetti, può essere considerata l'applicazione pratica di quei concetti, come la programmazione concorrente e/o distribuita, che per troppo tempo sono rimasti pura teoria, confinata ai corsi universitari come PSN o in alcuni centri di ricerca. Un'applicazione realizzata tramite una popolazione di agenti, infatti, è concorrente in quanto ogni agente può essere composto da più processi o thread (sfruttando le capacità multithread di sistemi operativi come Linux, Windows95/NT, o i prossimi BeOS e Freedows), ma anche distribuita, in quanto l'insieme degli agenti, che girano su macchine diverse di una rete, concorrono a risolvere il problema generale (che può essere di Information Retrieval in ambito Internet/Intranet, di simulazione di processi vari, di monitoraggio, ecc.).
Come risultato del lavoro di molti ricercatori, nell'ambito delle Università e di grandi aziende, sono già apparse diverse librerie utilizzabili per creare applicazioni multiagente. La programmazione ad agenti è basata strettamente su quella a oggetti, in quanto un agente può essere definito in modo molto naturale come un oggetto, o meglio come una classe che incapsula le funzionalità implementate dall'agente, i cui metodi e proprietà pubblici definiscono i servizi che esso mette a disposizione degli altri agenti, servizi realizzati tramite metodi e proprietà privati o protetti. Si può ovviamente usare un qualunque linguaggio a oggetti, ma la natura inerentemente distribuita di un'applicazione multiagente e l'eterogeneità a livello hardware e di sistema operativo che si può incontrare in una rete di computer fanno preferire il linguaggio che per definizione è nato per programmare in ambienti con tali caratteristiche: Java.
Nel seguito si
descriveranno due librerie per la programmazione multiagente, entrambe
realizzate in Java, che affrontano il problema da due punti di vista, direi
complementari: l'Aglets Workbench e la JAFMAS.
2. Aglets: agenti mobili in un ambiente di rete
Gli IBM Aglets [1] sono oggetti Java che possono vagare sulla rete insieme ai propri dati, spostandosi da un host a un altro insieme ai propri dati, e sono il risultato della ricerca di un gruppo di ricercatori della IBM giapponese. L'architettura di questa libreria è basata su un'API (J-AAPI) che definisce metodi e proprietà di un aglet e dev'essere fornita dalla macchina su cui l'aglet deve girare. Ovviamente, in applicazione del principio del «write once, run everywhere», è ininfluente quale sia l'architettura e/o il sistema operativo della macchina ospite. Il pacchetto comprende un server a interfaccia grafica chiamato Tahiti, che permette di creare, clonare, spostare e compiere altre operazioni sui propri agenti.
2.1. Struttura della J-AAPI
Essa si compone di alcune classi e interfacce e 5 classi che forniscono i metodi e le proprietà per definire un aglet, il suo comportamento, le sue comunicazioni, migrazioni e riproduzioni («cloning»).
Il modello che esse definiscono prevede che un aglet giri come processo autonomo sulla macchina host, che è protetta da aglet che potrebbero danneggiarla dal cosiddetto «contesto», che fornisce un ambiente di esecuzione per l'aglet (questo viene creato da un demone di rete, che inserisce poi in esso gli aglets e può fornire un'interfaccia utente, grafica o a linea di comando); d'altra parte, questo viene schermato da un «proxy» rispetto alla macchina host, sia per impedire l'esecuzione diretta dei metodi degli aglets sia per rendere trasparente all'aglet la sua vera localizzazione. Gli aglets si scambiano messaggi, in maniera sincrona o asincrona, amministrati da un apposito gestore. Un aglet determina il suo itinerario secondo certe tecniche di routing e per tutta la sua esistenza ha un identificatore unico che lo distingue.
Per creare un aglet bisogna prima ottenere il contesto nel quale l'aglet opererà, con il metodo
public final AgletContext getAgletContext()(notare che questo è final, cioè non può essere ridefinito). In pratica, l'aglet che vuol creare un altro aglet eseguirà la chiamata
getAgletContext().createAglet(getCodeBase(), «SomeAglet», null);in cui SomeAglet è la classe dell'aglet che è in corso di creazione e al posto di null può esserci una qualche inizializzazione per il nuovo aglet, se occorre. I tre metodi successivi, invocati dal contesto, possono essere personalizzati per dotare l'aglet di un comportamento specifico.
È
il costruttore dell'aglet, e secondo gli autori non dovrebbe essere ridefinito.
Esegue l'inizializzazione
del nuovo aglet, ed è qui che si può agire per personalizzarlo.
Dà
il via al thread del nuovo aglet, in seguito a creazione, invio, ritiro
o attivazione.
public final Object clone()Questo metodo serve a ciò: esso clona l'aglet e il suo proxy, restituendo quest'ultimo al chiamante. Tale metodo potrebbe però essere sfruttato per attaccare un sistema ospite determinando l'esaurimento delle sue risorse, dal momento che si potrebbe programmare un aglet per clonare sé stesso all'infinito, e un rimedio, indicato dagli autori, consiste nel far sì che un sistema consenta la clonazione solo di aglets «certificati», di cui il sistema stesso si fida. Questo espediente previene anche un effetto analogo causato da errori di programmazione.
Viene chiamato
in seguito al tentativo di clonare un aglet. Di default genera un eccezione
di tipo SecurityException, e quindi dev'essere ridefinito per eseguire
le azioni più appropriate per l'aglet che viene clonato.
Serve a inizializzare
il nuovo clone.
Al termine dell'esecuzione di un aglet, devono essere deallocate tutte le risorse che il sistema ha riservato per esso e per il suo contesto: questo è importante perché l'esecuzione di solito avviene su server remoti, per i quali la disponibilità di risorse, in termini soprattutto di memoria allocata, è un aspetto critico. La deallocazione avviene tramite il metodo
public final void dispose()che non può essere ridefinito e uccide tutti i thread dell'aglet che lo chiama e provoca l'invocazione del metodo
public void onDisposing()che può essere ridefinito per compiere le azioni più appropriate, come chiudere i files e cancellare le finestre che l'aglet ha usato.
Una proprietà fondamentale degli aglets è la loro mobilità, implementata dal metodo
public final AgletProxy dispatch(URL destination)che invia l'aglet all'indirizzo che prende come argomento. Gli autori usano sempre Agent Transfer Protocol (atp) negli esempi, ma il protocollo effettivamente usato dipende dall'implementazione di J-AAPI e da quelli supportati dal server remoto. La chiamata a dispatch() genera un'altra serie di chiamate. In particolare:
permette
di personalizzare la fase di preparazione al trasferimento.
All'arrivo
viene invocato per reinizializzare l'aglet appena trasferito, e anche questo
può essere ridefinito.
retractAglet(someAgletURL)La chiamata
getAgletContext().retractAglet(someAgletURL);
cerca di richiamare indietro l'aglet specificato dall'URL che prende come argomento. Questo è formato dall'url del server remoto in cui l'aglet sta girando e dall'identificatore dell'aglet stesso:
www.paperopoli.com#identificatoreCome conseguenza viene invocato il metodo
public void onReverting(URL remoteURL)che può essere personalizzato all'occorrenza (anche per impedire il rientro). RemoteURL è il server da cui proviene la richiesta. Al rientro (se permesso) viene poi invocato OnArrival().
Gli aglets possono anche essere attivati e disattivati, per esempio memorizzandoli in dispositivi secondari.
Determina
la disattivazione di un aglet, che viene rimosso dal contesto e risvegliato
dopo un tempo stabilito dall'argomento duration (il sistema può
anche violare questa scadenza, in funzione di fattori come il carico).
Chiamato
in conseguenza di deactivate(), può essere ridefinito per compiere
determinate azioni in caso di richiesta di disattivazione.
Riattiva
un aglet e può essere anch'esso ridefinito in caso di necessità
per specificare cosa fare al momento della riattivazione.
Come detto in precedenza, ogni aglet gira in un contesto, che serve anche a proteggere il sistema da aglet dannosi o malfunzionanti. Un aglet ha accesso al suo contesto tramite il metodo getAgletContext(). È l'interfaccia AgletContext che provvede una serie di metodi per gestire la «vita» di un aglet nel suo contesto e i suoi rapporti con altri aglet ospitati in esso. Il metodo
public abstract AgletProxy createAglet(URL codeBase, String code, Object init)crea un aglet della classe specificata da code, cercando il file relativo in codeBase, passando poi init a OnCreation(), e restituisce un handle al proxy dell'aglet creato, da usare per manipolazioni successive. Nel caso in cui codeBase sia NULL, la ricerca avviene nelle directory specificate nella variabile di ambiente AGLET_PATH. Da notare che un oggetto di tipo URL permette di specificare sia indirizzi remoti (con prefissi http:, atp:, o quello che sia) sia locali, per mezzo del prefisso file:. In alternativa è possibile usare il metodo getCodeBase() per ottenere la directory in cui si trova il codice del creatore. Il metodo
public abstract Enumeration getAgletProxies()ottiene una lista degli aglets (di classe Enumeration) appartenenti al contesto corrente, compresi quelli disattivati. Avendo un oggetto Enumeration possiamo usare i metodi hasMoreElements() e nextElement() per testare la fine della lista e leggere il prossimo elemento. I metodi
public abstract AgletProxy getAgletProxy(AgletIdentifier identity)restituiscono il proxy di un aglet dato il suo identificatore (nel primo caso) o la coppia URL+identificatore (da usare nel caso di aglet remoto). La chiamata a uno di essi è necessaria per esempio nel caso in cui si voglia comunicare con un aglet, dato che sono i proxy ad occuparsi di questo. Ottenuto il proxy dell'aglet con cui vogliamo comunicare, abbiamo poi alcuni metodi che servono per gestire efettivamente la comunicazione. La coppia
public abstract AgletProxy getAgletProxy(URL host, AgletIdentifier identity)
public Object sendMessage(Message message)è messa a disposizione dal proxy per inviare un messaggio (passato come argomento) in modo rispettivamente sincrono o asincrono. Nel primo caso viene restituita la risposta stessa, nel secondo un handle per ricevere la risposta in seguito. Il metodo
public FutureReply sendAsyncMessage(Message message)
public boolean handleMessage(Message message)consente di gestire un messaggio ricevuto (il parametro) ed è tipicamente ridefinito dal programmatore. Viene restituito true o false a seconda che il messaggio sia riconosciuto oppure no. Per quanto riguarda i messaggi scambiati, il metodo
public Message(String kind[, ...])è il costruttore del messaggio, che costituisce un esempio di polimorfismo. I puntini opzionali stanno infatti a indicare un secondo argomento opzionale che, se presente, specifica un ulteriore dato che qualifica il messaggio (di un tipo base), mentre se non è presente crea un oggetto Hashtable. Il primo argomento, obbligatorio, indica invece la «specie» del messaggio. L'oggetto Message ha tre proprietà pubbliche: kind, arg, timeStamp, leggibili con i metodi getKind(), getArg(), getTimeStamp(); la terza proprietà è impostata automaticamente al momento della creazione. Il metodo sameKind(String kind) confronta poi la specie del messaggio che la invoca con la stringa passata come argomento. Se il messaggio creato è di tipo hash table, i metodi
public void setArg(String key, Object value)consentono di manipolarlo; questo tipo di messaggio può essere usato per trasmettere al destinatario più valori (organizzati in coppie
public Object getArg(String key)
public void sendReply([...])invia una risposta, che opzionalmente include un valore (passato come argomento);
public void sendException(Exception exception)può inviare al contrario un'eccezione, se la gestione del messaggio ha condotto a qualche errore. Se abbiamo inviato un messaggio asincrono (con sendAsyncMessage()), proseguendo poi per i fatti nostri, potremmo a un certo punto dover testare se è stata inviata una risposta. A questo scopo abbiamo a disposizione alcuni metodi. Il primo è
public Object getReply()che è un metodo dell'oggetto FutureReply restituito da sendAsyncMessage() che sospende il chiamante finché non arriva la risposta al messaggio. I metodi
public boolean isAvailable()servono invece a implementare una semantica non bloccante: il primo testa se una risposta è disponibile, gli altri due attendono rispettivamente la durata specificata o l'arrivo del messaggio.
public void waitForReply(long duration)
public void waitForReply()
Esiste un apposito gestore che si cura dei messaggi, in particolare esso rende deterministica la loro ricezione da parte del destinatario: questo riceverà due messaggi nello stesso ordine in cui sono stati trasmessi dal mittente (si dice che i messaggi vengono serializzati dal gestore dei messaggi). In questo modo la comunicazione tra due aglets resta deterministica, dato che un messaggio viene inoltrato al destinatario solo quando questo ha terminato l'elaborazione del precedente. Il metodo
public final MessageManager getMessageManager()permette a un aglet di accedere al proprio gestore dei messaggi, mentre il metodo
public void setPriority(String kind, int priority)imposta la priorità dei messaggi di specie kind al valore priority, cosicché l'aglet può decidere a quali messaggi dare la precedenza nella coda del gestore. Inoltre il metodo
public void exitMonitor()fa sì che venga creato un nuovo thread per la gestione dei messaggi che va in parallelo a quello già esistente (un nuovo messaggio può così essere elaborato simultaneamente a quello in corso), mentre i metodi
public void waitMessage()servono a gestire la sospensione dell'elaborazione di messaggi: il primo metodo sospende il thread che lo esegue, il secondo e il terzo risvegliano rispettivamente uno e tutti i thread sospesi del corrente gestore di messaggi. Infine il metodo
public void notifyMessage()
public void notifyAllMessages()
public void destroy()termina il gestore dei messaggi. In seguito alla chiamata di questo metodo, tutti i messaggi in coda vengono cancellati e quelli inviati successivamente rifiutati.
È notevole anche la gestione del multicast all'interno di un contesto. I canali multicast hanno comunque la limitazione di essere solo locali, cioè di essere definiti solo nel contesto corrente. Per gestire l'iscrizione di un aglet a gruppi multicast (basati sulla specie dei messaggi) abbiamo tre metodi:
public void multicastMessage(Message message)che invia il messaggio specificato come argomento a tutti gli aglets iscritti a quel tipo di messaggio.
2.4. Un possibile utilizzo
Come abbiamo visto, gli aglets sono degli oggetti il cui punto di forza è la mobilità, e che possono essere dotati di un comportamento peculiare tramite il metodo run() nonché tramite i metodi invocati automaticamente in seguito alla creazione, inizializzazione, attivazione/disattivazione, terminazione, invio/arrivo/richiamo (i vari OnXXX()).
Il collante di un insieme di aglets è dato da quello che i loro autori chiamano il «contesto», e questo fatto può essere sfruttato per interessanti applicazioni. Infatti immaginiamo di avere diversi ambienti nei quali vive una popolazione (di agenti mobili, ognuno programmato in un certo modo e quindi dotato di una qualche forma di «intelligenza»). Ogni individuo di questa popolazione abita quindi in un «universo» costituito da più computer collegati in rete.
Lo scenario potrebbe essere questo: un certo particolare ambiente viene implementato in un host da uno o più aglets «fissi», cioè che non si spostano mai (è possibile ottenere questo comportamento ridefinendo il metodo onDispatching() di una classe di aglets in modo tale che come unica azione generi l'eccezione SecurityException). Gli agenti che vivono in tale ambiente sono invece costituiti da aglets mobili, e allora il «mondo» ospitato su un certo host è costituito dal «contesto» che raggruppa gli aglets fissi (l'«ambiente») e quelli mobili eventualmente presenti (gli «agenti»), che possono anche non esserci, cioè quell'ambiente può essere disabitato in un certo istante.
Gli stimoli con cui l'ambiente sollecita gli agenti e le risposte degli agenti che modificano l'ambiente sono allora costituite da un normale scambio di messaggi fra aglets, così come l'interazione fra agenti. Un agente può sperimentare diversi ambienti semplicemente trasferendosi da un host a un altro nella rete (l'«universo» degli agenti), anche pianificando i suoi spostamenti tramite scambio di informazioni con agenti remoti. Il comportamento dell'agente viene specificato, come detto, dai metodi run(), OnXXX() e dal costruttore, e può quindi incorporare qualunque tipo di logica sia rappresentabile con un metodo Java. Per esempio, un agente può decidere di trasferirsi in un host piuttosto che in un altro comunicando con altri agenti che vivono negli host presi in esame ed esaminando le risposte (o in base a esse decidere di non spostarsi affatto). Da notare che l'agente può anche prendere una decisione «sbagliata», cioè, in base alla sua logica e ai dati che riceve dall'esterno, decidere di trasferirsi in un mondo meno «ospitale» di quello in cui ora si trova.
È chiaro
allora che agenti dotati di logiche o basi di conoscenza o «necessità»
diverse, vivendo e spostandosi fra i vari «mondi» (o «ambienti»,
cioè hosts) dell'«universo» (cioè la rete) potrebbero
trovare collocazioni ideali diverse, o estinguersi, o entrare in competizione
con altri agenti per lo stesso ambiente modificando i propri «geni»
(cioè la propria logica, conoscenza o necessità). Si potrebbe
perciò implementare in una rete di calcolatori un'interessante simulazione
dell'evoluzione darwiniana, soprattutto se si riuscisse a realizzare un
meccanismo di «mutazione» casuale o ambientale, che dovrebbe
entrare in azione per esempio quando un agente si «riproduce»
(con i metodi clone() e onClone()).
3. JAFMAS: Java-based Agent Framework for Multiagent Systems
3.1.
Generalità
Un limite degli IBM Aglets
risiede nella mancanza del supporto a forme di comunicazione «speech-act»,
nell'assenza di possibilità di coordinazione, cooperazione e coerenza
fra i vari agenti di un sistema (che annulla così i vantaggi della programmazione
distribuita e del calcolo parallelo che gli agenti permettono) e nell'uso del
protocollo TCP-IP che consente solo comunicazioni dirette. La libreria JAFMAS
intende risolvere tali difetti, presenti negli aglets così come in molti
tool analoghi, ed è quindi incentrata sul coordinamento dei componenti
di un programma multiagente: essa è composta di 16 classi Java che mettono
a disposizione del programmatore degli strumenti per definire sistemi multiagente
in grado di operare mantenendo coerente e consistente lo stato globale della
computazione pur senza forme di controllo centralizzato.
In questo quadro viene data enfasi al concetto di «conversazione» fra due o più agenti, cioè il protocollo secondo cui un gruppo di agenti si scambia della conoscenza può essere espresso tramite qualche linguaggio di tipo conversazionale (fra cui KQML [3]). Per giustificare questo approccio si fa ricorso alle reti di Petri, con le quali vengono modellate le conversazioni fra agenti, e di cui viene dato anche un veloce richiamo in [2]. Nelle intenzioni del suo autore, infatti, un'applicazione multiagente programmata con JAFMAS è in pratica una comunità di agenti che «conversano», per esempio con il formalismo KQML, e si coordinano per risolvere il problema dato.
3.2. Utilizzo della libreria
JAFMAS consiste di sedici classi Java e di una metodologia per lo sviluppo di applicazioni. Quest'ultima si compone di 5 passi:
CreateAgent, Agent, Conversation, ConvRule sono le sole classi che il programmatore deve ridefinire per creare agenti dal comportamento specifico per l'applicazione di interesse, mentre tutti gli aspetti di comunicazione, coordinamento fra agenti e coerenza globale del calcolo sono incapsulati dalle altre classi. In particolare, concretizzando il punto 5. della metodologia prima esposta, si dovranno implementare le suddette quattro classi nel seguente ordine:
CreateAgent:
Conversation:
Dovrà
essere creata un'estensione di tale classe per ogni automa a stati finiti
(rappresentante una conversazione) definito al punto 2. della metodologia
di programmazione di JAFMAS. Inoltre si deve ridefinire il metodo
4. Altri ambienti di programmazione per sistemi multiagente
Verrà dato per concludere un breve cenno su altri tre pacchetti per la programmazione di applicazioni multiagente, caratterizzati dal fatto di essere 100% Java e di usare estensivamente le caratteristiche della versione 1.1 del JDK (come l'invocazione di metodi remoti, RMI).
4.1 Odyssey
Si tratta del frutto del lavoro della General Magic, arrivato alla beta 2. Questa compagnia sviluppò il linguaggio Telescript per programmare agenti mobili, che ebbe notevole successo, e quando iniziò l'affermazione di Java per la creazione di applicazioni multipiattaforma essa creò l'ambiente Odyssey [4] (in Pure Java), in cui incorporò i concetti già sviluppati per Telescript. Attualmente Odyssey è gratis per usi non commerciali, e GM non ha ancora deciso se diverrà un prodotto commerciale in future release.
Odyssey provvede il supporto per la tecnologia DCOM della Microsoft. Inoltre, l'output di un'applicazione può essere diretto allo standard output, a una finestra oppure a un file sull'host in cui essa viene eseguita, caratteristica questa utile in fase di debugging. Questo però comporta degli inconvenienti: se per esempio l'output è diretto a una finestra, negli intervalli di tempo in cui l'agente non si trova nella macchina virtuale in cui essa è stata definita non si vedrà alcun output di debugging, e questo è dovuto al fatto che Java non ha un supporto nativo per il displaying remoto (meglio sarebbe stato loggare tale output su qualche file durante tali intervalli). Inoltre se l'output di debugging è diretto su un file, l'agente non può muoversi e ogni tentativo di migrare genera un'eccezione, tutto ciò a causa della natura non serializzabile dei file.
Una caratteristica presente ma non ancora documentata è la possibilità per gli agenti di «darsi appuntamento» su particolari macchine della rete. Parimenti non ancora documentata è la possibilità da parte di un agente di «pubblicare» degli oggetti, rendendo disponibili dei riferimenti ad essi a tutti gli altri agenti dell'applicazione. Un limite è inoltre il fatto che solo un server Odyssey può essere eseguito su un dato computer, per cui il testing di applicazioni rende necessario l'accesso a più macchine distinte mentre non è possibile eseguirlo lanciando più istanze della JVM sulla stessa macchina fisica.
4.2 Voyager
È un prodotto ObjectSpace, ora alla versione 1.0 beta 2.1 [5], utilizzabile liberamente per uso interno. Esso consiste di due librerie, voyager.core.jar e voyager.tools.jar (archivi compressi), delle quali solo la prima è necessaria, mentre la seconda contiene dei tools per lo sviluppo (nessun sorgente disponibile).
La comunicazione fra agenti avviene per mezzo del Virtual Object. Voyager dispone del Virtual Code Compiler, che prende una classe (formato sorgente o .class) e la modifica ottenendo una sorta di «mirror» della classe, che può essere istanziato, con il quale si può comunicare e che può essere spostato in una rete. Un oggetto trasformato con VCC assume le caratteristiche di un agente; esso non dispone però necessariamente di un proprio thread, cioè a meno che non sia definito diversamente, esso è solo un oggetto passivo. La comunicazione è sincrona e asincrona, esiste l'invocazione remota di metodi e inoltre riferimenti a oggetti virtuali remoti possono essere passati come parametri. Il server degli agenti, chiamato «voyager», non deve girare necessariamente su tutte le macchine della rete.
Un difetto consiste nel fatto che VCC elabora una classe solo se questa è sintatticamente corretta. Poiché di solito il codice delle classi contiene riferimenti ad altre classi (di oggetti virtuali), questo obbliga il programmatore a definire delle classi vuote per poter compilare correttamente con VCC. Questo dovrebbe essere risolto in futuro, come pure saranno fornite altre caratteristiche come il supporto per il protocollo UDP e la comunicazione multicast e una maggiore attenzione all'aspetto sicurezza.
4.3 Concordia
Si tratta del progetto a più ampio respiro della Horizon [6], una divisione Mitsubishi (non esiste una beta pubblica). Esso utilizza interamente il protocollo RMI per il trasporto e la comunicazione, ed ha un'architettura totalmente modulare. Il modulo fondamentale è il Concordia Server, che fornisce i servizi di comunicazione e l'ambiente di esecuzione degli agenti. In ogni nodo della rete gira uno o più di tali moduli, mentre un solo modulo Administration Manager è necessario in tutta la rete. Quest'ultimo fornisce alcuni servizi come sicurezza, gestione eventi e migrazione di agenti, e per motivi di robustezza o di amministrazione condivisa possono comunque esisterne più di uno nella rete.
Il Persistence Manager si occupa della tolleranza ai guasti, e a tale scopo gestisce le informazioni di stato dei vari agenti nonché il restarting degli stessi. Il Queue Manager invece si occupa della migrazione degli agenti, in tutti i suoi aspetti di mantenimento dello stato (attraverso il Persistence Manager), di nuovi tentativi di migrazione dopo eventuali fallimenti dovuti a disconnessioni o altro, e della priorità con cui gli agenti vengono eseguiti in un particolare server. Questa coppia di moduli quindi fornisce una robusta tolleranza ai guasti, maggiore rispetto ad altri pacchetti per la programmazione ad agenti, e tutto ciò è trasparente (per esempio i tentativi di migrazione o il ripristino dello stato) sia agli agenti che agli amministratori del sistema.
Il modello cooperativo è fornito dal modulo Event Manager, che realizza un modello asincrono in cui gli agenti generano eventi e ascoltano eventi generati da altri agenti. La classe AgentClass poi permette a un gruppo di agenti di cooperare in maniera simile ai meeting di agenti implementati da Odyssey, mentre dovrebbe essere in via di sviluppo il supporto alla comunicazione diretta fra due agenti (bypassando l'Event Manager).
Infine, la sicurezza è realizzata tramite SSL3 (Secure Socket Layer v.3) durante lo spostamento degli agenti e la cifratura dello stato degli agenti durante la loro memorizzazione su supporti persistenti. Ogni agente ha associato anche uno «user id», in modo che il sistema che lo riceve può impostare i giusti permessi di accesso. Tutto ciò è realizzato dal Security Manager, che può supportare anche meccanismi tipo i certificati VeriSign, permettendo così lo sviluppo di applicazioni su rete geografica (anche Internet).
5. Conclusioni
Fra i vari sistemi analizzati il Concordia sembra il più promettente, a causa della sua architettura innovativa, ma rischia di venir penalizzato dal ritardo della sua disponibilità (l'alpha-testing è possibile solo alle organizzazioni commerciali interessate al suo impiego, e non è ancora prevista una licenza per scopi di ricerca e didattici). D'altra parte gli IBM Aglets hanno il punto di forza nella ricchezza della gestione della mobilità degli agenti, mentre JAFMAS (che non è il frutto dello sforzo di un'organizzazione commerciale come gli altri ambienti, ma è stato sviluppato da **** **** nell'ambito delle sue ricerche da tesista a ****) è interessante per gli aspetti della coordinazione degli agenti e del modello conversazionale utilizzato. Solo il tempo potrà quindi dire quali ambienti si affermeranno e in quali settori dello sviluppo software.
6. Bibliografia
Il materiale
utilizzato per la stesura di questo articolo proviene in gran parte dalla
documentazione presente ai siti [1] e [2]. Purtroppo l'Aglets CookBook
presente in [1] è ancora incompleto. In [3] è contenuta la
specifica per il formalismo KQML citato al paragrafo 3. su JAFMAS, mentre
in [2] viene fornito un breve richiamo delle reti di Petri, nonché
l'elenco completo di metodi e proprietà delle classi JAFMAS. In
[2] si trova anche una panoramica più articolata delle varie librerie
esistenti per la programmazione ad agenti (non tutte implementate in Java),
precisamente nel file AgentReview.ps (Attenzione: i documenti contenuti
in [2] sono per lo più in formato PostScript, per cui si dovrà
disporre di un programma in grado di leggere tale formato, come l'ottimo
GhostScript+GhostView sotto Windows).
[1] IBM Aglets
- http://www.trl.ibm.co.jp/projects/s7230/aglets/index_e.htm
[2] JAFMAS -
http://www.ececs.uc.edu/~abaker/JAFMAS
[3] KQML - http://www.cs.umbc.edu/kqml/
[4] Odyssey
- http://www.genmagic.com/agents/
- http://www.genmagic.com/agents/MAF/
[5] Voyager
- http://www.objectspace.com/Voyager/
[6] Concordia
- http://www.meitca.com/HSL/Projects/Concordia
|
||
MokaByte ricerca
nuovi collaboratori.
|
||
|