Skip to content

10. Desarrollo de aplicaciones distribuidas CORBA

10.1. Introducción

En el capítulo anterior, vimos cómo crear aplicaciones distribuidas en Java con el paquete RMI. Aquí abordamos el mismo problema, pero esta vez con la arquitectura CORBA. CORBA (Common Object Request Broker Architecture) es una especificación definida por el OMG (Object Management Group), que agrupa a numerosas empresas del mundo de la informática. CORBA define un «bus de software» accesible para aplicaciones escritas en diferentes lenguajes:

Image

Veremos que la construcción de una aplicación distribuida con CORBA es similar al método empleado con Java RMI: los conceptos son parecidos. CORBA ofrece la ventaja de la interoperabilidad con aplicaciones escritas en otros lenguajes.

10.2. Proceso de desarrollo de una aplicación CORBA

10.2.1. Introducción

Para desarrollar una aplicación cliente-servidor CORBA, seguiremos los siguientes pasos:

  1. escritura de la interfaz del servidor con IDL (Lenguaje de Definición de Interfaz)
  2. generación de las clases «esqueleto» y «stub» del servidor
  3. escritura del servidor
  4. escritura del cliente
  5. compilación de todas las clases
  6. Inicio de un directorio de servicios CORBA
  7. Inicio del servidor
  8. inicio del cliente

Tomamos como primer ejemplo el del servidor de eco ya utilizado en el contexto RMI. De esta manera, el lector podrá ver las diferencias entre los dos métodos.

La aplicación se ha probado con el jdk1.2.

10.2.2. Escritura de la interfaz del servidor

Al igual que con Java RMI, desde la perspectiva del cliente, el servidor se define por su interfaz. Si bien las clases que implementan el servidor no son necesarias para el cliente, las de su interfaz sí lo son. Mientras que Java RMI utilizaba una interfaz Java que daba lugar a las clases «esqueleto» y «stub» del servidor, la arquitectura Java CORBA requiere que la interfaz se describa en un lenguaje distinto de Java. Esta interfaz dará lugar a varias clases, algunas de las cuales serán utilizadas por el cliente y otras por el servidor.

La descripción de la interfaz de eco será la siguiente:

module echo{
    interface iSrvEcho{
        string echo(in string msg);
    };
};

La descripción de la interfaz se almacenará en un archivo echo.idl. Está escrita en el lenguaje IDL (Interface Definition Language) de OMG. Para que sea útil, debe ser analizada por un programa que creará archivos fuente en el lenguaje utilizado para desarrollar la aplicación CORBA. En este caso, utilizaremos el programa idltojava.exe, el cual, a partir de la interfaz anterior, creará los archivos fuente .java necesarios para la aplicación. El programa idltojava.exe no viene incluido con el JDK. Se puede obtener en el sitio web de Sun: http://java.sun.com.

Analicemos las pocas líneas de la interfaz idl anterior:

module echo

es equivalente al paquete echo de Java. La compilación de la interfaz dará lugar al paquete Java echo c.a.d, un directorio que contiene clases de Java.

interface iSrvEcho

es equivalente a la interfaz iSrvEcho de Java. Dará lugar a una interfaz de Java.

string echo(in string msg)

es equivalente a la instrucción de Java String echo(String msg). Los tipos del lenguaje IDL no se corresponden exactamente con los del lenguaje Java. Las correspondencias se pueden encontrar más adelante en este capítulo. En el lenguaje IDL, los parámetros de una función pueden ser de entrada (in), de salida (out) o de entrada-salida (inout). Aquí, el método echo recibe un parámetro de entrada msg, que es una cadena de caracteres, y devuelve una cadena de caracteres como resultado.

La interfaz anterior es la de nuestro servidor de eco. Recordemos que una interfaz remota describe los métodos del objeto servidor a los que pueden acceder los clientes. En este caso, solo el método echo estará disponible para los clientes.

10.2.3. Compilación de la interfaz IDL del servidor

Una vez definida la interfaz del servidor, se generan los archivos Java correspondientes.

E:\data\java\corba\ECHO>dir *.idl

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL

E:\data\java\corba\ECHO>d:\javaidl\idltojava.exe -fno-cpp echo.idl

La opción -fno-cpp sirve para indicar que no se debe utilizar un preprocesador (utilizado con mayor frecuencia con C/C++). La compilación del archivo echo.idl genera un subdirectorio echo que contiene los siguientes archivos:

E:\data\java\corba\ECHO>dir echo

_ISRVE~1 JAV         1 095  17/03/99  17:19 _iSrvEchoStub.java
ISRVEC~1 JAV           311  17/03/99  17:19 iSrvEcho.java
ISRVEC~2 JAV           825  17/03/99  17:19 iSrvEchoHolder.java
ISRVEC~3 JAV         1 827  17/03/99  17:19 iSrvEchoHelper.java
_ISRVE~2 JAV         1 803  17/03/99  17:19 _iSrvEchoImplBase.java

El archivo iSrvEcho.java es el archivo Java que describe la interfaz del servidor:

/*  * Archivo: ./ECHO/ISRVECHO.JAVA  * De: ECHO.IDL  * Fecha: lunes 15 de marzo a las 13:56:08 de 1999  *   Por: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 de agosto de 1998 16:25:34  */

package echo;
public interface iSrvEcho
    extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
    String echo(String msg)
;
}

Se observa que es prácticamente una traducción palabra por palabra de la interfaz IDL. Si tenemos curiosidad por revisar el contenido de los otros archivos .java, encontraremos elementos más complejos. Esto es lo que dice la documentación sobre la función de estos diferentes archivos:

iSrvEcho.java

la interfaz del servidor

_iSrvEchoImplbase.java

implementa la interfaz anterior iSrvEcho. Es una clase abstracta, el «esqueleto» del servidor, que le proporciona las funcionalidades CORBA necesarias para la aplicación distribuida.

_iSrvEchoStub.java

Es la imagen («stub») del servidor que utilizará el cliente. Proporciona al cliente las funcionalidades CORBA para conectarse al servidor.

iSrvEchoHelper.java

Proporciona los métodos necesarios para la gestión de referencias de objetos CORBA

iSrvEchoHolder.java

Proporciona los métodos necesarios para administrar los parámetros de entrada y salida de los métodos de la interfaz.

10.2.4. Compilación de las clases generadas a partir de la interfaz IDL

Es recomendable compilar las clases anteriores. En otro ejemplo veremos que aquí se pueden detectar errores debidos a un funcionamiento incorrecto del generador idltojava. En este caso todo sale bien y, tras la compilación, en el directorio del paquete echo se encuentran los siguientes archivos:

E:\data\java\corba\ECHO\echo>dir

_ISRVE~1 JAV         1 095  17/03/99  17:19 _iSrvEchoStub.java
ISRVEC~1 JAV           311  17/03/99  17:19 iSrvEcho.java
ISRVEC~2 JAV           825  17/03/99  17:19 iSrvEchoHolder.java
ISRVEC~3 JAV         1 827  17/03/99  17:19 iSrvEchoHelper.java
_ISRVE~2 JAV         1 803  17/03/99  17:19 _iSrvEchoImplBase.java
_ISRVE~1 CLA         2 275  18/03/99  11:25 _iSrvEchoImplBase.class
_ISRVE~2 CLA         1 383  18/03/99  11:25 _iSrvEchoStub.class
ISRVEC~1 CLA           251  18/03/99  11:25 iSrvEcho.class
ISRVEC~2 CLA         2 078  18/03/99  11:25 iSrvEchoHelper.class
ISRVEC~3 CLA           858  18/03/99  11:25 iSrvEchoHolder.class

10.2.5. Escritura del servidor

10.2.5.1. Implementación de la interfaz iSrvEcho

Anteriormente definimos la interfaz iSrvEcho. Ahora escribiremos la clase que implementa esta interfaz. Dicha clase derivará de la clase _iSrvEchoImplbase.java, la cual, como se indicó anteriormente, ya implementa la interfaz iSrvEcho.

// paquetes importados
import echo.*;

// clase que implementa el eco remoto
public class srvEcho extends _iSrvEchoImplBase{
    // método que implementa el eco
    public String echo(String msg){
        return  "["  + msg + "]";
    }// fin del eco
}// fin de clase

El código se explica por sí mismo. Esta clase se guarda en el archivo srvEcho.java, ubicado en el directorio principal del paquete de la interfaz iSrvEcho.

Se puede compilar para verificarlo:

E:\data\java\corba\ECHO>j:\jdk12\bin\javac srvEcho.java

E:\data\java\corba\ECHO>dir

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.5.2. Registro de la clase de creación del servidor

Al igual que con una aplicación cliente-servidor RMI, un servidor CORBA debe registrarse en un directorio para que los clientes puedan acceder a él. Es este procedimiento de registro el que, a nivel de desarrollo, difiere según se trate de una aplicación CORBA o RMI. A continuación se muestra el del servidor CORBA de eco registrado en el archivo serveurEcho.java:

// paquetes importados
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- clase serveurEcho
public class serveurEcho{
    // ------- main: inicia el servidor de eco
    // sintaxis pg machineAnnuaire portAnnuaire nomService
    // máquina: máquina que aloja el directorio CORBA
    // puerto: puerto del directorio CORBA
    // nomService: nombre del servicio que se va a registrar

  public static void main(String arg[]){
    // ¿Están ahí los argumentos?
    if(arg.length!=3){
        System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomService");
        System.exit(1);
    }
    // se recuperan los argumentos
    String machine=arg[0];
    String port=arg[1];
    String nomService=arg[2];

    try{

    // necesitamos un objeto CORBA para trabajar
    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
      ORB orb=ORB.init(initORB,null);
    // se agrega el servicio al directorio de servicios
    // se llamará srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // Creamos el servidor y lo asociamos al servicio srvEcho
    srvEcho serveurEcho=new srvEcho();
      ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);
    // seguimiento
    System.out.println("Serveur d'écho prêt");
    // espera de las solicitudes de los clientes
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
    // se produjo un error
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// en espera
}// serveurEcho

A continuación explicamos las líneas generales de la puesta en marcha del servidor sin entrar en detalles que, a primera vista, pueden parecer complejos. Es importante recordar las líneas generales del ejemplo anterior, ya que se repiten en todos los servidores CORBA.

10.2.5.2.1. Los parámetros del servidor

Un servidor CORBA debe registrarse en un servicio de directorio que opere en una máquina y un puerto específicos. Nuestra aplicación recibirá estos dos datos como parámetros. El servicio así registrado debe tener un nombre; este será el tercer parámetro.

10.2.5.2.2. Crear el objeto de acceso al servicio de directorio CORBA

Para acceder al servicio de directorio y registrar nuestro servidor de eco, necesitamos un objeto llamado ORB (Object Request Broker) que se obtiene mediante el siguiente método de clase:

ORB ORB.init(String [] args, Properties prop)

Args :     tableau de paires de chaînes de caractères, chaque paire étant de la forme (paramètre,valeur)

Prop :     propriétés de l’application

El ejemplo utiliza la siguiente secuencia para obtener el objeto ORB:


    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
       ORB orb=ORB.init(initORB,null);

Los pares (parámetro, valor) utilizados son los siguientes:


("-ORBInitialHost",machine) : ce couple précise la machine ou opère l’annuaire des services CORBA, ici la machine passée en paramètre au serveur.

("-ORBInitialPort",port ) : ce couple précise le port ou opère l’annuaire des services CORBA, ici le port passé en paramètre au serveur.

El segundo parámetro del método init se deja en null. Si también se hubiera dejado el primer parámetro en null, el par (máquina, puerto) utilizado habría sido el predeterminado (localhost,900).

10.2.5.2.3. Registrar el servidor en el directorio de servicios CORBA

El registro del servidor en el directorio se realiza mediante las siguientes operaciones:

    // se agrega el servicio al directorio de servicios
    // se llamará srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // se crea el servidor y se asocia al servicio srvEcho
    srvEcho serveurEcho=new srvEcho();
          ncRef.rebind(path,serveurEcho);
            orb.connect(serveurEcho);

La primera parte del código consiste en preparar el nombre del servicio. Este nombre se representa en el código mediante la variable path. El nombre de un servicio incluye varios componentes:

  • un componente inicial objRef, un objeto genérico que debe convertirse a un tipo NamingContext, en este caso ncRef.
  • el nombre del servicio, en este caso nomService, que se ha pasado como parámetro al servidor

Estos componentes del nombre (NameComponent) se agrupan en una matriz, en este caso path. Es esta matriz la que «nombra» de manera precisa el servicio creado. Una vez creado el nombre, queda

  • asociarlo a una instancia del servidor (la clase srvEcho creada anteriormente)

srvEcho serveurEcho=new srvEcho();
  • y registrarlo en el directorio
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

10.2.5.3. Compilación de la clase de inicio del servidor

Se compila la clase anterior:

E:\data\java\corba\ECHO>j:\jdk12\bin\javac serveurEcho.java

E:\data\java\corba\ECHO>dir

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SERVEU~1 CLA         1 793  18/03/99  13:18 serveurEcho.class
SERVEU~1 JAV         1 806  16/03/99  15:38 serveurEcho.java
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.6. Texto del cliente

10.2.6.1. El código

Escribimos un cliente para probar nuestro servicio de eco. Le pasaremos al cliente los mismos tres parámetros que al servidor:

Máquina: máquina donde se encuentra el directorio de servicios CORBA

Puerto: puerto en el que opera este directorio

nomService: nombre del servicio de eco

El cliente se conecta al servicio de eco y luego le pide al usuario que escriba mensajes en el teclado. Estos se envían al servidor de eco, que los devuelve. En la pantalla se muestra el seguimiento de este diálogo.

El cliente CORBA del servicio de eco se parece mucho al cliente RMI que ya se escribió. Una vez más, el cliente debe conectarse a un servicio de directorio para obtener una referencia del objeto-servidor al que desea conectarse. La diferencia entre los dos clientes radica ahí y únicamente ahí. A continuación se muestra el código del cliente de eco CORBA:


    

10.2.6.2. La conexión del cliente al servidor

El cliente CORBA mencionado anteriormente se conecta al servidor mediante la instrucción:

        // se establece la conexión con el servidor de eco
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

Al finalizar esta operación, el cliente cuenta con una referencia del servidor de eco. A partir de ahí, un cliente CORBA no difiere de un cliente RMI. El método privado que garantiza la conexión con el servidor es el siguiente:

// paquetes importados
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

// ---------- clase cltEcho
public class cltEcho {

    public static void main(String arg[]){
         // sintaxis: cltEcho machineAnnuaire portAnnuaire nombre del servicio
         // máquina: máquina en la que opera el directorio de servicios CORBA
         // puerto: puerto en el que opera el directorio de servicios
         // nomService: nombre del servicio de eco

         // verificación de los argumentos
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
            System.exit(1);
        }

         // se recuperan los parámetros
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];

         // se establece la conexión con el servidor de eco
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

         // diálogo cliente-servidor
        BufferedReader in=null;
        String msg=null;
        String reponse=null;
        iSrvEcho serveur=null;

        try{
             // apertura del flujo del teclado
            in=new BufferedReader(new InputStreamReader(System.in));
             // bucle de lectura de los mensajes que se enviarán al servidor de eco
            System.out.print("Message : ");
            msg=in.readLine().toLowerCase().trim();
            while(! msg.equals("fin")){
                 // envío del mensaje al servidor y recepción de la respuesta
                reponse=serveurEcho.echo(msg);
                 // seguimiento
                System.out.println("Réponse serveur : " + reponse);
                 // mensaje siguiente
                System.out.print("Message : ");                
                msg=in.readLine().toLowerCase().trim();
            }// while
             // se acabó
            System.exit(0);
         // manejo de errores         
        } catch (Exception e){
            System.err.println("Erreur : " + e);
            System.exit(2);
        }// try
    }// main

     // ---------------------- getServeurEcho
    private static iSrvEcho getServeurEcho(String machine, String port, 
            String nomService){

         // solicita una referencia del servidor de eco
         // seguimiento
        System.out.println("--> Connexion au serveur CORBA en cours...");
         // la referencia del servidor de eco
        iSrvEcho serveurEcho=null;
        try{
             // se solicita un objeto CORBA para trabajar
            String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
          ORB orb=ORB.init(initORB,null);
             // se utiliza el servicio de directorio para localizar el servidor de eco
          org.omg.CORBA.Object objRef=
                orb.resolve_initial_references("NameService");
          NamingContext ncRef=NamingContextHelper.narrow(objRef);
             // el servicio buscado se llama srvEcho; se solicita
          NameComponent nc= new NameComponent(nomService,"");
          NameComponent path[]={nc};
          serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));
        } catch (Exception e){
            System.err.println("Erreur lors de la localisation du serveur d'écho ("
                + e + ")");
            System.exit(10);
        }// try-catch
         // se devuelve la referencia al servidor
        return serveurEcho;
    }// getServeurEcho

}// clase

Encontramos las mismas secuencias de código que en el servidor:

  • creamos un objeto ORB que nos permitirá comunicarnos con el directorio de servicios CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
      ORB orb=ORB.init(initORB,null);
  • se definen los diferentes componentes del nombre del servicio de eco
        org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
  • Se solicita al servicio de directorio una referencia del servicio de eco (aquí es donde nos diferenciamos del servidor)
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

10.2.6.3. Compilation

E:\data\java\corba\ECHO>j:\jdk12\bin\javac cltEcho.java

E:\data\java\corba\ECHO>dir

CLTECH~1 CLA         2 599  18/03/99  13:51 cltEcho.class
CLTECH~1 JAV         2 907  16/03/99  16:15 cltEcho.java
ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SERVEU~1 CLA         1 793  18/03/99  13:18 serveurEcho.class
SERVEU~1 JAV         1 806  16/03/99  15:38 serveurEcho.java
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.7. Pruebas

10.2.7.1. Inicio del servicio de directorio

En una máquina con Windows, iniciamos el servicio de directorio de la siguiente manera:

E:\data\java\corba\ECHO>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000

lo cual inicia el servicio de directorio en el puerto 1000 de la máquina.

El servicio de directorio tnameserv genera una salida en pantalla similar a la siguiente:

Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000

No es muy legible, pero nos quedaremos con la última línea: el servicio está activo en el puerto 1000.

10.2.7.2. Inicio del servidor de eco

El servicio de eco se inicia con tres parámetros:


E:\data\java\corba\ECHO>start j:\jdk12\bin\java serveurEcho localhost 1000 srvEcho

El servidor muestra:

    Serveur d’écho prêt

10.2.7.3. Inicio del cliente en la misma máquina que el servidor


E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho localhost 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin

10.2.7.4. Inicio del cliente en una computadora con Windows distinta a la del servidor

E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho tahe.istia.univ-angers.fr 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : abcd
Réponse serveur : [abcd]
Message : efgh
Réponse serveur : [efgh]
Message : fin

10.3. Ejemplo 2: un servidor SQL

10.3.1. Introducción

Aquí retomamos la descripción del servidor SQL, que ya se analizó en el contexto de Java RMI, con el objetivo de resaltar los puntos en común entre ambos métodos, así como sus diferencias. Recordemos la función de este servidor SQL: se encuentra en una máquina con Windows y permite a los clientes remotos acceder a las bases de datos públicas ODBC de esta estación de trabajo con Windows.

Image

El cliente CORBA podría realizar tres operaciones:

  • conectarse a la base de datos de su elección
  • enviar consultas SQL
  • cerrar la conexión

El servidor ejecuta las consultas SQL del cliente y le envía los resultados. Esa es su función principal y por eso lo llamamos servidor SQL. Aplicamos los diferentes pasos vistos anteriormente con el servidor de eco.

10.3.2. Escritura de la interfaz IDL del servidor

Recordemos, a modo de resumen, la interfaz RMI que habíamos utilizado para el servidor:

import java.rmi.*;

// la interfaz remota
public interface interSQL extends Remote{
    public String connect(String pilote, String url, String id, String mdp)
        throws java.rmi.RemoteException;
    public String[] executeSQL(String requete, String separateur) 
        throws java.rmi.RemoteException;
    public String close()
        throws java.rmi.RemoteException;
}

La función de los distintos métodos era la siguiente:

Connect: el cliente se conecta a una base de datos remota, para lo cual proporciona el controlador, la URL JDBC, así como su identificador (id) y su contraseña (mdp) para acceder a dicha base. El servidor le devuelve una cadena de caracteres que indica el resultado de la conexión:

    200 - Connexion réussie
    500 - Echec de la connexion

executeSQL: el cliente solicita la ejecución de una consulta SQL en la base de datos a la que está conectado. Indica el carácter que debe separar los campos en los resultados que se le devuelven. El servidor devuelve una matriz de cadenas:

    100 n

para una consulta de actualización de la base de datos, donde n es el número de filas actualizadas

    500 msg d’erreur

si la consulta generó un error

    501 Pas de résultats

si la consulta no generó ningún resultado

    101 ligne1
    101 ligne2
    101 ...

si la consulta ha generado resultados. Las líneas devueltas por el servidor son los resultados de la consulta.

Close: el cliente cierra su conexión con la base de datos remota. El servidor devuelve una cadena que indica el resultado de este cierre:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

La interfaz IDL del servidor será la siguiente:

module srvSQL{

    typedef sequence<string> resultats;

    interface interSQL{
        string connect(in string pilote, in string urlBase, in string id, in string mdp);
        resultats executeSQL(in string requete, in string separateur);
        string close();
    };// interfaz
};// módulo

La única novedad con respecto a lo que vimos con la interfaz IDL del servidor de eco es el uso de la palabra clave sequence. Esta palabra clave permite definir un array unidimensional. La definición se realiza en dos pasos:

  • definición de un tipo para designar la matriz, en este caso «resultados»:
typedef sequence<string> resultats;

La palabra clave typedef es bien conocida entre los programadores de C/C++: permite definir un nuevo tipo. Aquí, el tipo resultats se define como equivalente al tipo sequence<string>, c.a.d, un array dinámico (sin tamaño fijo) de cadenas de caracteres.

  • Uso del nuevo tipo donde sea necesario
resultats executeSQL(in string requete, in string separateur);

Por lo tanto, el método executeSQL devuelve un array de cadenas de caracteres.

10.3.3. Compilación de la interfaz IDL del servidor

La interfaz IDL anterior se coloca en el archivo srvSQL.idl. Se compila este archivo:

E:\data\java\corba\sql>d:\javaidl\idltojava -fno-cpp srvSQL.idl

E:\data\java\corba\sql>dir

SRVSQL   IDL           275  19/03/99   9:59 srvSQL.idl
SRVSQL         <REP>        19/03/99   9:41 srvSQL

Se observa que la compilación ha generado un directorio con el nombre del módulo de la interfaz IDL (srvSQL). Veamos el contenido de este directorio:

E:\data\java\corba\sql>dir srvSQl

RESULT~1 JAV           833  19/03/99  10:00 resultatsHolder.java
RESULT~2 JAV         1 883  19/03/99  10:00 resultatsHelper.java
_INTER~1 JAV         2 474  19/03/99  10:00 _interSQLStub.java
INTERS~1 JAV           448  19/03/99  10:00 interSQL.java
INTERS~2 JAV           841  19/03/99  10:00 interSQLHolder.java
INTERS~3 JAV         1 855  19/03/99  10:00 interSQLHelper.java
_INTER~2 JAV         4 535  19/03/99  10:00 _interSQLImplBase.java

Cabe recordar que los archivos Helper y Holder son clases relacionadas con los parámetros de entrada-salida y los resultados de los métodos de la interfaz remota. El directorio srvSQL contiene todos los archivos .java relacionados con la interfaz interSQL definida en el archivo .idl. También contiene archivos relacionados con el tipo resultats creado en la interfaz IDL.

El archivo interSQL.java es el archivo Java de la interfaz de nuestro servidor. Es importante verificar que lo que se ha generado automáticamente se ajuste a lo que esperamos. El archivo interSQL.java generado es el siguiente:

/*  * Archivo: ./SRVSQL/INTERSQL.JAVA  * De: SRVSQL.IDL  * Fecha: viernes 19 de marzo a las 09:59:48 de 1999  *   Por: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 de agosto de 1998 16:25:34  */

package srvSQL;
public interface interSQL
    extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
    String connect(String pilote, String urlBase, String id, String mdp)
;
    String[] executeSQL(String requete, String separateur)
;
    String close()
;
}

Se observa que tenemos la misma interfaz que la utilizada para el cliente-servidor RMI. Por lo tanto, podemos continuar. Compilemos todos estos archivos .java:

E:\data\java\corba\sql\srvSQL>j:\jdk12\bin\javac *.java

Note: _interSQLImplBase.java uses or overrides a deprecated API.  Recompile with
 "-deprecation" for details.
1 warning

E:\data\java\corba\sql\srvSQL>dir *.class

_INTER~1 CLA         3 094  19/03/99  10:01 _interSQLImplBase.class
_INTER~2 CLA         1 953  19/03/99  10:01 _interSQLStub.class
INTERS~1 CLA           430  19/03/99  10:01 interSQL.class
INTERS~2 CLA         2 096  19/03/99  10:01 interSQLHelper.class
INTERS~3 CLA           870  19/03/99  10:01 interSQLHolder.class
RESULT~1 CLA         2 047  19/03/99  10:01 resultatsHelper.class
RESULT~2 CLA           881  19/03/99  10:01 resultatsHolder.class

10.3.4. Codificación del servidor SQL

Ahora escribimos el código del servidor SQL. Recordemos que esta clase debe derivarse de la clase abstracta _nomInterfaceImplBase generada por la compilación del archivo IDL. Aparte de esta particularidad y salvo las secuencias de código relacionadas con el registro del servicio en un directorio, el código del servidor CORBA es idéntico al del servidor RMI:

// paquetes importados
import java.sql.*;
import java.util.*;
import srvSQL.*;

// clase SQLServant
public class SQLServant extends _interSQLImplBase{

    // datos globales de la clase
    private Connection DB;

    // --------------- conexión
    public String connect(String pilote, String url, String id,
        String mdp){

        // conexión a la base de datos mediante el controlador
        // identificación con el ID y la contraseña

        String resultat=null;            // resultado del método
        try{
            // carga del controlador
            Class.forName(pilote);
            // solicitud de conexión
            DB=DriverManager.getConnection(url,id,mdp);
            // ok
            resultat="200 Connexion réussie";
        } catch (Exception e){
            // error
            resultat="500 Echec de la connexion (" + e + ")";
        }
        // fin
        return resultat;
    }            

    // ------------- executeSQL
    public String[] executeSQL(String requete, String separateur){

        // ejecuta una consulta SQL en la base de datos DB
        // y coloca los resultados en una tabla de cadenas

        // datos necesarios para ejecutar la consulta
        Statement S=null;
        ResultSet RS=null;
        String[] lignes=null;
        Vector resultats=new Vector();
        String ligne=null;

        try{
            // creación del contenedor de la consulta
            S=DB.createStatement();
            // ejecución de la consulta
            if (! S.execute(requete)){
                // consulta de actualización
                // se devuelve el número de filas actualizadas
                lignes=new String[1];
                lignes[0]="100 "+S.getUpdateCount();
                return lignes;
            }
            // era una consulta
            // se recuperan los resultados
            RS=S.getResultSet();
            // número de campos del conjunto de resultados
            int nbChamps=RS.getMetaData().getColumnCount();
            // se procesan
            while(RS.next()){
                // creación de la fila de resultados
                ligne="101 ";
                for (int i=1;i<nbChamps;i++)
                    ligne+=RS.getString(i)+separateur;
                ligne+=RS.getString(nbChamps);
                // se agrega al vector de resultados
                resultats.addElement(ligne);
            }// while
            // fin del procesamiento de los resultados
            // se liberan los recursos
            RS.close();
            S.close();
            // se entregan los resultados
            int nbLignes=resultats.size();
            if (nbLignes==0){
                lignes=new String[1];
                lignes[0]="501 Pas de résultats";
            } else {
                lignes=new String[resultats.size()];
                for(int i=0;i<lignes.length;i++)
                    lignes[i]=(String) resultats.elementAt(i);
            }//si
            return lignes;
        } catch (Exception e){
            // error
            lignes=new String[1];
            lignes[0]="500 " + e;
            return lignes;
        }// try-catch
    }// executeSQL

    // --------------- cerrar
    public String close(){
        // cierra la conexión con la base de datos
        String resultat=null;
        try{
            DB.close();
            resultat="200 Base fermée";
        } catch (Exception e){
            resultat="500 Erreur à la fermeture de la base ("+e+")";
        }
        // devuelve el resultado
        return resultat;
    }
}// clase SQLServant

Esta clase se encuentra en el archivo SQLServant.java que estamos compilando:

E:\data\java\corba\sql>dir

SRVSQL   IDL           275  19/03/99   9:59 srvSQL.idl
SRVSQL         <REP>        19/03/99   9:41 srvSQL
SQLSER~1 JAV         2 941  15/03/99   9:09 SQLServant.java

E:\data\java\corba\sql>j:\jdk12\bin\javac SQLServant.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class

10.3.5. Código del programa de inicio del servidor SQL

La clase anterior representa el servidor SQL una vez iniciado. Previamente, debe registrarse en un directorio de servicios CORBA. Al igual que con el servicio de eco, lo haremos con una clase especial a la que le pasaremos, en el momento de la ejecución, tres parámetros:

Máquina: máquina donde se encuentra el directorio de servicios CORBA

Puerto: puerto en el que opera este directorio

nomService: nombre del servicio SQL

El código de esta clase es casi idéntico al de la clase que hacía lo mismo para el servicio de eco. Hemos resaltado en letras grandes la línea que difiere entre ambas clases: no crea el mismo objeto de servidor. Así pues, vemos que seguimos teniendo la misma mecánica de inicio del servidor. Si aislamos esta mecánica en una clase, como se ha hecho aquí, se vuelve casi transparente para el desarrollador.

// paquetes importados
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- clase serveurSQL
public class serveurSQL{
    // ------- main: inicia el servidor SQL
  public static void main(String arg[]){
        // serveurSQL máquina, puerto, servicio

        //¿Tenemos el número correcto de argumentos?
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
            System.exit(1);
        }
        // se recuperan los argumentos
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
    try{
            // se necesita un objeto CORBA para trabajar
      ORB orb=ORB.init(initORB,null);
            // se agrega el servicio al directorio de servicios
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
        // se crea el servidor y se asocia al servicio srvSQL
        SQLServant serveurSQL=new SQLServant();
      ncRef.rebind(path,serveurSQL);
        orb.connect(serveurSQL);
        // seguimiento
        System.out.println("Serveur SQL prêt");
        // espera de solicitudes de los clientes
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
            // se produjo un error
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// manual
}// srvSQL

Compilamos esta nueva clase:

E:\data\java\corba\sql>j:\jdk12\bin\javac serveurSQL.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class
SERVEU~1 CLA         1 800  19/03/99  10:33 serveurSQL.class

10.3.6. Escritura del cliente

El cliente del servidor CORBA se invoca con los siguientes parámetros:

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

máquina: máquina donde se encuentra el directorio de servicios CORBA

puerto: puerto en el que opera este directorio

nomService: nombre del servicio SQL

controlador: controlador que debe utilizar el servidor SQL para administrar la base de datos deseada

urlBase: URL JDBC de la base de datos que se va a administrar

id: identidad del cliente o nulo si no hay identidad

mdp: contraseña del cliente o nula si no hay contraseña

separador: carácter que el servidor SQL debe utilizar para separar los campos de las líneas de resultados de una consulta

A continuación se muestra un ejemplo de los parámetros posibles:

    localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

donde:

máquina: máquina en la que se encuentra el directorio de servicios CORBA

puerto: puerto en el que opera este directorio

srvSQL: srvSQL, nombre CORBA del servidor SQL

controlador: sun.jdbc.odbc.JdbcOdbcDriver, el controlador habitual para bases de datos con interfaz ODBC

urlBase: jdbc:odbc:articles, para utilizar una base de datos «articles» declarada en la lista de bases de datos públicas ODBC de la máquina Windows

id: null, sin identidad

contraseña: null, sin contraseña

separador: , los campos de los resultados estarán separados por una coma

Una vez iniciado con los parámetros anteriores, el cliente sigue los siguientes pasos:

  • se conecta a la máquina en el puerto para solicitar el servicio CORBA srvSQL
  • solicita la conexión a la base de datos de artículos
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • le pide al usuario que escriba una consulta SQL en el teclado
  • la envía al servidor SQL
executeSQL(requete, ’’,’’);
  • muestra en pantalla los resultados devueltos por el servidor
  • vuelve a pedirle al usuario que escriba una consulta SQL en el teclado. Se detendrá cuando termine la consulta.

A continuación se muestra el código Java del cliente. Los comentarios deberían ser suficientes para entenderlo. Se observará que:

  • el código es idéntico al del cliente RMI que ya se analizó. Se diferencia en el proceso de solicitud del servicio al directorio, proceso que se encuentra aislado en el método getServeurSQL.
  • El método getServeurSQL es idéntico al escrito para el cliente de eco

Por lo tanto, vemos que:

  • un cliente CORBA solo se diferencia de un cliente RMI en la forma en que se comunica con el servidor
  • esta forma es idéntica para todos los clientes CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;

public class clientSQL {

    // datos globales de la clase
    private static String syntaxe =
        "syntaxe : cltSQL machine port service pilote urlBase id mdp separateur";
    private static BufferedReader in=null;
    private static interSQL serveurSQL=null;

    public static void main(String arg[]){
        // sintaxis: cltSQL máquina puerto separador controlador URL ID contraseña
        // máquina puerto: máquina y puerto del directorio de servicios CORBA con el que se debe contactar
        // servicio: nombre del servicio
        // controlador: controlador que se debe utilizar para la base de datos que se va a utilizar
        // urlBase: URL JDBC de la base de datos que se va a utilizar
        // id: identidad del usuario
        // contraseña: su contraseña
        // separador: cadena que separa los campos en los resultados de una consulta

        // verificación del número de argumentos
        if(arg.length!=8)
            erreur(syntaxe,1);

        // inicialización de los parámetros de conexión a la base de datos
        String machine=arg[0];
        String port=arg[1];
        String service=arg[2];        
        String pilote=arg[3];
        String urlBase=arg[4];
        String id, mdp, separateur;
        if(arg[5].equals("null")) id=""; else id=arg[5];
        if(arg[6].equals("null")) mdp=""; else mdp=arg[6];        
        if(arg[7].equals("null")) separateur=" "; else separateur=arg[7];        

        // parámetros del servicio de directorio CORBA
        String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
        // cliente CORBA: se solicita una referencia del servidor SQL
        interSQL serveurSQL=getServeurSQL(machine,port,service);

        // diálogo cliente-servidor
        String requete=null;
        String reponse=null;
        String[] lignes=null;
        String codeErreur=null;

        try{
            // apertura del flujo del teclado
            in=new BufferedReader(new InputStreamReader(System.in));
            // seguimiento
            System.out.println("--> Connexion à la base de données en cours");
            // solicitud de conexión inicial a la base de datos
            reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
            // seguimiento
            System.out.println("<-- "+reponse);
            // análisis de la respuesta
            codeErreur=reponse.substring(0,3);
            if(codeErreur.equals("500")) 
                erreur("Abandon sur erreur de connexion à la base",3);
            // bucle de lectura de las consultas que se enviarán al servidor SQL
            System.out.print("--> Requête : ");
            requete=in.readLine().toLowerCase().trim();
            while(! requete.equals("fin")){
                // envío de la consulta al servidor y recepción de la respuesta
                lignes=serveurSQL.executeSQL(requete,separateur);
                // seguimiento
                afficheLignes(lignes);
                // siguiente solicitud
                System.out.print("--> Requête : ");                
                requete=in.readLine().toLowerCase().trim();
            }// while
            // seguimiento
            System.out.println("--> Fermeture de la connexion à la base de données distante");
            // se cierra la conexión
            reponse=serveurSQL.close();
            // seguimiento
            System.out.println("<-- " + reponse);
            // fin
            System.exit(0);
        // manejo de errores        
        } catch (Exception e){
            erreur("Abandon sur erreur : " + e,2);
        }// intentar
    }// main

    // ----------- AfficheLignes
    private static void afficheLignes(String[] lignes){
        for (int i=0;i<lignes.length;i++)
            System.out.println("<-- " + lignes[i]);
    }// afficheLignes

    // ------------ error
    private static void erreur(String msg, int exitCode){
        // visualización del mensaje de error
        System.err.println(msg);
        // posible liberación de recursos
        try{
            in.close();
            serveurSQL.close();
        } catch(Exception e){}
        // salimos
        System.exit(exitCode);
    }// error

    // ---------------------- getServeurSQL
    private static interSQL getServeurSQL(String machine, String port, String service){
        // solicita una referencia del servidor SQL
        // máquina: máquina del directorio de servicios CORBA
        // puerto: puerto del directorio de servicios CORBA
        // servicio: nombre del servicio CORBA que se va a solicitar

        // seguimiento
        System.out.println("--> Connexion au serveur CORBA en cours...");
        // la referencia del servidor SQL
        interSQL serveurSQL=null;
        // parámetros del servicio de directorio CORBA
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
                try{
            // se solicita un objeto CORBA para trabajar; para ello, se utiliza el puerto 
            // de escucha del directorio de servicios CORBA
      ORB orb=ORB.init(initORB,null);
            // se utiliza el servicio de directorio para localizar el servidor SQL
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
            // el servicio buscado se llama srvSQL — lo solicitamos
      NameComponent nc= new NameComponent(service,"");
      NameComponent path[]={nc};
      serveurSQL=interSQLHelper.narrow(ncRef.resolve(path));
        } catch (Exception e){
            System.err.println("Erreur lors de la localisation du serveur SQL ("
                + e + ")");
            System.exit(10);
        }// try-catch
        // se devuelve la referencia al servidor
        return serveurSQL;
    }// getServeurSQL

}// clase

Compilemos la clase del cliente:

E:\data\java\corba\sql>j:\jdk12\bin\javac clientSQL.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class
SERVEU~1 CLA         1 800  19/03/99  10:33 serveurSQL.class
CLIENT~1 CLA         3 774  19/03/99  10:45 clientSQL.class

Estamos listos para las pruebas.

10.3.7. Pruebas

10.3.7.1. Pré-requis

Supongamos que una base de datos llamada «Artículos» está disponible públicamente en la máquina Windows del servidor:

Image

Esta base de datos tiene la siguiente estructura:

nombre
tipo
code
código del artículo de 4 caracteres
nom
su nombre (cadena de caracteres)
prix
su precio (real)
stock_actu
su stock actual (entero)
stock_mini
el stock mínimo (en número entero) por debajo del cual hay que reabastecer el artículo

10.3.7.2. Lanzamiento del servicio de directorio

E:\data\java\corba\sql>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000

Le service d’annuaire est lancé sur le port 1000. Il affiche dans une fenêtre DOS quelque chose du genre :

Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
052800000018afabcafe000000027693d3fd000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000

10.3.7.3. Inicio del servidor SQL

Se inicia el servidor SQL:

E:\data\java\corba\sql>start j:\jdk12\bin\java serveurSQL localhost 1000 srvSQL

Muestra en una ventana DOS:

    Serveur SQL prêt

10.3.7.4. Inicio de un cliente en la misma máquina que el servidor

Estos son los resultados obtenidos con un cliente en la misma máquina que el servidor:

E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Consulta: select nombre, stock_actu, stock_mini from artículos
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 cachalot,6,6
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> Consulta: eliminar de artículos donde stock_mini<7
<-- 100 1
--> Consulta: seleccione nombre, stock_actu, stock_mini de artículos
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

10.3.7.5. Inicio de un cliente en una máquina distinta a la del servidor

Estos son los resultados obtenidos con un cliente en una máquina distinta a la del servidor:

E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL tahe.istia.univ-angers.fr 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Consulta: select * from artículos
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,canoé,1502,7,7
<-- 101 x123,fusil,3000,9,8
<-- 101 s345,skis nautiques,1800,13,8
<-- 101 f450,essai3,3,13,9
<-- 101 z400,léopard,500000,7,7
<-- 101 g457,panthère,800000,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

10.4. Correspondencias entre IDL y JAVA

A continuación, presentamos las correspondencias entre los tipos simples IDL y JAVA:

tipo IDL
tipo Java
boolean
booleano
char
carácter
wchar
char
byte
byte
cadena
java.lang.String
wstring
java.lang.String
short
short
short sin signo
short
long
int
long sin signo
int
long long
long
unsigned long long
long
float
float
double
double

Recordemos que para definir un array de elementos de tipo T en la interfaz IDL, se utiliza la instrucción:

    typedef   sequence<T> nomType;

y que, posteriormente, se utiliza nomType para hacer referencia al tipo del array. Así, en la interfaz del servidor SQL se utilizó la declaración:

    typedef sequence<string> resultats;

para que resultats haga referencia a una tabla String[] en Java.