MokaByte
Numero 22 - Settembre 1998
|
|||
|
|
||
Lorenzo Bettini |
|
||
In questo articolo vedremo quello che la versione 1.2 del jdk (ancora in beta) mette a disposizione per l'implementazione di applicazioni che utilizzano l'architettura CORBA
Introduzione
CORBA (Common
Object Request Broker Architecture) è lo standard per architetture
ad oggetti distribuite sviluppato dal consorzio dell'Object Management
Group (OMG). Si tratta di un'architettura per la realizzazione di un open
software bus, appunto l'Object Request Broker (ORB). Utilizzando questo
bus, applicazioni ad oggetti eterogenee possono interoperare attraverso
la rete, indipendentemente dall'architettura e dai sistemi operativi; fin
qui niente di nuovo: il nostro Java è nato per questo :-) In questo
caso però l'indipendenza si espande al linguaggio: tali applicazioni
(ed in particolare i componenti ad oggetti utilizzati da queste) possono
essere scritti in linguaggi differenti. In questo modo un programma scritto
in C++ può richiamare i metodi di un oggetto (magari remoto) scritto
in Java, oppure in Smalltalk, o addirittura in COBOL!
Di CORBA (specialmente correlato a Java) è già stato parlato su MokaByte ([1], [2]), quindi ci si limiterà solo a riprendere gli aspetti più salienti di questa architettura, in modo da dare anche una piccola introduzione; per avere una visione più totale comunque si rimanda ai suddetti articoli.
In particolare in questo articolo si parlerà dell'ORB messo a disposizione (ovviamente gratuitamente) dalla Sun per il jdk 1.2: Java IDL; purtroppo nella versione beta 3, manca il compilatore necessario per utilizzare questo ORB, che dovrà essere scaricato manualmente dal sito della Sun all'indirizzo: http://developer.javasoft.com/developer/earlyAccess/jdk12/idltojava.html.
Vale la pena notare che funzionalità simili a quelle messe a disposizione da CORBA, erano ottenibili anche utilizzando le RMI (Remote Method Invocation) API (anche queste già trattate in MokaByte: [3], [4]), già presenti nel jdk; infatti in questo modo si possono richiamare metodi su oggetti remoti (una volta ottenuto un riferimento locale). La differenza sta nel fatto che l'RMI può essere applicato solo ad oggetti e componenti Java, mentre l'intento di CORBA è quello di far cooperare oggetti (remoti) scritti in linguaggi differenti. Un confronto fra CORBA e RMI si può trovare in [5].
CORBA
Diamo in questo
paragrafo alcune nozioni fondamentali su CORBA. Per chi volesse approfondire,
oltre agli articoli [1], [2],
si consiglia di visitare il sito ufficiale della OMG: http://www.omg.org
che contiene anche moltissima documentazione, da quella di base, alle vere
e proprie specifiche CORBA, che dovrebbero essere implementate dai vari
ORB per essere considerati CORBA compliant.
Ovviamente oggetti scritti in linguaggi differenti, in esecuzione su Sistemi Operativi differenti ed architetture completamente diverse non possono comunicare direttamente, ma devono utilizzare certe interfacce (e protocolli) e comunicare attraverso dei proxy che comunque rendono totalmente trasparenti i dettagli della comunicazione: una volta ottenuto un riferimento ad un oggetto remoto si potrà richiamare semplicemente un suo metodo nel modo usuale.
Questo del resto non è differente dall'RMI in cui si ha uno stub (dalla parte del client) ed uno skeleton (dalla parte del server) che fungono appunto da proxy. Anche in CORBA (o forse sarebbe meglio dire anche in RMI, come in CORBA, visto che quest'ultimo è nato molto prima di Java) i termini sono gli stessi. Nella figura seguente è infatti schematizzato (in modo molto semplificato) come avviene la chiamata di un metodo di un oggetto remoto:
Un servant è un'istanza dell'implementazione di un oggetto CORBA, mentre un server è un processo che istanzia i servant e li mette a disposizione dei client, che sono applicazioni che invocano metodi di oggetti CORBA. Un client di un oggetto CORBA richiama i metodi di un oggetto remoto tramite un object reference che ottiene dal server. Lo stub, tramite l'ORB, identifica la macchina su cui è presente il server CORBA che gestisce l'oggetto del quale si vuole richiamare il metodo. Una volta stabilita la connessione con tale macchina, la richiesta, insieme ad i vari eventuali parametri con cui si richiama il metodo, vengono spediti dall'ORB del client all'ORB del server; quest'ultimo ORB presenta la richiesta allo skeleton che provvede a richiamare il metodo del servant, e, una volta terminato, a consegnare i risultati al client (ovviamente sempre tramite l'ORB). Quindi, come già detto, tutte le comunicazioni sono trasparenti al client, che penserà di invocare semplicemente un metodo di un oggetto normale. I due ORB non devono essere necessariamente istanze della stessa implementazione: basta che implementino gli standard di CORBA; del resto questo è tipico dei protocolli di Internet: quando si accede ad un sito FTP, vi si accede indipendentemente dalla particolare implementazione del server, in quanto viene utilizzato un protocollo standard.
I dati prima di essere spediti vengono memorizzati in un formato opportuno standard di CORBA; tale processo viene detto marshaling. Il recupero dei dati sul server viene detto a sua volta unmarshaling.
Ovviamente, poiché tali oggetti possono essere implementati con linguaggi di programmazione differente, ci deve essere un modo tramite il quale la comunicazione possa avvenire; del resto per chiamare i metodi di un oggetto non se ne deve conoscere l'implementazione, ma solo conoscerne l'interfaccia (questo è vero nella normale programmazione, ed ancora di più nella programmazione ad oggetti che esalta il concetto di information hiding). Quindi è sufficiente avere a disposizione un modo standard per definire le interfacce degli oggetti CORBA.
Il metodo per
definire tali interfacce è, tanto per cambiare, un linguaggio di
programmazione, che fa parte dello standard CORBA: questo linguaggio si
chiama IDL, ovverosia Interface Definition Language. Oltre
all'ORB, quindi è necessario un compilatore per tale linguaggio;
la Sun mette a disposizione per il jdk 1.2 il compilatore idltojava.
Esistono in commercio molti ORB [6], con relativi
compilatori IDL, ma questo ha il vantaggio di essere free! (esistono, a
tal proposito anche altre implementazioni free). L'implementazione di questo
ORB segue le specifiche 2.0 di CORBA.
A questo punto
è bene fare alcuni chiarimenti: Java IDL è un ORB
CORBA 2.0, e non, come potrebbe sembrare a prima vista, un'implementazione
dell'IDL di OMG; ovviamente anche questo è incluso. Effettivamente
la scelta del nome di questo ORB da parte della Sun non è stata
molto felice in fatto di chiarezza ;-)
Al compilatore
si deve "dare in pasto" un programma scritto appunto in IDL, che definisce
solamente l'interfaccia dell'oggetto CORBA, quindi il linguaggio
IDL è effettivamente un linguaggio puramente dichiarativo. Il compilatore
produrrà in output dei file che descrivono questa interfaccia nel
linguaggio appropriato; in questo caso si tratterà di un'interfaccia
in Java, ma se si da in input lo stesso file ad un compilatore IDL per
linguaggio C++ si otterrà in output un programma con una (o più)
classe in C++ (probabilmente si tratterà di una classe astratta,
che può essere intesa come un'interfaccia in C++).
In particolare
il compilatore genererà anche i file per lo stub e per lo skeleton
(come avviene nell'RMI). Poiché al compilatore viene fornita solo
l'interfaccia, le classi generate dovranno essere utilizzate (tramite la
derivazione o l'implementazione di un'interfaccia) per l'effettiva implementazione,
che spetta sempre al programmatore sia del server che del client.
Vediamo alcuni
ulteriori dettagli ovviamente con un esempio
Un
esempio
Vogliamo realizzare
un oggetto CORBA che restituisca la data e l'ora. Tale oggetto DateTime
avrà quindi due metodi: getDate() e getTime() che
restituiscono il risultato sotto forma di stringa. Inoltre vogliamo anche
un metodo che restituisca un long: il numero di millisecondi (dal Gennaio
del 1970), getMSsecs().
Definizione
dell'interfaccia
Dobbiamo quindi
scrivere l'interfaccia di tale oggetto in IDL, memorizzandola nel file
DateTime.idl:
module DateTimeAppCome si può notare il linguaggio IDL non si discosta molto dal C++ e da Java. In particolare un modulo è quello che in Java è un package, cioè una sorta di namespace che include interfacce e dichiarazioni di strutture (che non vedremo in questo primo esempio). Sulla dichiarazione dei metodi non c'è molto da dire; ovviamente si devono utilizzare i tipi dell'IDL, che mapperanno in quelli del linguaggio di destinazione, in questo caso string corrisponde a String, e long long a long (mentre long corrisponde a int). Una tabella dettagliata si può trovare nella documentazione ufficiale del jdk 1.2.
{
interface DateTime
{
string getDate();
string getTime();
long long getMSecs() ;
};
};
idltojava -fno-cpp DateTime.idlIl compilatore necessita di un preprocessore (che però non viene fornito dalla Sun), e di default questo compilatore sotto Windows 95 è quello del Visual C++ (!) mentre sotto Unix è cpp (quello standard del gcc). Ovviamente è anche possibile specificare tramite variabile quale altro preprocessore utilizzare; inutile dire che questo è alquanto fastidioso, in quanto non necessariamente uno ha un compilatore C++ installato! Fortunatamente per questo semplice esempio non è necessario un preprocessore, e quindi si può dire al compilatore di non utilizzarlo (tramite appunto l'opzione -fno-cpp).
package DateTimeApp;
public abstract class _DateTimeImplBase
extends org.omg.CORBA.DynamicImplementation
implements DateTimeApp.DateTime { ...
package DateTimeApp;
public class _DateTimeStub
extends org.omg.CORBA.portable.ObjectImpl
implements DateTimeApp.DateTime { ...
package DateTimeApp;
public interface DateTime
extends org.omg.CORBA.Object {
String getDate() ;
String getTime() ;
long getMSecs() ;
}
Ecco il listato del Client:Oltre ai file del pacchetto DateTimeApp, vengono inclusi anche altri pacchetti che servono per utilizzare CORBA. In particolare si utilizzerà il naming service, tramite il quale si recupererà un riferimento all'oggetto remoto, specificandone il nome. Per ottenere un object reference al name server si utilizza il metodo resolve_initial_references passandogli la stringa "NameService" che è definita per tutti gli ORB. Per prima cosa comunque è stato inizializzato l'ORB tramite il metodo init di classe ORB. Il narrowing (cioè il cast) è effettuato richiamando il metodo narrow della classe NamingContextHelper, fornita da CORBA. A questo punto si ha effettivamente NamingContext che si utilizzerà per ottenere un riferimento ad un oggetto DateTime; questo si effettua con la chiamata ncRef.resolve(path); a tale metodo deve essere passato un array di oggetti NameComponent che rappresenta il path di DateTime sul server (che quindi deve essere conosciuto dal client). In questo caso si tratta di un array di un solo elemento. Come si vede il riferimento viene subito sottoposto a narrowing utilizzando stavolta l'helper creato dal compilatore; a questo punto si ha effettivamente un oggetto DateTime, e si possono richiamare i metodi di tale oggetto remoto. Il riferimento viene poi passato ad un frame (DateTimeClientFrame) che presenterà un'interfaccia grafica per richiamare interattivamente i metodi dell'oggetto. Anche questa classe utilizzerà questo oggetto come se fosse un oggetto standard, ignorando completamente che si tratta di un oggetto remoto.
/*
* Esempio di Client CORBA
*/import DateTimeApp.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;public class DateTimeClient{
public static void main(String args[]){
try{
// crea ed inizializza l'ORB
ORB orb = ORB.init(args, null);// si ricava il root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);// Viene risolto l'Object Reference in Naming
System.out.println( "Recupero dell'Object Reference ..." ) ;
NameComponent nc = new NameComponent("DateTime", "");
NameComponent path[] = {nc};
DateTime DateTimeRef = DateTimeHelper.narrow(ncRef.resolve(path));
System.out.println( "... Object Reference ottenuto!\n" ) ;// si richiama i metodi del DateTime
System.out.println( "Chiamata metodi dell'oggetto remoto ..." ) ;
System.out.println("Data : " + DateTimeRef.getDate());
System.out.println("Ora : " + DateTimeRef.getTime());
System.out.println("MilliSecs : " + DateTimeRef.getMSecs());// si crea il frame
DateTimeClientFrame frame = new DateTimeClientFrame(DateTimeRef) ;
frame.pack() ;
frame.show() ;
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
}
Il
server ed il servant
Il server ha
una struttura simile a quella del client; la sostanziale differenza è
che, poiché gestisce oggetti DateTimeClient, deve provvedere
a connettere gli oggetti servant (in questo esempio ne viene istanziato
solo uno) all'ORB (utilizzando il metodo connect di classe ORB)
ed a registrare tale classe (interfaccia) presso il name server (in modo
che i client remoti possano ottenere un object reference specificando il
nome di tale classe) utilizzando il metodo rebind di classe NamingContext.
/*A questo punto il server deve rimanere attivo, in attesa di richieste da parte del client, e quindi si mette in attesa di essere notificato su un oggetto qualsiasi (notifica che non arriverà mai: si tratta di un espediente per non fare terminare il server); si noti che non gestirà personalmente le richieste: queste verranno gestite dal sistema dell'ORB, tramite lo skeleton.
Esempio di server di oggetti CORBA
*/import DateTimeApp.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;public class DateTimeServer {
public static void main(String args[])
{
try{
// crea ed inizializza l'ORB
ORB orb = ORB.init(args, null);// crea il servant e lo registra presso l'ORB
DateTimeServant DateTimeRef = new DateTimeServant();
orb.connect(DateTimeRef);// recupera il root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);// effettua il binding dell'Object Reference in Naming
NameComponent nc = new NameComponent("DateTime", "");
NameComponent path[] = {nc};
ncRef.rebind(path, DateTimeRef);// attende richieste dal client
java.lang.Object sync = new java.lang.Object();
synchronized (sync) {
sync.wait();
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
}
import java.util.Date ;Come si può notare si tratta di una classe semplicissima che si limita ad implementare i metodi definiti dall'interfaccia DateTime; tale interfaccia è implementata dalla classe astratta _DateTimeImplBase creata automaticamente dal compilatore e da cui deriva il servant. E' questa classe astratta che funge da skeleton e che fa la maggior parte del lavoro!
import java.sql.Time ;class DateTimeServant extends _DateTimeImplBase
{
public String getDate() { return (new Date()).toString(); }
public String getTime() {
return (new Time(new Date().getTime()).toString() ) ;
}
public long getMSecs() { return (new Date()).getTime() ; }
}
Testiamo
il tutto
A questo punto
siamo pronti per testare tutto quello che abbiamo scritto finora. Ovviamente
di dovranno compilare tutti i .java che sono stati creati automaticamente
e manualmente.
Prima di eseguire
il server si deve mandare il esecuzione il name server di Java IDL
(simile all'rmiregistry), specificando la porta su cui rimanere
in ascolto:
tnameserv
-ORBInitialPort 9999
A schermo si
dovrebbe vedere una scritta (abbastanza incomprensibile) del tipo:
Initial Naming Context:Su un'altra finestra, a questo punto, si può lanciare il server:IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e300000000001000000000000003400010000000000086c6f72656e7a6f000401
00000000001cafabcafe0000000235f2382e00000000000000080000000000000000
TransientNameServer: setting port for initial object references to: 9999
java DateTimeServer -ORBInitialPort 9999E su un'altra ancora il client
java DateTimeClient -ORBInitialPort
9999
Ottenendo a
schermo il seguente output:
Recupero dell'Object Reference ...Il client farà poi partire una finestra grafica dalla quale si potranno richiamare i vari metodi dell'oggetto remoto:
... Object Reference ottenuto!Chiamata metodi dell'oggetto remoto ...
Data : Sun Sep 06 07:25:01 GMT 1998
Ora : 07:25:08
MilliSecs : 905066709020
MokaByte Web 1998 - www.mokabyte.it MokaByte ricerca nuovi collaboratori. Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it |