![]() |
Università degli Studi di Cagliari |
Facoltà di Ingegneria - Corso di laurea in Ingegneria Elettronica |
TESINA |
di Ingegneria del Software |
Anno accademico 1997/98 |
ICARO |
Studio della installazione dell'Aglet Software Development Kit in ambiente Linux e sviluppo di un sistema di monitoraggio di server attraverso agenti mobili |
Docente:
|
Realizzatori:
|
Introduzione
PresentazioneNell'ambito dello studio di applicazioni ad oggetti distribuiti una delle tecnologie proposte si basa sul concetto di agente mobile. Tra i vari prodotti commerciali disponibili ha riscosso un buon successo quello sviluppato da I.B.M. Japan, basato sul linguaggio Java. Questo sistema ad agenti, denominati Aglets, offre alta portabilità, una architettura robusta e sicura, unite ad un supporto completo per il programmatore.
L'ambiente di sviluppo degli Aglets (ASDK - Aglets Software Development Kit) è interamente realizzato in Java ed è liberamente scaricabile dal sito I.B.M.; questo particolare, unito al fatto che anche il JDK è distribuito gratuitamente dalla Sun Microsystems, rende gli Aglets una soluzione economica per implementare oggetti distribuiti.
Benchè scritto in 100% Pure Java, l'ASDK è stato testato (ed è in un certo senso "garantito") solo sulle piattaforme commerciali Microsoft Windows 95/NT, Sun Solaris e SunOS, I.B.M. OS/2 e AIX. Partendo dal presupposto di portabilità di codice Java in diverse piattaforme, scopo del nostro progetto è
Il progetto è stato denominato ICARO, le cui vocali individuano l'acronimo Intelligent Agent-based Observer.
Introduzione agli agenti mobili e all'architettura Aglets
Gli agenti mobiliNegli ultimi anni si è sviluppato il paradigma ad oggetti distribuiti che, sfruttando le moderne architteture di rete, si prefigge l'obiettivo di rendere possibile la realizzazione di sistemi di calcolo potenti ed estremamente flessibili. Locale e remoto perdono di importanza e diversi oggetti, appunto "distribuiti", cooperano per ottenere il risultato richiesto dall'utente. In una visione di questo tipo rientrano esempi di macchine parallele virtuali, database eterogenei e soprattutto la possibilità di ottenere grosse capacità di calcolo combinando assieme diverse componenti semplici. Accanto alle soluzioni "statiche" (in cui gli oggetti, pur essendo distribuiti risiedono permanentemente su una macchina) come CORBA del consorzio OMG, DCOM di Microsoft e RMI della JavaSoft, esiste la cosìddetta tecnologia ad agenti mobili. La loro caratteristica principale consiste nell'essere oggetti distribuiti dotati appunto di mobilità, cioè della possibilità di spostarsi dinamicamente da una macchina ad un'altra a seconda del compito da svolgere.
Oltre a questa caratteristica peculiare, gli agenti mobili possono essere sviluppati in modo che siano autonomi e reattivi rispetto all'ambiente di esecuzione in cui si trovano (per esempio in grado di ricevere dal sistema la notifica di un imminente shutdown e di conseguenza decidere di migrare verso un'altra macchina), dotati della possibilità di comunicare ed eventualmente di "imparare" durante il loro ciclo di vita.
Vi sono diverse buone ragioni per utilizzare la tecnologia ad agenti mobili:
Tra le diverse soluzioni proposte una delle piu' sofisticate e' quella degli Aglets sviluppata da I.B.M. e basata sul linguaggio Java.
L'architettura AgletsGli Aglets sono una architettura ad agenti mobili interamente sviluppata in Java; questa prima caratteristica li rende intrinsecamente multipiattaforma. Per poter utilizzare gli Aglets è sufficente disporre di una Java Virtual Machine e di uno stack TCP/IP nel proprio sistema operativo.
Sostanzialmente l'architettura degli Aglets è basata su:
Applet, così il
primo passo per la creazione di un aglet consiste nel dichiarare una sottoclasse di Aglet
L'uso di Java fornisce in partenza agli Aglets caratteristiche di multithreading, sicurezza nonchè le potenzialità offerte dalla programmazione ad oggetti. I programmatori I.B.M. hanno ulteriormente potenziato queste caratteristiche di Java con delle soluzioni dedicate quali ad esempio i proxy (utilizzati per aumentare la sicurezza degli aglet all'interno di un contesto) e un sistema di messaggistica.
Tutti gli elementi qui descritti costituiscono l'ASDK (Aglet Software Development Kit).
Installazione ASDK in ambiente Linux
PreliminariI requisiti di sistema:
Per poter installare l'ASDK (Aglets Software Development Kit) sulla propria macchina è necessario disporre del JDK (Java Development Kit) versione 1.1 o successiva. Per l'ambiente Linux non è disponibile il JDK ufficiale sviluppato dalla Sun, mentre esiste un porting indipendente (aggiornato all'API 1.1) sviluppato appositamente per Linux e liberamente scaricabile dalla rete.
Pur avendo utilizzato l'ultima versione disponibile della distribuzione
Debian (1.3.1) si aveva a
disposizione una versione di tale JDK che non implementava tutte le API 1.1 più recenti, in particolare la
libreria di classi e la relativa interfaccia sulla JVM (Java Virtual Machine) risultava carente nel supporto al
sistema grafico (X11 - Motif) e ad alcune nuove estensioni del linguaggio. La versione in questione è la
chapman: 10/12/12-23:12. Tali errori si manifestavano già in fase di installazione,
a causa del fatto che il pacchetto ASDK disponibile era in formato .class autoinstallante
(realizzata attraverso InstallShield).
gulch$ java Agletsl_0
|
In pratica il file classes.zip fornito con la versione chapman conteneva le tutte dichiarazioni
dell'API 1.1, che però non trovavano implementazione sulla JVM (da qui il messaggio no awt in shared library
path in /usr/lib/jdk/i586/libawt.so).
È stato necessario installare una versione del JDK sempre compatibile con l'API 1.1 ma dotata di pieno
supporto a tutta la libreria. La versione risultata pienamente rispondente alle esigenze
è la 1.1.3 (versione sbb:08/16/97-14:45). Il file "incriminato" (jdk/i586/libawt.so) è
passato da 211018 byte a ben 1877689 byte!
Installazione JDKUna volta scaricato e decompresso l'archivio (LINUX.JDK.1.1.3-v2.tar.gz) la procedura di installazione si
riduce a settare alcune variabili d'ambiente e alcuni link simbolici. Supponendo d'aver installato il JDK
nella directory /usr/local/jdk sarà necessario includere nel PATH la posizione
degli eseguibili java e javac
Nota: si presuppone 1'uso di BASH come SHELL di sistema
inserendo
PATH=$PATH:/usr/1oca1/jdk/bin
|
nel file /etc/profile (nella configurazione globale della macchina) o nel file ~/.profile
(nella configurazione di un singolo utente). Nello stesso file sarà inoltre necessario specificare il
percorso delle librerie standard di Java:
CLASSPATH=/usr/local/jdk/lib/classes.zip
|
È necessario infine aggiornare alcuni link simbolici presenti nella distribuzione Debian in modo tale che puntino agli eseguibili del JDK appena installato (l'effettiva procedura dipende, oltre che dalla distribuzione installata, anche dal tipo di pacchetti installati, per cui non si riportano dettagliatamente i passi effettuati).
Installazione ASDKL'installazione dell'ASDK è quasi totalmente automatica: eseguendo il file di classe
Aglets1_0.class (java Aglets1_0) il sistema, dopo aver chiesto di specificare la directory di destinazione,
decomprime le classi e le relative directory.
L'ASDK necessita di 7 variabili d'ambiente, dedicate all'aglet server e all'ambiente di sviluppo.
AGLET_HOME=/home/g10/java/Agletsl.0
|
Vediamone in dettaglio la funzione:
AGLET_HOME
ASDK_CLASSPATH
CLASSPATH è necessario specificare, oltre
al nome della directory, anche il nome del file - nel nostro caso classes.zip);
ASDK_JAVA
PATH
PATH corrente anche la directory contenente gli eseguibili
dell'aglet-server;
CLASSPATH
AGLET_PATH
AGLET_EXPORT_PATH
È importante notare che i file di classe vengono trasferiti da un server ad
un altro solo se essi sono presenti all'interno dei percorsi specificati da
AGLET_EXPORT_PATH del server di origine.
Nota:
Uso dell'ASDKUna volta installato l'ASDK, per avviare l'aglet server è sufficiente
eseguire lo shell script $AGLET_HOME/bin/agletsd (che, avendo settato le
variabili d'ambiente, dovrebbe essere raggiungibile senza specificarne il percorso).
Al primo avvio il sistema crea automaticamente alcuni file nella home directory
dell'utente che esegue l'aglet server. Tali file, contenuti nella directory .aglets,
contengono la configurazione dell'aglet server stesso: parametri di sicurezza (accesso a
risorse locali e remote), parametri dell'interfaccia grafica, personalizzazioni dell'utente.
Il fatto che i file di configurazione siano contenuti all'interno della home directory dell'utente che esegue di volta in volta l'aglet server permette, in un ambito multiutente come quello di Linux, di separare l'installazione dell'ASDK dalla sua gestione: la prima sarà compito dell'amministratore di sistema (che potrà ad esempio stabilire la porta TCP/IP d'appoggio), la seconda sarà compito degli utenti (benchè l'ASDK non ponga vincoli di questo tipo).
| ALCUNE NOTE SULLA SICUREZZA | ||
| L'ASDK può essere installato ed eseguito da qualsiasi utente della
macchina; chiunque può attivare il proprio aglet server e dunque
creare potenzialmente un buco di sicurezza nella macchina ospite. Di più,
ciascun utente può stabilire il tipo di risorse a cui aglet locali
e remoti possono accedere (il loro accesso avrà le stesse restrizioni
dell'utente che esegue il server). In uno scenario di questo tipo utenti
malintenzionati potrebbero installare il proprio aglet server con permessi
di lettura sul filesystem aperti verso aglet remoti. A questo punto un aglet di semplicissima realizzazione potrebbe leggere il file delle password ( /etc/passwd, aperto in lettura a tutti gli
utenti), decriptare la password di root e tornare all'host di partenza con
la preziosa informazione senza che questa attività sia in
alcun modo tracciata sul sistema. Una situazione come quella appena
descritta può essere facilmente risolta utilizzando il sistema delle
Shadow-Password, ma un programmatore più esperto potrebbe
sfruttare bug della JVM per raggiungere i suoi scopi.
| ||
Gli sviluppatori dell'ASDK ribadiscono che spedire un agente da una macchina all'altra equivale ad inviare un messaggio di posta elettronica: per questo motivo al primo avvio è necessario compilare un apposito modulo di registrazione con proprio nome, ente di appartenenza e indirizzo di posta elettronica. Tali informazioni accompagneranno ogni agente che partirà dalla nostra macchina.
L'interfaccia dell'aglet server può considerarsi divisa in due parti: una consolle (Java consolle) e l'aglet viewer (nel nostro caso Tahiti):
La prima (in modalità testo) compare all'interno della shell da cui è stato avviato il server e notifica informazioni di servizio: accesso ai file di configurazione, errori di sistema - del server e della JVM -, eccezioni non raccolte. La seconda (in modalita grafica) è chiamata Tahiti (detta anche aglet viewer) e costituisce per cosi dire il pannello di controllo del server stesso. Attraverso Tahiti è possibile maneggiare aglet locali e remoti, settare protezioni e permessi, monitorare l'attivita del sistema (per dettagli si rimanda alla documentazione ufficiale).
Nota:
trusted) e quelli recuperati da
un server remoto (untrusted). Per un errore nell'implementazione dell'interfaccia
grafica, la finestra suddetta modifica solamente i parametri relativi agli aglet locali. Tale
imprevisto si è manifestato durante lo sviluppo del nostro agente mobile: malgrado
avessimo accordato i permessi di lettura al file /proc/loadavg a qualsiasi
tipo di agente, il sistema rifiutava l'accesso agli agenti remoti. Abbiamo poi rilevato che
l'interfaccia agisce sempre e comunque solo sul file
~home_directory/.aglets/security/trusted relativo agli aglet locali lasciando
del tutto invariato il file ~home_directory/.aglets/security/untrusted relativo
a quelli remoti. Per far funzionare correttamente il nostro aglet è stato necessario
modificare il file manualmente copiando il contenuto di trusted su untrusted.
Dai sette pulsanti della finestra di Tahiti è possibile creare, clonare e distruggere gli aglet, recuperarli o spedirli attraverso la retela rete, visualizzare la finestra di dialogo e avere informazioni su quelli attivi. Le stesse funzionalita sono attivabili da menù: attraverso i quali à possibile selezionare tutte le altre funzioni disponibili (parametri di sicurezza e utente).
Per creare un aglet (non per svilupparlo ma per eseguirlo) è necessario
selezionare la funzione
dell'aglet viewer, che presenta una finestra di
dialogo da cui è possibile selezionare aglet locali o remoti.
Per individuare un aglet è necessario fornire l'URL preciso da cui scaricarlo:
oltre a specificare il nome della classe (senza l'estensione .class) è
necessario specificare protocollo, nome dell'host, porta, percorso dell'interno dell'host
ove risiede l'aglet. Per aglet locali è possibile settare un'apposita variabile
d'ambiente (AGLET_PATH) per mezzo della quale quale è possibile
richiamare degli aglet specificando solamente il nome del file .class.
In tutti gli altri casi sarà necessario specificare il percorso esatto:
file:/percorso/del/file/
file:///percorso/del/file/
protocollo://nomeHost:porta/percorso/del/file/
Nota:
atp://nomeHost:4434/percorso/del/file/. Per utilizzare una porta diversa è
necessario avviare l'aglet server con una apposita opzione a linea di comando (questo permette
ad esempio di avviare più server su una stessa macchina). Per utilizzare protocolli
diversi dall'ATP, e dunque effettuare il tunnelling attraverso un altro protocollo, è
necessario configurare il pannello di controllo di Tahiti
H:\Studio\Tesina\Aglet\Icaro.class sarà
individuato con file:/H:/STUDIO/TESINA/AGLET/
Tramite un apposito file di registro (~home_directory_utente/.aglets/aglet.properties)
è possibile conservare liste di host e aglet a cui si accede di frequente.
Esistono due modalita per la creazione degli aglet.
La prima (Create) carica se necessario ed esegue il codice di classe
dell'Aglet specificato; la seconda (Reload and Create) forza la
Garbage Collection della JVM e ricarica il file di classe.
Questa seconda modalità è utile in fase di sviluppo e debugging perchè
forza il sistema a caricare sempre e comunque l'ultima versione del file di classe.
Un altro metodo per rendere attivi degli aglet nel contesto corrente è quello
di recuperarli da una macchina remota; perchè ciò sia possibile è necessario
che l'aglet sia attivo nel contesto della macchina remota e che il codice delle classi che lo costituiscono siano all'interno del percorso individuato dalla variabile d'ambiente
AGLET_EXPORT_PATH. Senza questo presupposto il class loader della macchina
locale non è in grado di recuperare il codice.
Per tutte le altre caratteristiche dell'aglet viewer si rimanda alla documentazione ufficiale.
Il codice sorgente
Note introduttiveUna volta installato l'ASDK in ambiente Linux se ne voleva verificare il funzionamento
sviluppando un piccolo agente mobile di test. Abbiamo così creato ICARO,
l'Intelligent Agent-based Observer, un agente in grado, una volta giunto su una macchina Linux dotato di aglet server, di leggere lo stato della macchina e tornare all'host di partenza. Altre caratteristiche del programma sono l'interfaccia grafica completa e la gestione di un ampio numero di eccezioni. Il suo funzionamento si basa sulla lettura di un file presente nel pseudo-filesystem /proc/ contenente l'immagine sotto forma di file di diverse variabili d'esecuzione del kernel. Quella d'interesse per il progetto era /proc/loadavg,
contenente in un'unica riga di testo il carico medio della macchina negli intervalli uno, cinque e
quindici minuti appena trascorsi, numero di processi attivi e in esecuzioni, numero di processi
terminati dall'avvio della macchina.
Nota:
javac -deprecation Icaro.java
setText(java.lang.String)
e waitMessage(int), che fanno parte della classe Aglet. Questi
si occupano rispettivamente di scrivere un messaggio (ricevuto come parametro) nella
finestra dell'aglet viewer e di lasciare il messaggio visibile per un certo intervallo
di tempo (specificato come argomento in millisecondi). È importante notare che
la chiamata a waitMessage() può essere fatta solo all'interno del
metodo destinatario del messaggio così come indicato nella catena di if
di handleMessage(). Una chiamata a
waitMessage() in altra posizione del codice provoca un'eccezione.
ICARO: il codice dell'agletVengono inclusi package e classi indispensabili per il programma:
com.ibm.aglet, com.ibm.event, com.ibm.util
com.ibm.agletx.SimpleItinerary
com.ibm.agletx non viene
inserito nella gerarchia delle classi standard dell'ASDK
(presenti su $AGLET_HOME/lib/com/ibm/) ma viene inserito in un'altra
directory (in $AGLET_HOME/public/com/ibm/). Tale errore si manifesta sia
in ambiente Linux che in ambiente Windows NT (e non potrebbe essere altrimenti, visto che
la procedura di installazione è interamente realizzata in Java, dunque
indipendentemente dalla piattaforma di destinazione). La presenza di questa anomalia rende
inutilizzabili anche gli esempi forniti con l'ASDK, dato che in fase di compilazione il
compilatore non trova SimpleItinerary nel CLASSPATH. Per
risolvere il problema è sufficiente spostare il package nella posizione corretta oppure
creare un opportuno link simbolico.
/*
|
La classe principale del progetto è Icaro, che estende la classe
astratta Aglet da cui eredita tutte le caratteristiche principali degli aglet:
mobilità, accesso al contesto, protezioni, interattività (Tahiti e scambio di
messaggi).
public class Icaro extends Aglet {
|
Come abbiamo visto, il compito di Icaro è spostarsi tra diverse macchine Linux, leggerne lo stato, tornare all'host di partenza (per la precisione all'host di creazione); per questo motivo l'insieme dei dati (lo stato) che l'aglet deve portare con se è limitato all'URL dell'host di partenza, porta e protocollo di default, lista degli host da visitare, lista dello stato di ciascun host visitato, alcuni contatori necessari a tenere un riferimento durante l'esecuzione del suo ciclo di vita:
String home = null;
|
La mobilità degli aglet è ottenuta attraverso un'istanza della
classe SimpleItinerary. I metodi di questa classe permettono di spostare gli
agenti da un host all'altro (ove sia presente un aglet server attivo) e di controllarne il
comportamento alla partenza (attraverso la gestione delle eccezioni) e all'arrivo (
attraverso lo scambio di messaggi). Anche l'oggetto itinerary (istanza di
SimpleItinerary) viene serializzato, cioè trasportato insieme allo stato
durante lo spostamento dell'aglet. Questo è necessario perchè il costruttore
di SimpleItinerary riceve come parametro il riferimento all'aglet a cui si riferisce:
SimpleItinerary itinerary = null;
|
L'aglet dichiara un oggeto della classe SimpleItinerary necessario per il
suo spostamento. Tale oggetto, in fase di istanziazione (vedi codice del metodo onCreation())
contiene un riferimento all'aglet che l'ha creato e del quale dovrà gestire la
mobilità.
L'interazione tra utente e agente avviene attraverso una interfaccia grafica
apposita (realizzata da noi attraverso la libreria standard AWT), dalla quale
è possibile controllare tutti i parametri dell'agente stesso.
Non appena l'agente
comincia a muoversi l'interfaccia non è più necessaria, e a maggior ragione
è del tutto inutile in quelle macchine ove l'unica operazione compiuta è la
lettura di un file. La finestra di dialogo è perciò definita transient
in modo tale che il suo stato non venga serializzato (la classe è comunque serializzata,
altrimenti non sarebbe possibile creare Icaro da un server remoto).
transient Frame mainDialog = new MainDialog(this);
|
Alla finestra appena istanziata viene passato attraverso il costruttore un
riferimento (this) all'aglet di cui costituirà l'interfaccia. Tale
riferimento verrà utilizzato da tutte le finestre per accedere ai parametri
dell'agente e per controllarne il comportamento.
All'atto della creazione il contesto ove l'aglet viene istanziato richiama il
metodo pubblico onCreation() per mezzo del quale avvengono tutte le inizializzazioni:
public void onCreation(Object init) {
|
viene istanziato l'oggetto della classe SimpleItinerary (al quale viene passato il
riferimento all'aglet corrente), viene mostrata la finestra di dialogo, viene inizializzato
il contatore degli host da visitare. Viene poi salvato l'indirizzo dell'host dove l'aglet
viene creato e che costituisce la destinazione a fine ciclo:
home = getAgletContext().getHostingURL().toString();
|
Attraverso il metodo getAgletContext() si ottiene il contesto corrente
dell'aglet. Il contesto a sua volta definisce un metodo (getHostingURL()) che restituisce
l'URL dell'host ove l'aglet server è in esecuzione. L'oggetto URL infine
possiede il metodo toString() che restituisce l'URL sotto forma di stringa.
L'interazione tra contesto e aglet avviene attraverso lo scambio di messaggi e la
chiamata di metodi noti. I messaggi vengono gestiti attraverso una chiamata al metodo
pubblico (necessario perchè sia accessibile al contesto)
handleMessage(),
ereditato dalla classe Aglet.
public boolean handleMessage(Message msg) {
|
L'architettura degli Aglet prevede messaggi composti da due parti: la prima, di tipo
String individua il kind (tipo) del messaggio. In genere è
sufficiente questa porzione per stabilire la comunicazione (ed è questa che viene
usata dal nostro agente mobile). Il messaggio può avere anche una seconda parte,
chiamata arg, contenente uno dei tipi primitivi o un oggetto qualsiasi (specificato
nel costruttore). Tale porzione può essere usata per fornire informazioni complesse
a chi riceve il messaggio. Si potrebbe ad esempio pensare di inviare un messaggio
"go" (kind) che contiene come argomento l'indirizzo di destinazione
costituito da un'istanza della classe SimpleItinerary.
Durante il suo ciclo di vita ICARO può ricevere quattro tipi di messaggio:
"atHome"
"startCheck"
"getStatus"
"dialog"
A seconda del messaggio ricevuto handleMessage() richiama il metodo opportuno,
restituendo true se il messaggio è stato riconosciuto (e dunque
maneggiato) o false in caso contrario.
Il metodo atHome(),
public void atHome(Message msg) {
|
richiamato da handleMessage in corrispondenza del messaggio "atHome",
si limita a visualizzare (attraverso la notifica del messaggio "dialog") la
finestra con i risultati. Questa potrebbe essere visualizzata con una semplice chiamata al
metodo mainDialog.show() (e appositi controlli di consistenza), ma si è
lo scambio di messaggi per avere un livello di astrazione maggiore.
Il metodo startCheck
public synchronized void startCheck(Message msg) {
|
costituisce una regione critica (da qui il modificatore sincronized) e richiama
il metodo nextDestination() che si occupa di inviare l'aglet all'host successivo.
Il metodo nextDestination() sovraintende alla mobilità
dell'agente. Esso, richiamato dall'utente a inizio ciclo e dopo ogni check locale,
stabilisce a quale host inviare l'aglet. Dovendo eseguire uno spostamento all'host
successivo a quello corrente, viene prima di tutto incrementato il contatore relativo
alla posizione corrente. Come già detto la spedizione dell'aglet avviene attraverso
un istanza della classe SimpleItinerary che definisce il metodo go()
che può sollevare tutto un insieme di eccezioni legate alla connessione di rete.
Per questo motivo l'invocazione del metodo itinerary.go() è fatta
all'interno di un blocco try..catch.
Se si è esaurito l'elenco degli host da visitare si inizializza il contatore
e l'aglet viene rispedito all'host di partenza.
In caso contrario viene inviato all'host successivo indicato dall'utente.
Il metodo go() della classe SimpleItinerary permette di
specificare, oltre all'host di destinazione, anche un messaggio che verra notificato
all'arrivo dell'aglet.
E' attraverso questo messaggio che si controlla il comportamento di Icaro in un
determinato host.
/*
|
nextDestination() è in grado di raccogliere
cinque tipi di eccezioni relative a errori di connessione:
MalformedURLException
UnknownHostException
UnknownServiceException
ServerNotFoundException
ConnectException
RequestRefusedException
Per ognuna di queste viene salvata nel vettore di stato una stringa che notifica
all'utente il tipo di errore riscontrato e viene richiamato ricorsivamente il
metodo nextDestination() che sposta il focus sull'host successivo.
Per altri tipi di eccezioni viene semplicemente fatto un output dello stack
sulla console Java.
Il metodo getStatus(), richiamato all' arrivo in un host, si occupa di leggere
lo stato della macchina (richiamando il metodo readStatus())e di richiamare
il metodo nextDestination() per proseguire il ciclo.
/*
|
Il metodo readStatus() è quello che effettivamente rileva
il carico della macchina e lo salva nella posizione del vettore di stato relativo all'host
corrente. Il carico medio di una macchina Linux è riportato da diverse
utilità di sistema (top, uptime) e può essere
ricavato leggendo il file loadavg presente nel pseudofilesystem proc
(si rimanda alla manualistica in linea per maggiori dettagli). Il file /proc/loadavg è costituito da una sola
linea nella quale è registrato il carico medio della macchina nei tre
intervalli più significativi (uno, cinque e quindici minuti appena trascorsi),
oltre al rapporto processi_attivi/processi_in_esecuzione e il numero di
processi eseguiti dall'avvio della macchina.
Per prima cosa viene dichiarato e istanziato un oggetto della classe File, a cui
viene passato nel costruttore il nome del file interessato (/proc/loadvg).
La lettura del file, all'interno di un blocco try..catch, avviene attraverso
un'istanza della classe FileReader una comoda sottoclasse di InputStreamReader
dedicata alla lettura dei file di testo l'assegnazione della stringa di stato
al vettore di stato avviene attraverso una chiamata del metodo readLine()
di un istanza della classe BufferedReader che funge da buffer per lo stream
individuato da FileReader.
/*
|
La clausola catch ci permette di raccogliere eventuali eccezioni sollevate durante l'accesso al file.
Nota:
L'ultimo metodo della classe Icaro è invocato alla notifica
del messaggio "dialog" e si occupa della visualizzazione della finestra principale
mainDialog.
/*
|
ICARO: il codice della finestra principaleL'interazione tra utente e ICARO avviene attraverso una finestra che
contiene gli elementi standard delle interfacce grafiche : bordo di ridimensionamento, pulsanti di chiusura e
attivazione, barra del titolo. Tutte queste caratteristiche sono ereditate
dalla classe Frame.
Il contenuto della finestra puè essere considerato suddiviso in due parti complementari: una barra dei menù e un insieme di componenti organizzati attraverso una griglia. Tutti gli elementi necessari alla creazione dell'interfaccia (compreso il riferimento all'agente controllato) sono dichiarati come attributi privati dell' oggetto.
class MainDialog extends Frame {
|
La barra dei menù è assemblata usando le istanze delle classi
MenuBar, Menu e MenuItem. MenuBar
costituisce la barra vera e propria dei menù a cui vengono
addizionate delle etichette di menù attraverso il metodo add().
A ciascun menù vengono poi aggiunte le diverse voci attraverso il metodo add().
Nella seconda parte della finestra sono presenti alcuni componenti (pulsanti,
caselle di testo, liste) organizzati e distribuiti attraverso una
griglia che costituisce l'oggetto Layout, cioe lo stile della finestra stessa.
La creazione di tale griglia avviene attraverso un'istanza della classe
GridBagLayout e della classe GridBagConstraints. La prima costituisce il
layout vero e proprio (che verrà assegnato al frame attraverso una chiamata
del metodo setLayout), mentre la seconda si occupa della collocazione di
ciascun componente all'interno della griglia.
Per ciascun componente verrà dunque settata una apposita configurazione del
GridBagConstraints: dimensione dei bordi, fattore di riempimento, posizione
all'interno della griglia (coordinate colonna - riga), occupazione all'interno
della griglia (numero di colonne e righe occupate), fattore di
ridimensionamento della cella. Nel listato vi sarà dunque una successione
di configurazione/assegnazione/riempimento dei vari elementi che costituiscono
l'interfaccia.
/*
|
Per rendere più leggibile il codice si è pensato di raggruppare
le assegnazioni del GridBagConstraints (ben sei) nella chiamata ad un metodo
che ricevesse la configurazione come parametro; tale metodo, chiamato buildConstraints(),
è definito di classe perchè utilizzato da più finestre. Vi è poi
un metodo che si occupa di mostrare la lista degli host già inseriti dall'utente.
public static void buildConstraints(GridBagConstraints gbc, int x, int y, int w, int
h, int w100, int h100) {
|
La gestione degli eventi generati dall'utente avviene attraverso il metodo
action() ereditato dalla classe Frame (che a sua volta lo eredita dalla classe
Component). Tale metodo, deprecato nell'API 1.1 viene sovrascritto
opportunamente per controllare il comportamento del programma; esso riceve
come parametri un oggetto Event, che contiene l'evento vero e proprio, e un
riferimento all'oggetto che ha generato quell'evento. Attraverso una sequenza
di if..else si controlla l'attributo Event.target per stabilire quale azione
eseguire. Un primo blocco if..else gestisce gli eventi relativi alle voci di
menù: chiusura delle finestre, distruzione dell'agente, scelta del protocollo e della
porta di default, visualizzazione della finestra informazione.
La scelta di porta e protocollo avviene attraverso un istanza della classe
TemplateDialog.
public boolean handleEvent(Event ev) {
|
Un secondo blocco si occupa della gestione dei pulsanti, che è del tutto analoga a quella vista per le voci di menù:
else if (ev.target == closeButton) {
|
Alla pressione del pulsante addHostButton viene chiamato il metodo
addHost() che copia il contenuto della casella di testo hostTextField:
public void addHost() {
|
Se la casella di testo non è vuota la stringa in essa contenuta viene aggiunta
all'oggetto lista hostList, concatenando protocollo di default, nome dell'host,
porta di default; infine viene incrementato il contatore hostNumber
dell'aglet che contiene il numero degli host da visitare e viene eliminata la
stringa dal TextField. Il metodo addHost() è richiamato
anche alla pressione del tasto invio quando il focus è sul TextField:
questo permette di inserire rapidamente diversi indirizzi senza dover togliere le mani dalla tastiera.
In maniera analoga premendo il pulsante removeHostButton viene richiamato il
metodo removeHost() che elimina dalla hostList un host precedentemente
selezionato. Se nessun host è selezionato
(hostList.getSelectedIndex == -1)
il metodo termina senza compiere nessuna azione.
public void removeHost() {
|
La pressione del tasto startButton comanda l'inizio del ciclo di controllo.
Se la lista degli host non è vuota il suo contenuto viene copiato nel vettore
itineraryArray dell'aglet. In realtà il metodo hostList.getItems() crea un
vettore di stringhe contenenti gli elementi della lista, e tale vettore viene
poi assegnato all'aggetto itineraryArray. Successivamente viene istanziato
l'oggetto statusArray come vettore di stringa di dimensione pari al numero di
host da visitare. Dopo aver inizializzato il contatore aglet.counter viene
notificato all'agente il messaggio "startCheck" che comanderà effettivamente il
movimento dell'aglet.
ICARO: il codice della finestra dei risultatiLa classe DetailsDialog, sottoclasse di Frame,
costruisce la finestra con i risultati del controllo effetuato.
La costruzione dell'interfaccia avviene in
maniera del tutto analoga a quanto visto per la classe MainDialog:
viene definita una griglia e al suo interno vengono assemblati i vari
componenti. I risultati sono presentati all'interno di una lista le cui righe
(elementi) contengono, per ciascun host, l'indirizzo (preso dal vettore
itineraryArray) e lo stato (preso dal vettore statusArray).
Come gia' visto per la classe MainDialog, anche DetailsDialog riceve attraverso il proprio
costruttore un riferimento all'aglet corrente.
class DetailsDialog extends Frame {
|
Non essendo disponibile un oggetto "tabella" nella libreria AWT di Java 1.1 (ma sara' presente nella libreria swing presente in Java 1.2 di prossimo rilascio) si è pensato di "simularne" una all'interno dell'oggetto lista formattando opportunamente il testo delle righe.
Tale formattazione è possibile solo se i caratteri
all'interno della lista sono a spaziatura fissa. In ambiente XWindows questa
condizione è sempre verificata, mentre sotto Windows NT l'impostazione di
default prevede dei caratteri a spaziatura variabile (volendo rendere ICARO
multipiattaforma sarà sufficiente impostare un carattere a spaziatura fissa
attraverso il metodo setFont() dell'oggetto List). Il codice del metodo
updateData() assembla le varie righe utilizzando istanze della classe
StringBuffer: questa rispetto alla classe String permette la modifiica della
stringa memorizzata.
public void updateData() {
|
Per ogni macchina controllata vi saranno quattro colonne contenenti l'URL e il carico medio nei tre intervalli 1, 5, 15 minuti (oppure l'eventuale messaggio d'errore). Attravesro spazi e caratteri aggiuntivi verranno incolonnate le varie voci.
ICARO: il codice della finestra delle opzioniLa classe TemplateDialog è richiamata dal menù dell'aglet ogni volta
che viene richiesta una variazione del protocollo o della porta di default.
TemplateDialog estende la classe Dialog: è dunque una
finestra senza barra del titolo e non ridimensionabile. Il suo costruttore richiama quello della
superclasse, a cui passa il riferimento al frame da cui è stata creata e
una costante booleana (true) che specifica la finestra di dialogo di tipo
modale.
Nota:
parent) da cui è stata richiamata (applicazioni che definiscono come
finestra principale una "dialog box" sono comunque costrette a dichiarare una
"window" fittizia d'appoggio - in Java un'istanza della classe Frame).
/*
|
Dovendo implementare la stessa funzionalità per entrambi i parametri, si è
pensato di sfruttare un'unica finestra modificata in fase di creazione.
Il costruttore riceve come parametro una stringa che specifica il tipo di
finestra richiesta. Piuttosto che utilizzare un blocco if..else per gestire il
parametro in ingresso (e dunque per stabilire quale attributo dell'aglet modificare,
port o protocol), abbiamo preferito, per esercizio, utilizzare una potenzialità dell'API 1.1 chiamata
reflection. Questa caratteristica, detta anche
introspezione, permette di recuperare a tempo di esecuzione informazioni sulla
classe di appartenenza di un oggetto, sui suoi metodi e i suoi attributi.
Attraverso il metodo getClass(), ereditato da Object,
si ottiene un'istanza di Class che individua la classe dell'oggetto corrente.
Possiamo dire in un certo senso che come l'istanziazione di un oggetto
costituisce la "concretizzazione" di una classe, così la reflection permette
di "astrarre" la classe da un oggetto concreto.
Una volta ottenuto la classe dell'aglet
c = aglet.getClass();
|
possiamo ottenere un riferimento ad un certo campo (o metodo) semplicemente
fornendone il nome come parametro al metodo getDeclaredField() (questo metodo
a differenza di getField() può riferirsi a qualsiasi attributo dell'oggetto
indipendentemente dal fatto che sia pubblico o meno).
A tale metodo verrà passato come argomento il parametro dialogType ricevuto
nel costruttore della finestra, che è una stringa avente i due valori protocol
o port (nomi che coincidono con gli omonimi attributi dell'aglet).
c.getDeclaredField(dialogType)
|
Il metodo qui sopra restituirà un oggetto della classe Field specifico del campo
individuato da dialogType. Per ottenere il valore di un campo di un determinato oggetto
sarà sufficiente richiamare il metodo get() sull'oggetto
Field, specificando come argomento l'oggetto (istanza della classe a cui
il campo individuato da Field appartiene) a cui si riferisce. Tale metodo
restituisce un'istanza di Object, che attraverso il casting viene trasformato
in una istanza della classe di appartenenza del campo stesso.
oldValue = (String) c.getDeclaredField(dialogType).get(aglet);
|
Un meccanismo simile si utilizza per scrivere su un oggetto Field: una volta
individuato all'interno della classe attraverso getDeclaredField(), è
sufficiente richiamarne il metodo set() fornendo come argomento l'oggetto su cui
si vuole agire e il valore da registrare. L'accesso ai metodi e ai campi di un
oggetto Class può sollevare delle eccezioni (ad esempio, campo inesistente o
non accessibile) che vanno raccolte.
c = aglet.getClass(); // otteniamo l'oggetto "classe" di Icaro...
|
ICARO: il codice dell'about boxL'ultima classe InfoDialog si occupa di visualizzare una semplice dialog box
con le informazioni sul programma:
Il codice riprende lo stile utilizzato per la creazione delle altre finestre:
class InfoDialog extends Dialog {
|
ConclusioniIl nostro progetto si è basato su tre tecnologie ancora in fase di maturazione: Java come linguaggio, gli Aglets come sistema ad agenti mobili, Linux come sistema operativo. Il primo è tuttora in continua crescita, ma non può ancora considerarsi una piattaforma collaudata per lo sviluppo di applicazioni professionali. Questo costituisce una ulteriore sfida per l'ASDK: proporre un'architettura innovativa - gli agenti mobili - basandosi su un linguaggio ancora giovane. Infine Linux costituisce probabilmente l'incognita più grande, dato che è completamente basato sul free software, e dunque sul contributo volontario dei suoi sostenitori. Con la nostra tesina abbiamo potuto verificare che è possibile combinare, non senza qualche difficoltà, queste tre tecnologie per ottenere - almeno in potenza - un prodotto valido. In particolare:
Ulteriori sviluppi di ICARO potrebbero essere:
Strumenti utilizzati
Computer e sistemi operativi
Compilatori e tools Java
Tools per lo sviluppo degli Aglets
Altri strumenti
Bibliografia
Documenti online e riviste
Libri
Indirizzi utili
RingraziamentiVogliamo ringraziare alcune persone che ci hanno aiutato nello studio di Java, nello sviluppo di ICARO... e dunque nella preparazione di questo esame:
| Alessandro Spanu | g10@tiffany.diee.unica.it |
| Stefano Sanna | stefano@mcweb.unica.it |
| ICARO homepage | http://gulch.unica.it/~gerda/tesina/ |