Il
Naming Service è sicuramente il principale meccanismo CORBA per
la localizzazione di oggetti su un ORB. Fa parte delle specifiche CORBA
dal 1993 ed è il servizio più importante tra quelli standardizzati
da OMG.
Fornisce
un meccanismo di mapping tra un nome ed un object reference, quindi rappresenta
anche un metodo per rendere disponibile un servant ad un client remoto.
Il meccanismo è simile a quello di registrazione ed interrogazione
del registry in RMI.
Nella
programmazione distribuita l’utilizzo di un nome per reperire una risorsa
ha degli evidenti vantaggi rispetto all’utilizzo di un riferimento. In
primo luogo il nome è significativo per lo sviluppatore, in secondo
luogo è completamente indipendente dagli eventuali restart dell’oggetto
remoto.
Struttura del
Naming Service
L’idea
base del Naming Service è quella di incapsulare in modo trasparente
i servizi di naming e directory già esistenti. I nomi quindi sono
strutturabili secondo uno schema gerarchico ad albero, uno schema astratto
indipendente dalle singole convenzioni delle varie piattaforme di naming
o di directory.
L’operazione
che associa un nome ad un reference è detta bind (esiste anche l’operazione
di unbind). L’operazione che recupera un reference a partire da un nome
è detta naming resolution.
Esistono
dei naming context all’interno dei quali il nome è univoco. I naming
context possono essere ricondotti ai nodi intermedi dell’albero di naming
ed al concetto di directory in un file system. Possono esistere più
nomi associati ad uno stesso oggetto.
In
questo scenario quindi un nome è una sequenza di name component,
questi formano il cosiddetto compound name. I nodi intermedi sono utilizzati
per individuare un context, mentre i nodi foglia sono i simple name.
Figura
1 — Struttura albero di Naming
Un
compound name quindi individua il cammino (path) che, attraverso la risoluzione
di tutti i context, porta al simple name che identifica la risorsa.
Ogni
NameComponent è una struttura con due elementi. L’identifier è
la stringa nome, mentre il kind è un attributo associabile al component.
Questo attributo non è considerato dal Naming Service, ma è
destinato al software applicativo.
La
definizione IDL del NameComponent è la seguente
//
IDL
module
CosNaming {
struct
NameComponent {
Istring id;
Istring kind;
};
typedef
sequence <NameComponent> Name;
}
L’interfaccia
principale è NamingContext e fornisce tutte le operazioni necessarie
alla definizione dell’albero di Naming ed alla sua navigazione. Per quel
che concerne il bind vengono fornite due funzionalità di bind tra
Name/Object, due funzionalità di bind Name/NamingContext ed una
funzionalità di unbind
//
IDL
module
CosNaming{
//
...
interface
NamingContext
{
// ...
void bind(in Name n, in Object obj)
raises( NotFound, CannotProceed,
InvalidName, AlreadyBound );
void rebind(in Name n, in Object obj)
raises( NotFound, CannotProceed, InvalidName );
void bind_context(in Name n, in NamingContext nc)
raises( NotFound, CannotProceed,
InvalidName, AlreadyBound );
void rebind_context(in Name n, in NamingContext nc)
raises( NotFound, CannotProceed, InvalidName );
void unbind(in Name n)
raises( NotFound, CannotProceed, InvalidName );
};
};
I metodi
rebind differiscono dai metodi bind semplicemente nel caso in cui il nome
sia già presente nel context: rebind sostituisce l’object reference
mentre bind rilancia un’eccezione AlreadyBound.
La
definizione dell’albero implica anche la possibilità di creare o
distruggere i NamingContext
//
IDL
module
CosNaming{
//
...
interface
NamingContext
{
// ...
NamingContext new_context();
NamingContext bind_new_context(in Name n)
raises( NotFound, AlreadyBound,
CannotProceed, InvalidName );
void destroy() raises( NotEmpty );
};
};
La
risoluzione di un name è attuata mediante il metodo resolve. La
procedura di risoluzione di un nome gerarchico implicherà la navigazione
ricorsiva dell’albero di context
//
IDL
module
CosNaming{
//
...
interface
NamingContext
{
// ...
Object resolve(in Name n)
raises( NotFound, CannotProceed, InvalidName );
};
};
La
navigazione del Naming è invece legata all’utilizzo del metodo list
che ritorna un insieme di name sui quali è possibile operare iterativamente.
Più precisamente ciò che viene ritornato è un oggetto
di tipo BindingIterator che, tramite i metodi next_one e next_n, permette
di navigare attraverso tutti i bind associati al context
//
IDL
module
CosNaming{
//...
interface
BindingIterator {
boolean next_one(out Binding b);
boolean next_n(in unsigned long how_many,
out BindingList bl);
void destroy();
};
interface
NamingContext
{
// ...
void list(in unsigned long how_many,
out BindingList bl, out BindingIterator bi );
};
};
Utilizzare il
Naming Service
Alla
luce di quanto visto finora sul Naming Service, è possibile migliorare
l’esempio della calcolatrice visto il mese scorso [1]. Lo scenario generale
non cambia: esiste un oggetto servant (non necessita di alcuna modifica),
un oggetto server di servizio (pubblica il servant) ed il client.
Il
Naming Service ha impatto solo sulle modalità di registrazione e
di accesso all’oggetto, quindi solo sul client e sul server. Anche l’IDL
non necessita di alcuna modifica.
Per
pubblicare un oggetto il server dovrà in primo luogo ottenere un
riferimento al Naming Service. Come detto in [2], è possibile ottenere
un riferimento ad un qualunque servizio CORBA invocando sull’ORB il metodo
resolve_initial_references. Ottenuto il riferimento, come per qualunque
oggetto CORBA, andrà utilizzato il narrow fornito dal corrispondente
Helper
org.omg.CORBA.Object
objRef =
orb.resolve_initial_references("NameService");
NamingContext
ncRef = NamingContextHelper.narrow(objRef);
A questo
punto è possibile effettuare il bind tra il NameComponent opportuno
e l’istanza di servant (calc è in questo caso l’istanza di CalcolatriceImpl,
si veda più avanti il codice completo)
NameComponent
nc = new NameComponent("Calc", "");
NameComponent
path[] = {nc};
ncRef.rebind(path,
calc);
I due
parametri del costruttore NameComponent sono rispettivamente il name ed
il kind.
Ecco
il codice completo della classe server
package
server;
import
utility.*;
import
java.io.*;
import
org.omg.CosNaming.*;
import
org.omg.CosNaming.NamingContextPackage.*;
public
class CalcolatriceServer {
public static void main(String[] args) {
try {
// Inizializza l'ORB.
org.omg.CORBA.ORB orb =
org.omg.CORBA.ORB.init(args,null);
// Crea un oggetto Calcolatrice
CalcolatriceImpl calc = new CalcolatriceImpl();
orb.connect(calc);
// Stampa l'object reference in versione stringa
System.out.println("Creata Calcolatrice:\n" +
orb.object_to_string(calc));
// Root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
// Fa il bind dell'oggetto nel Naming
NameComponent nc = new NameComponent("Calc", "");
NameComponent path[] = {nc};
ncRef.rebind(path, calc);
// Attende l'invocazione di un client
java.lang.Object sync = new java.lang.Object();
synchronized (sync) {
sync.wait();
}
} catch (Exception e) {
System.err.println("Server error: " + e);
e.printStackTrace(System.out);
}
}
}
Il
client dovrà effettuare le stesse operazioni per ottenere il riferimento
al Naming Service. Poi dovrà effettuare la resolve per ottenere
un riferimento all’oggetto remoto Calcolatrice
NameComponent
nc = new NameComponent("Calc", "");
NameComponent
path[] = {nc};
Calcolatrice
calc =
CalcolatriceHelper.narrow(ncRef.resolve(path));
Ecco
il codice completo della classe client
package
client;
import
utility.*;
import
java.io.*;
import
org.omg.CosNaming.*;
import
org.omg.CORBA.*;
public
class CalcolatriceClient {
public
static void main(String args[]) {
try{
// Crea ed inizializza l'ORB
ORB orb = ORB.init(args, null);
// Root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
// Utilizzo il Naming per ottenere
// il riferimento all'oggetto
NameComponent nc = new NameComponent("Calc", "");
NameComponent path[] = {nc};
Calcolatrice calc =
CalcolatriceHelper.narrow(ncRef.resolve(path));
// Ottiene da input tastiera i 2 numeri da sommare
BufferedReader
inputUser = new BufferedReader (
new InputStreamReader(System.in));
String first, second;
int a, b;
// Leggo primo addendo
System.out.println ();
System.out.print("A = ");
first = inputUser.readLine();
a = Integer.valueOf(first).intValue ();
// Leggo secondo addendo
System.out.println ();
System.out.print("B = ");
second = inputUser.readLine();
b = Integer.valueOf(second).intValue ();
// Invoca il metodo remoto passandogli i parametri
System.out.println ();
System.out.print("Il risultato e': ");
System.out.print(calc.somma(a, b));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Per
eseguire l’esempio sarà necessario effettuare un passo in più
rispetto a quanto fatto in precedenza, ovvero attivare il Naming Service
prima di mandare in esecuzione il server.
L’attivazione
del servizio è legata all’implementazione CORBA, sarà quindi
differente da ORB ad ORB (nel caso si utilizzi VisiBroker sarà necessario
avviare anche il tool OsAgent trattato in seguito)
tnameserv
-ORBInitialPort 1050 (per l’implementazione Sun)
nameserv
NameService (per l’implementazione VisiBroker)
A questo
punto sarà possibile avviare il server (per il significato dei flag
utilizzati si rimanda alla documentazione del prodotto)
java
server.CalcolatriceServer -ORBInitialPort 1050 (per l’implementazione Sun)
java
-DSVCnameroot=NameService server.CalcolatriceServer (per l’implementazione
VisiBroker)
Ed
infine avviare il client
java
client.CalcolatriceClient -ORBInitialPort 1050 (per l’implementazione Sun)
java
-DSVCnameroot=NameService client.CalcolatriceClient (per l’implementazione
VisiBroker)
E’
possibile notare come in questa modalità l’utilizzo di CORBA abbia
parecchie similitudini con quello di RMI: l’utilizzo di tnameserv (nameserv)
rimanda a quello di rmiregistry, così come i metodi del NamingContext
bind e rebind rimandano ai metodi usati per RMI Naming.bind e Naming.rebind.
Una volta ottenuto il reference l’utilizzo dell’oggetto remoto è
sostanzialmente identico.
Conclusioni
In
questo articolo abbiamo presentato il CORBA Naming Service utilizzandolo
per “migliorare” la nostra semplice Calcolatrice remota. Il prossimo mese
affronteremo (mediante il pattern Factory) alcune delle problematiche legate
all’accesso concorrente ad un oggetto remoto.
Bibliografia
[1]
G. Morello - "Corso CORBA III Parte: un semplice esempio", Mokabyte N.
50, Marzo 2001
[2]
G. Morello - "Corso CORBA I Parte: una breve introduzione", Mokabyte N.
48, Gennaio 2001 |