MokaByte 51 - Aprile  2001
Foto dell'autore non disponibile
di
Gianluca Morello
Corso CORBA
IV parte: CORBA Naming Service
Questo mese presenteremo il servizio più utilizzato tra quelli standardizzati da OMG: il CORBA Naming Service
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 

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java®, Jini®  e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it