Primeros pasos con ORBacus 4 Diego Sevilla Ruiz Sistemas Distribuidos, 5o Índice 1. Introducción 1 2. Descarga y copia 2 3. Variables de entorno 4 4. Primeros pasos 4.1. Fichero IDL . . 4.2. Cliente . . . . . 4.3. Implementación 4.4. El servidor . . 4.5. Makefile . . . . 4.6. Ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 . 4 . 5 . 6 . 8 . 10 . 12 5. Aspectos avanzados: Threads 5.1. Uso de Threads en ORBacus . . . . . . . 5.2. El ejemplo . . . . . . . . . . . . . . . . . . 5.3. Definiciones IDL . . . . . . . . . . . . . . 5.4. El cliente . . . . . . . . . . . . . . . . . . 5.5. La implementación del servidor del sensor 5.6. Makefiles . . . . . . . . . . . . . . . . . . 5.7. Configuración de Threads en ORBacus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . del . . . . . . . . . . . . . . . . servant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Revisión 1. . . . . . . . . . . . . . . . . . . 13 13 14 14 15 19 25 26 27 Introducción Este documento explica los primeros pasos para usar ORBacus en su versión para C++. Este software se utilizará en las prácticas de la asignatura. ORBacus es una implementación de CORBA de las más flexibles y mejor implementadas. Además, aunque 1 no es gratis, su código está disponible para ser usado en universidades sin cargo. Existen otros totalmente libres, pero ORBacus ofrece la solución más sencilla y más potente a la vez. Este documento muestra cómo descargar, compilar y un ejemplo sencillo de uso con ORBacus. 2. Descarga y copia Para realizar las prácticas se puede descargar el software ORBacus versión 4 de la dirección: http://ditec.um.es/ssdd/OB-4.1.0.tar.gz Y posteriormente descomprimirlo en un directorio que se utilizará para la compilación: tar zxvf OB-4.1.0.tar.gz que a su vez crea el directorio OB-4.1.0. Entrando dentro de ese directorio, hay que realizar dos tareas: Editar el fichero config/gcc-check y ponerle como segunda lı́nea: exit 0 (esto es debido a que el script de comprobación del compilador gcc no funciona con las versiones nuevas, 3.x). Ejecutar el script runconfig: dsevilla@neuromancer:~/prog/ob/OB-4.1.0$ ./runconfig ************************ * ORBacus Configurator * ************************ Enter ’c’ if you use a C shell, or ’b’ for a bourne shell: [b] b Please select from the following compiler/platform combinations: (1) (2) (3) (4) (5) SUN Forte 6 update 2 C++ 5.3 GCC 2.95.3 SGI C++ 7.2 or 7.3 HP aC++ A.03.27 AIX VisualAge C++ 5.0 2 SUN Solaris 2.6, 7 and 8 SUN Solaris, Linux SGI Irix 6.5 HP-UX B.11.00 AIX Version 4.3.x (6) Compaq C++ 6.2-024 Compaq Tru64 V5.1 Please choose your compiler/platform combination: [2] 2 Do you want to create shared libraries? [yes] Do you want optimized code to be generated? [no] Add debug information to the generated code? [no] Please enter any extra preprocessor flags, like ‘-I/some/directory’: [] Please enter any extra compiler flags, like ‘-pipe’: [] Please enter any extra linker flags, like ‘-L/some/directory’: [] Please enter any extra archiver flags, like ‘-xarch=v9’: [] Where do you want to install everything? [/home/dsevilla/prog/ob] To run ‘configure’, execute the following code in your shell: . ./go El directorio de instalación puede ser el que el usuario elija, como por ejemplo /usr/local. A partir de ahora lo llamaremos $ORBACUS. Finalmente, debemos ejecutar: dsevilla@neuromancer:~/prog/ob/OB-4.1.0$ . ./go Eso ejecuta una serie de comprobaciones necesarias para poder compilar el software. Finalmente, hay que ejecutar “make install min”, que construye ORBacus y lo instala 3 en el directorio $ORBACUS. El proceso de compilación es largo (al menos una hora). En el caso de tener que interrumpir el proceso, hay que repetir los pasos puestos en la última parte: Ejecutar el guión “go” y después “make install min”. El target install min instala sólo las partes mı́nimas necesarias (no ejemplos, demos, tests, etc.). En el caso de que el proceso fallase, se puede intentar en dos pasos, “make” y “make install”. 3. Variables de entorno Una vez compilado e instalado ORBacus en el directorio $ORBACUS, se tienen que establecer las variables de entorno necesarias: export PATH=$PATH:$ORBACUS/bin export LD LIBRARY PATH=$LD LIBRARY PATH:$ORBACUS/lib Es conveniente introducir esas definiciones dentro de un fichero, por ejemplo “orbacus-env”: export ORBACUS= (directorio donde ORBacus está instalado) export PATH=$PATH:$ORBACUS/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORBACUS/lib y ejecutarlo con: . orbacus-env (el punto es equivalente a la orden source del shell). 4. Primeros pasos Para comprender el funcionamiento de ORBacus y para introducir las caracterı́sticas del proceso de desarrollo con él, introduciremos un ejemplo sencillo de una aplicación que define un IDL, un cliente y un servidor. 4.1. Fichero IDL El fichero IDL, Hola.idl es el siguiente: 1 3 interface Hola { string dihola ( in long num ) ; }; 5 // $Id : Hola . idl 220 2004 -11 -01 21:10:05 Z dsevilla $ La función dihola simplemente retornará un string con la cadena ¨hola¨ concatenada tantas veces como le indique el parámetro num. Para generar los stubs y skeletons, hay que ejecutar el compilador de IDL: 4 idl Hola.idl Esto genera los ficheros de stub (Hola.h y Hola.cpp) y skeleton (Hola skel.h y Hola skel.cpp). 4.2. Cliente A continuación se muestra el cliente (cliente.cpp): Es un programa que es capaz de utilizar el objeto Hola: 2 4 6 # include # include # include # include < OB / CORBA .h > " Hola . h " < iostream > < stdlib .h > // Incluir las definiciones de ORBacus // Incluir el " stub " // argv [1] IOR // argv [2] número de veces que dice hola 8 10 12 14 16 18 20 22 int main ( int argc , char ** argv ) { try { // Iniciar el ORB CORBA :: ORB_var orb = CORBA :: ORB_init ( argc , argv ) ; // Recuperar la cadena de caracteres de la lı́nea // de comandos que guarda el IOR y convertirla // en una referencia a un objeto CORBA CORBA :: String_var s ; CORBA :: Object_var o = orb - > string_to_object ( argv [1]) ; // Obtener el número de la lı́nea de comandos CORBA :: Long i = atol ( argv [2]) ; 24 26 // Convertir el objeto CORBA a un objeto de la clase // Hola Hola_var h = Hola :: _narrow ( o ) ; 28 30 32 34 36 // Llamar al método dihola usando la referencia al objeto s = h - > dihola ( i ) ; std :: cout << s << std :: endl ; } catch (...) { std :: cerr << " quietorl ! " << std :: endl ; } } 38 // $Id : cliente . cpp 174 2004 -09 -28 23:33:43 Z dsevilla $ El cliente realiza las siguientes labores: Lı́nea 1: Incluye el fichero necesario para los programas CORBA que utilizan ORBacus. 5 Lı́nea 2: Incluye el stub para el tipo de objeto Hola. Lı́nea 14: Se inicia el ORB. Normalmente, el ORB se implementa como una función de librerı́a. Esta llamada realiza todas las funciones de inicialización para el ORB. Lı́nea 20: Se utiliza la operación del ORB string to object para convertir el IOR dado en la lı́nea de comando como primer parámetro en una referencia a un objeto CORBA. Todos los objetos CORBA heredan de la interfaz CORBA::Object. Lı́nea 27: El método narrow se utiliza para especializar la referencia obtenida a un objeto del tipo especı́fico. En este caso Hola. La función devolverá en h la referencia especializada a un objeto de tipo Hola, o un objeto nulo si la conversión no se puede realizar (el objeto en realidad no era del tipo especı́fico requerido). Para comprobar si una referencia a un objeto CORBA es nula, se utiliza la función CORBA::is nil. Lı́nea 30: Se realiza la llamada propiamente dicha. La abstracción proporcionada por CORBA permite hacer una llamada al objeto remoto como si fuera un objeto local. El resultado se introduce en la variable s. 4.3. Implementación del servant Para implementar un objeto CORBA, esto es, para ofrecer sus servicios al mundo, se tienen que implementar dos cosas: El servant que contiene la implementación de los métodos del interfaz que se ofrece al exterior, y un servidor, que quedará esperando conexiones en un puerto IP. El servant es simplemente un objeto del lenguaje de programación (en este caso C++) que implementa la funcionalidad de los métodos del objeto CORBA. Este servant es llamado por el skeleton cuando un cliente llama a un método del objeto CORBA implementado por ese servant. El compilador de IDL de ORBacus ofrece la posibilidad de generar automáticamente una implementación vacı́a del servant que podrá modificar el programador para implementar las operaciones. Este esqueleto vacı́o se genera ejecutando: idl --impl Hola.idl Esto genera dos ficheros “vacı́os”: Hola impl.h y Hola impl.cpp. A continuación se lista el fichero Hola impl.h: 1 # ifndef ___Hola_impl_h__ # define ___Hola_impl_h__ 3 # include < Hola_skel .h > 5 6 7 9 11 13 // // IDL : Hola :1.0 // class Hola_impl : virtual public POA_Hola , virtual public PortableServer :: RefCountServantBase { Hola_impl ( const Hola_impl &) ; void operator =( const Hola_impl &) ; PortableServer :: POA_var poa_ ; 15 17 public : Hola_impl ( PortableServer :: POA_ptr ) ; ~ Hola_impl () ; 19 21 virtual PortableServer :: POA_ptr _default_POA () ; 23 // // IDL : Hola / dihola :1.0 // virtual char * dihola ( CORBA :: Long num ) throw ( CORBA :: SystemException ) ; 25 27 29 }; 31 # endif 33 // $Id : Hola_impl . h 174 2004 -09 -28 23:33:43 Z dsevilla $ De destacar en este fichero es: El servant se implementa en una clase Hola impl. Todas las clases servant heredan de la clase POA <interfaz>. El interfaz de ese objeto, salvo algunos métodos adicionales que se explican en la teorı́a de la asignatura, sigue el definido en el IDL, con la operación dihola. El código del servant es el que se muestra a continuación. Se ha implementado la función dihola con una implementación trivial en el fichero Hola impl.cpp: 1 # include < OB / CORBA .h > # include < Hola_impl .h > 3 5 7 9 // // IDL : Hola :1.0 // Hola_impl :: Hola_impl ( PortableServer :: POA_ptr poa ) : poa_ ( PortableServer :: POA :: _duplicate ( poa ) ) { } 11 Hola_impl ::~ Hola_impl () 7 13 { } 15 17 19 PortableServer :: POA_ptr Hola_impl :: _default_POA () { return PortableServer :: POA :: _duplicate ( poa_ ) ; } 21 23 25 27 29 // // IDL : Hola / dihola :1.0 // char * Hola_impl :: dihola ( CORBA :: Long num ) throw ( CORBA :: SystemException ) { char * _r = CORBA :: string_alloc (4 * num + 1) ; for ( size_t i = 0; i < num ; ++ i ) strcpy ( _r + 4* i , " Hola " ) ; 31 33 return _r ; 35 } 37 // $Id : Hola_impl . cpp 174 2004 -09 -28 23:33:43 Z dsevilla $ 4.4. El servidor En cualquier aplicación CORBA debe existir un servidor que quede esperando las peticiones sobre los objetos CORBA implementados por él (servants). El servidor es un programa C++ normal que dejará activado un servant para el objeto CORBA. 1 # include < OB / CORBA .h > 3 # include " Hola_impl . h " 5 # include < cstdlib > # include < fstream > 7 using namespace std ; 9 11 13 int main ( int argc , char * argv [] , char *[]) { CORBA :: ORB_var orb ; 15 17 try { orb = CORBA :: ORB_init ( argc , argv ) ; 19 // 8 // Obtener el POA raı́z // CORBA :: Object_var poaObj = orb - > r e s o l v e _ i n i t i a l _ r e f e r en c e s ( " RootPOA " ) ; PortableServer :: POA_var rootPOA = PortableServer :: POA :: _narrow ( poaObj ) ; 21 23 25 // // Obtener el POA manager // PortableServer :: POAManager_var manager = rootPOA - > the_POAManager () ; 27 29 // // Crear el objeto implementación // Hola_impl * hImpl = new Hola_impl ( rootPOA ) ; CORBA :: Object_var o = rootPOA - > s e r v a n t _ t o _ r e f e r e n c e ( hImpl ) ; Hola_var hola = Hola :: _narrow ( o ) ; 31 33 35 37 // // Grabar la referencia en un fichero // CORBA :: String_var s = orb - > object_to_string ( hola ) ; 39 41 const char * refFile = " Hola . ref " ; ofstream out ( refFile ) ; if ( out . fail () ) { cerr << argv [0] << " : no puedo abrir " << refFile << endl ; return 1; } 43 45 47 49 out << s << endl ; out . close () ; 51 53 // // Ejecutar la implementación // manager - > activate () ; orb - > run () ; 55 57 59 return 0; } catch ( const CORBA :: Exception & ex ) { cerr << ex << endl ; return 1; } 61 63 65 67 } 69 // $Id : servidor . cpp 174 2004 -09 -28 23:33:43 Z dsevilla $ 9 El servidor contiene casi toda la carga de la dificultad de la programación con CORBA. Las tareas que implementa el servidor son las siguientes: Lı́nea 18: El ORB se inicia como en el cliente. Lı́neas 23–24: Se obtiene el POA raı́z. El objeto servidor se tiene que registrar en un adaptador de objetos (OA). En CORBA, el POA es el adaptador de objetos, que se puede configurar como una jerarquı́a. En este caso, registraremos el objeto en el POA raı́z (RootPOA). Para la mayorı́a de los usos, este adaptador de objetos es suficiente. Lı́nea 19: Se obtiene el POA Manager. Este manager controla a un conjunto de adaptadores de objetos, permitiéndoles funcionar, o bien encolar las peticiones o rechazarlas. Lı́nea 34: Se crea un objeto servant Hola impl. Lı́nea 35: Se utiliza el método servant to reference del POA para obtener una referencia CORBA a partir de un servant. Lı́nea 36: La referencia se convierte a una referencia de un interfaz Hola. Este paso no es necesario, se muestra por completitud. Lı́nea 41: La referencia se convierte en una cadena de caracteres con la función del ORB object to string. Lı́neas 43–52: La referencia en formato cadena de caracteres se escribe al fichero “Hola.ref”. Lı́nea 57: Se activa el manager del POA. Desde ese momento, todos los objetos registrados en ese POA puede recibir invocaciones de métodos. Lı́nea 58: El ORB se pone a funcionar (esperar peticiones). El programa queda ası́ esperando las peticiones de los clientes. 4.5. Makefile Es importante construir un buen fichero Makefile, debido a que se tienen que utilizar varios programas y generar varios ficheros, por lo que el fichero para el programa make no es trivial. A continuación se muestra un fichero Makefile para el ejemplo que estamos considerando: 1 3 5 # Makefile # # Suponemos que la variable ORBACUS está definida en el entorno , y que # el compilador de IDL está dentro de los directorios listados en la variable # $PATH . Si no , otra alternativa serı́a utilizar $ORBACUS / bin / idl 10 7 9 # CPPFLAGS = - I$ ( ORBACUS ) / include -I . LDFLAGS = - L$ ( ORBACUS ) / lib LIBS = - lOB - lJTC - lpthread - ldl 15 IDLFILE = Hola . idl GENFILES = $ ( patsubst %.idl , % _skel .h , $ ( IDLFILE ) ) \ $ ( patsubst %.idl , % _skel . cpp , $ ( IDLFILE ) ) \ $ ( patsubst %.idl , %.h , $ ( IDLFILE ) ) \ $ ( patsubst %.idl , %. cpp , $ ( IDLFILE ) ) 17 all : cliente servidor 19 $ ( GENFILES ) : $ ( IDLFILE ) 21 cliente : cliente . o Hola . o $ ( CXX ) -o $@ $ ( LDFLAGS ) $ ( LIBS ) $ ^ 11 13 23 25 servidor : servidor . o Hola_skel . o Hola . o Hola_impl . o $ ( CXX ) -o $@ $ ( LDFLAGS ) $ ( LIBS ) $ ^ 27 # Parte genérica 29 %_skel . h %_skel . cpp %.h %.cpp : %.idl idl $ < 31 33 35 clean : rm - rf cliente servidor $ ( GENFILES ) *. o core *~ . depend . depend : $ ( CPP ) $ ( CPPFLAGS ) - MM - MG *. cpp 2 >/ dev / null > $@ 37 - include . depend 39 # $Id : Makefile 174 2004 -09 -28 23:33:43 Z dsevilla $ Las librerı́as utilizadas son parte del ORB (OB y JTC), además de las librerı́as necesarias para ese ORB en Linux (-lpthread, -ldl). La ejecución de make genera una salida como la siguiente: $ make gcc -E -I/opt2/corbalc/orbs/orbacus/include -I. -MM -MG *.cpp > .depend idl Hola.idl g++ -I/opt2/corbalc/orbs/orbacus/include -I. -c -o cliente.o cliente.cpp g++ -I/opt2/corbalc/orbs/orbacus/include -I. -c -o Hola.o Hola.cpp g++ -o cliente -L/opt2/corbalc/orbs/orbacus/lib -lOB -lJTC -lpthread -ldl cliente.o Hola.o g++ -I/opt2/corbalc/orbs/orbacus/include -I. -c -o servidor.o servidor.cpp g++ -I/opt2/corbalc/orbs/orbacus/include -I. -c -o Hola_skel.o Hola_skel.cpp g++ -I/opt2/corbalc/orbs/orbacus/include -I. -c -o Hola_impl.o Hola_impl.cpp g++ -o servidor -L/opt2/corbalc/orbs/orbacus/lib -lOB -lJTC -lpthread 11 -ldl servidor.o Hola_skel.o Hola.o Hola_impl.o 4.6. Ejecución La ejecución de la aplicación necesita primero de la ejecución del servidor. Tal y como se ha visto, el servidor crea un fichero llamado Hola.ref, en el que introduce el IOR del objeto servidor. En principio ejecutaremos el cliente y el servidor en la misma máquina, aunque usando el IOR, que se puede transmitir por correo o por cualquier otro medio, el cliente podrı́a utilizar nuestro objeto servidor desde cualquier otro ordenador. La ejecución1 : $ ./servidor & Esto crea el fichero Hola.ref: IOR:01dc05080d00000049444c3a486f6c613a312e30003d6040010000000000000 078000000010102000a0000006c6f63616c686f7374003b8525000000abacab3131 303636373432323731005f526f6f74504f410000cafebabe3f9531ff00000000000 00001000000010000002c0000000116000001000100040000002000010009010100 000101000100010509010100020000000001010001000105 Como curiosidad, el programa iordump (también perteneciente a ORBacus), permite obtener los datos del IOR: $ iordump ‘cat Hola.ref‘ IOR #1: byteorder: little endian type_id: IDL:Hola:1.0 Profile #1: iiop iiop_version: 1.2 host: localhost port: 34107 object_key: (37) 171 172 171 49 49 48 54 54 "...11066" 55 52 50 50 55 49 0 95 "742271._" 82 111 111 116 80 79 65 0 "RootPOA." 0 202 254 186 190 63 149 49 ".....?.1" 255 0 0 0 0 "....." Native char code set: "ISO 8859-1:1987; Latin Alphabet No. 1" Char conversion code sets: "ISO 646:1991 IRV (International Reference Version)" "ISO/IEC 10646-1:1993; UTF-16, UCS Transformation Format 16-bit form" 1 En el caso de que el ordenador no tenga bien configurada la red, se puede utilizar el comando de ejecución ./servidor -OAhost localhost & (la opción -OAhost especifica la IP a utilizar). 12 "ISO/IEC 10646-1:1993; UCS-2, Level 1" "X/Open UTF-8; UCS Transformation Format 8 (UTF-8)" Native wchar code set: "ISO/IEC 10646-1:1993; UTF-16, UCS Transformation Format 16-bit form" Wchar conversion code sets: "ISO/IEC 10646-1:1993; UCS-2, Level 1" "X/Open UTF-8; UCS Transformation Format 8 (UTF-8)" Finalmente, el cliente se puede ejecutar recibiendo como primer parámetro el IOR al que conectar y como segundo el número de veces que se concatenará la palabra “Hola”: $ ./cliente ‘cat Hola.ref‘ 6 HolaHolaHolaHolaHolaHola 5. 5.1. Aspectos avanzados: Threads Uso de Threads en ORBacus ORBacus incluye una librerı́a de threads llamada JTC (Java-Threads for C++). Su uso es muy similar a los hilos de Java. Ası́, al crear una clase C++ se le puede especificar que es un thread: # include < JTC / JTC .h > 2 4 6 class Hilo : public JTCThread { void run () ; }; Para el manejo automático de la memoria asociada a un hilo, también existe la clase JTCThreadHandle: 2 JTCThreadHandle h = new Hilo () ; h - > start () ; Los threads también ofrecen las operaciones isAlive() y terminate(), para comprobar si el hilo se está ejecutando y para terminarlo en su caso. Los hilos se pueden convertir en monitores para permitir que se bloqueen y para permitir hacer secciones crı́ticas: 2 4 6 8 10 class Hilo : public JTCThread , public JTCMonitor { public : // Método sincronizado void método () { JTCSynchronized s (* this ) ; } // Método con una región sincronizada void método2 () { 13 // otras cosas ... { JTCSyncrhonized s (* this ) ; // Región sincronizada } 12 14 } 16 }; El método ((método)) es una sección crı́tica. Nadie podrá ejecutarlo si ya se está ejecutando. Las clases JTCSynchronized siguen el patrón ((reserva de recursos en creación)), ası́ que cuando sale de ámbito la variable se elimina el lock sobre el candado. Véase también Henning & Vinoski, cap. 21, aunque ahı́ utilizan ACE (Adaptative Communication Envionment, Schmidt et al., http://www.cs.wustl.edu/~schmidt). 5.2. El ejemplo El siguiente ejemplo muestra el uso de threads en la implementación de una aplicación distribuida con múltiples clientes. El ejemplo muestra un programa servidor que está conectado a un ((sensor)). El sensor emite valores periódicamente o cuando las condiciones del medio cambian. Los clientes se suscriben a los eventos que emiten el sensor. El ejemplo es interesante porque se puede dar que se conecten virtualmente muchos clientes para leer los datos. El programa no puede tardar mucho tiempo en informar a los clientes, ya que los eventos del sensor pueden ser muy próximos en el tiempo (es decir, tiene que ser escalable con respecto al número de clientes). En la implementación se utilizarán threads, una para cada cliente. Sin embargo, se pueden utilizar otras alternativas que veremos en la teorı́a, como un ((pool)) de treads, etc. 5.3. Definiciones IDL Las definiciones IDL son básicas. Primero las estructuras comunes. Muestran los datos que genera el sensor y que se transmiten entre los clientes suscritos y el servidor (fichero SensorData.idl): 1 # ifndef SENSOR_DATA # define SENSOR_DATA 3 5 7 struct SensorData { long windspeed ; long other ; }; 9 # endif 11 // $Id : SensorData . idl 220 2004 -11 -01 21:10:05 Z dsevilla $ El interfaz del sensor es sencillo: permite añadir y eliminar ((listeners)) que recibirán los datos, y un método para poner a funcionar el sensor: 14 2 4 6 # include " SensorData . idl " # include " SensorListener . idl " interface Sensor { void addListener ( in SensorListener listener ) ; void removeListener ( in SensorListener listener ) ; 8 // Run sensor void run () ; 10 }; 12 // $Id : Sensor . idl 220 2004 -11 -01 21:10:05 Z dsevilla $ 1 # include " SensorData . idl " 3 interface SensorListener { oneway void nueva_lectura ( in SensorData v ) ; }; 5 7 // $Id : SensorListener . idl 233 2004 -11 -08 18:22:40 Z dsevilla $ Este último fichero define el listener. Como se ve, la función de notificación es ((oneway)). Este diseño suele ser el usual en aplicaciones de este tipo. Sin embargo, si se quiere asegurar que no se pierden mensajes, el método de notificación podrı́a ser un método normal, no oneway. 5.4. El cliente Como se ha dicho, en este caso, tanto el cliente como el servidor deben esperar peticiones: el servidor peticiones de añadir listeners; el cliente informaciones del sensor a través del método nueva lectura. Ası́, el cliente debe implementar el objeto SensorListener para que el servidor pueda informarle de cambios en sl sensor. La implementación no tiene grandes problemas y es sencilla: 2 # ifndef _ _ _ S e n s o r L i s t e n e r _ i m p l _ h _ _ # define _ _ _ S e n s o r L i s t e n e r _ i m p l _ h _ _ 4 # include < SensorListener_skel .h > 6 // // IDL : SensorListener :1.0 // class SensorListener_impl : virtual public POA_SensorListener , virtual public PortableServer :: RefCountServantBase { SensorListener_impl ( const SensorListener_impl &) ; void operator =( const SensorListener_impl &) ; 8 10 12 14 15 PortableServer :: POA_var poa_ ; 16 CORBA :: ULong id_ ; 18 public : 20 22 SensorListener_impl ( PortableServer :: POA_ptr , CORBA :: ULong id ) ; ~ SensorListener_impl () ; 24 virtual PortableServer :: POA_ptr _default_POA () ; 26 // // IDL : SensorListener / nueva_lectura :1.0 // virtual void nueva_lectura ( const SensorData & v ) throw ( CORBA :: SystemException ) ; 28 30 }; 32 # endif 1 3 5 7 9 11 # include < OB / CORBA .h > # include < SensorListener_impl .h > # include < iostream > // // IDL : SensorListener :1.0 // SensorListener_impl :: SensorListener_impl ( PortableServer :: POA_ptr poa , CORBA :: ULong id ) : poa_ ( PortableServer :: POA :: _duplicate ( poa ) ) , id_ ( id ) { } 13 15 SensorListener_impl ::~ SensorListener_impl () { } 17 19 21 PortableServer :: POA_ptr SensorListener_impl :: _default_POA () { return PortableServer :: POA :: _duplicate ( poa_ ) ; } 23 25 27 29 31 // // IDL : SensorListener / nueva_lectura :1.0 // void SensorListener_impl :: nueva_lectura ( const SensorData & v ) throw ( CORBA :: SystemException ) { std :: cerr << " Recibido evento ( " << id_ << " ) . windspeed = " << v . windspeed 16 << " other = " << v . other << std :: endl ; 33 35 } 37 // $Id : SensorListener_impl . cpp 174 2004 -09 -28 23:33:43 Z dsevilla $ El método nueva lectura, cuando es llamado por el servidor, imprime los valores en la salida estándar. 1 3 5 7 9 // -* - mode : c ++; c - basic - offset : 8; -* # include < OB / CORBA .h > // Incluir las definiciones de ORBacus # include " SensorListener . h " // Incluir el " stub " # include " Sensor . h " # include " SensorData . h " # include " SensorListener_impl . h " # include < iostream > # include < stdlib .h > # include < vector > 11 const int NUM_LISTENERS =1000; 13 // argv [1] IOR 15 typedef std :: vector < SensorListener_impl * > ImplVector ; typedef std :: vector < SensorListener_var > PtrVector ; 17 19 21 23 25 int main ( int argc , char ** argv ) { try { ImplVector iv ( NUM_LISTENERS ) ; PtrVector pv ( NUM_LISTENERS ) ; // Iniciar el ORB CORBA :: ORB_var orb = CORBA :: ORB_init ( argc , argv ) ; 27 29 31 33 // // Obtener el POA raı́z // CORBA :: Object_var poaObj = orb -> r e s o l v e _ i n i t i a l _ r e f e r e n c e s ( " RootPOA " ) ; PortableServer :: POA_var rootPOA = PortableServer :: POA :: _narrow ( poaObj ) ; 35 37 39 // // Obtener el POA manager // PortableServer :: POAManager_var manager = rootPOA -> the_POAManager () ; 41 43 // // Ejecutar la implementación // 17 45 manager -> activate () ; 47 // Recuperar la cadena de caracteres de la lı́nea // de comandos que guarda el IOR y convertirla // en una referencia a un objeto CORBA CORBA :: Object_var o = orb - > string_to_object ( argv [1]) ; 49 51 53 // Convertir el objeto CORBA a un objeto de la clase // SensorListener Sensor_var s = Sensor :: _narrow ( o ) ; 55 57 59 61 63 65 67 // // Crear mil objetos listener // for ( CORBA :: ULong i = 0; i < NUM_LISTENERS ; i ++) { SensorListener_impl * hImpl = new SensorListener_impl ( rootPOA , i ) ; CORBA :: Object_var o = rootPOA -> s e r v a n t _ t o _ r e f e r e n c e ( hImpl ) ; SensorListener_var listener = SensorListener :: _narrow ( o ) ; iv [ i ] = hImpl ; pv [ i ] = listener ; 69 // A~ n adir el listener al sensor s - > addListener ( listener . in () ) ; 71 } 73 75 s - > run () ; 77 orb -> run () ; 79 for ( CORBA :: ULong i = 0; i < NUM_LISTENERS ; i ++) { // Eliminar de la lista del sensor s - > removeListener ( pv [ i ]) ; 81 83 85 87 89 // Desactivar los objetos del POA PortableServer :: ObjectId_var oid = rootPOA - > reference_to_id ( pv [ i ]) ; rootPOA -> deactivate_object ( oid . in () ) ; // Eliminar las referencias // CORBA :: release ( pv [ i ]) ; 91 93 95 97 // Eliminar los servants delete iv [ i ]; } } catch (...) { std :: cerr << " quietorl ! " << std :: endl ; } 18 } 99 // $Id : cliente . cpp 233 2004 -11 -08 18:22:40 Z dsevilla $ 101 El cliente es algo más complejo. Básicamente: 1. Crea 1000 listeners. 2. Los registra con el sensor (lı́neas 59–72). 3. Inicia el sensor (lı́nea 75). 4. Desregistra y desactiva los listeners (lı́neas 79–94). Para ello, mantiene dos listas: iv, que guarda un array de servants y pv, que guarda un array de punteros a objetos CORBA (los listeners). 5.5. La implementación del servidor del sensor Empecemos con la definición del fichero de cabeceras C++ del sensor (Sensor impl.h): 1 # ifndef ___Sensor_impl_h__ # define ___Sensor_impl_h__ 3 # include < SensorListener_impl .h > 5 # include < Sensor_skel .h > 7 # include < list > 9 typedef std :: list < SensorListener_ptr > ListenerList ; 11 13 15 17 19 // // IDL : Sensor :1.0 // class Sensor_impl : virtual public POA_Sensor , virtual public PortableServer :: RefCountServantBase { Sensor_impl ( const Sensor_impl &) ; void operator =( const Sensor_impl &) ; 21 PortableServer :: POA_var poa_ ; 23 ListenerList ll_ ; 25 27 public : Sensor_impl ( PortableServer :: POA_ptr ) ; ~ Sensor_impl () ; 29 virtual PortableServer :: POA_ptr _default_POA () ; 31 19 // // IDL : Sensor / addListener :1.0 // virtual void addListener ( SensorListener_ptr listener ) throw ( CORBA :: SystemException ) ; 33 35 37 // // IDL : Sensor / removeListener :1.0 // virtual void removeListener ( SensorListener_ptr listener ) throw ( CORBA :: SystemException ) ; 39 41 43 // // IDL : Sensor / run :1.0 // virtual void run () throw ( CORBA :: SystemException ) ; 45 47 49 }; 51 # endif El fichero de implementación del servant es algo más complicado. Se muestra a continuación (después del listado se explica detalladamente): 1 # include < OB / CORBA .h > # include < Sensor_impl .h > 3 5 # include < iostream > # include < JTC / JTC .h > 11 // Clase - thread que envı́a un evento a un listener class ListenerNotifier : public JTCThread { SensorListener_ptr sl_ ; SensorData d_ ; 13 public : 7 9 ListenerNotifier ( SensorListener_ptr sl , const SensorData & d) : sl_ ( sl ) , d_ ( d ) { } 15 17 // Método run del thread void run () { sl_ - > nueva_lectura ( d_ ) ; } 19 21 23 }; 25 27 29 // Clase sin estado que gestiona el envı́o class L i s t e n e r G r o u p N o t i f i e r { typedef std :: list < JTCThreadHandle > ThreadList ; 20 ThreadList tl_ ; 31 public : void notify ( const ListenerList & ll , const SensorData & d ) { for ( ListenerList :: const_iterator it = ll . begin () , end = ll . end () ; it != end ; it ++) { JTCThreadHandle ln = new ListenerNotifier (* it , d ) ; ln -> start () ; 33 35 37 39 41 tl_ . push_back ( ln ) ; 43 } 45 // Sincronización final . Se utiliza el método " join " // para esperar a que terminen los threads . for ( ThreadList :: const_iterator it = tl_ . begin () , end = tl_ . end () ; it != end ; it ++) { do { try { (* it ) -> join () ; } catch ( const J T C I n t e r r u p t e d E x c e p t i o n &) { } } while ((* it ) -> isAlive () ) ; } 47 49 51 53 55 57 59 } 61 }; 63 65 67 69 // // IDL : Sensor :1.0 // Sensor_impl :: Sensor_impl ( PortableServer :: POA_ptr poa ) : poa_ ( PortableServer :: POA :: _duplicate ( poa ) ) { } 71 73 75 77 79 Sensor_impl ::~ Sensor_impl () { // Eliminar todos los listeners for ( ListenerList :: iterator it = ll_ . begin () , end = ll_ . end () ; it != end ; it ++) { CORBA :: release (* it ) ; ll_ . erase ( it ) ; 21 } 81 } 83 PortableServer :: POA_ptr Sensor_impl :: _default_POA () { return PortableServer :: POA :: _duplicate ( poa_ ) ; } 85 87 89 91 93 95 97 // // IDL : Sensor / addListener :1.0 // void Sensor_impl :: addListener ( SensorListener_ptr listener ) throw ( CORBA :: SystemException ) { // TODO : Comprobar duplicados ll_ . push_back ( SensorListener :: _duplicate ( listener ) ) ; } 99 101 103 105 107 109 111 113 115 117 119 121 123 125 127 // // IDL : Sensor / removeListener :1.0 // void Sensor_impl :: removeListener ( SensorListener_ptr listener ) throw ( CORBA :: SystemException ) { // OJO : Este código no es seguro , por los problemas de identidad // en CORBA . for ( ListenerList :: iterator it = ll_ . begin () , end = ll_ . end () ; it != end ; it ++) { if ( listener - > _is_equivalent (* it ) ) { CORBA :: release (* it ) ; ll_ . erase ( it ) ; break ; } } } // // IDL : Sensor / run :1.0 // void Sensor_impl :: run () throw ( CORBA :: SystemException ) { std :: cerr << " Servidor : comenzando " << std :: endl ; 129 131 SensorData * d = new SensorData ; d - > windspeed = 10; 22 d - > other = 20; 133 135 L i s t e n e r G r o u p N o t i f i e r lgn ; lgn . notify ( ll_ , * d ) ; 137 delete d ; std :: cerr << " Servidor : finalizado " << std :: endl ; 139 } 141 // $Id : Sensor_impl . cpp 1744 2007 -03 -15 20:37:01 Z dsevilla $ La implementación del sensor crea un hilo para notificar a cada listener. Nótese que el crear tantos hilos como elementos a notificar es un riesgo, por lo que se tendrı́an que idear otras técnicas, como un conjunto de hilos fijo, etc. Esto se deja como ejercicio. La clase que implementa ese hilo es ListenerNotifier (lı́nea 8). ORBacus utiliza la librerı́a JTC (Java-Threads for C++). Ası́, los hilos son parecidos a los de Java: heredan de JTCThread y tiene un método run. Cada clase ListenerNotifier tiene un SensorListener al que envı́a los datos del sensor. La clase ListenerGroupNotifier es una abstracción para una comunicación de grupo. Crea todos los hilos, creando cada uno de los objetos ListenerNotifier (hilos). Cada uno de los hilos se inicia con start (lı́nea 40), y se almacenan en una lista de hilos (tl ). Esta lista guarda objetos de tipo JTCThreadHandle. Estos objetos son handlers para los hilos que manejan la memoria de los mismos (no hace falta destruirlos, se destruyen automáticamente al salir de ámbito, como el idiom de adquisición de recurso en creación). El siguiente bucle (lı́neas 47–59), utiliza la función join para esperar a que todos los hilos terminen. Las funciones addListener y removeListener no tienen mucho problema. Su implementación es normal. Por último, el método run envı́a un evento de datos al grupo de los listeners utilizando la clase ListenerGroupNotifier. En cuanto al servidor, simplemente crea y activa el objeto Sensor: # include < OB / CORBA .h > 2 # include " Sensor_impl . h " 4 6 # include < stdlib .h > # include < fstream > 8 using namespace std ; 10 12 int main ( int argc , char * argv [] , char *[]) { CORBA :: ORB_var orb ; 14 16 try { orb = CORBA :: ORB_init ( argc , argv ) ; 23 18 // // Obtener el POA raı́z // CORBA :: Object_var poaObj = orb -> r e s o l v e _ i n i t i a l _ r e f e r en c e s ( " RootPOA " ) ; PortableServer :: POA_var rootPOA = PortableServer :: POA :: _narrow ( poaObj ) ; 20 22 24 // // Obtener el POA manager // PortableServer :: POAManager_var manager = rootPOA -> the_POAManager () ; 26 28 // // Crear el objeto implementación // Sensor_impl * hImpl = new Sensor_impl ( rootPOA ) ; CORBA :: Object_var o = rootPOA -> s e r v a n t _ t o _ r e f e r e n c e ( hImpl ) ; Sensor_var sensor = Sensor :: _narrow ( o ) ; 30 32 34 36 // // Grabar la referencia en un fichero // CORBA :: String_var s = orb -> object_to_string ( sensor ) ; 38 40 const char * refFile = " Sensor . ref " ; ofstream out ( refFile ) ; if ( out . fail () ) { cerr << argv [0] << " : no puedo abrir " << refFile << endl ; return 1; } 42 44 46 48 out << s << endl ; out . close () ; 50 52 // // Ejecutar la implementación // manager -> activate () ; orb -> run () ; 54 56 58 return 0; } catch ( const CORBA :: Exception & ex ) { cerr << ex << endl ; return 1; } 60 62 64 66 } 24 68 5.6. // $Id : servidor . cpp 174 2004 -09 -28 23:33:43 Z dsevilla $ Makefiles Por último, veamos los ficheros make. Estos ejemplos de makefiles son algo más sofisticados que los anteriores. Primero, el fichero Make.rules guarda las reglas generales para los ficheros IDL. Los dos macros, idlrule y process idls, generan las reglas (en tiempo de ejecución) para cada fichero IDL a partir de la variable $(IDLFILES): # -* - Makefile -* - vim : set ft = make : 2 4 6 8 10 12 14 # Para cada fichero IDL , genera una regla de compilación define idlrule $ (1) _skel . h $ (1) _skel . cpp $ (1) . h $ (1) . cpp : $ (1) . idl idl $$ < CLEANFILES += $ (1) _skel . h $ (1) _skel . cpp $ (1) . h $ (1) . cpp GENFILES += $ ( CLEANFILES ) endef # Procesa un conjunto de ficheros IDL en la variable IDLFILES define process_idls $ ( foreach i , $ (1) , $ ( eval $ ( call idlrule , $ ( i ) ) ) ) endef 16 $ ( call process_idls , $ ( IDLFILES ) ) 18 20 clean : rm - rf $ ( CLEANFILES ) 22 # $Id : Make . rules 234 2004 -11 -09 00:07:51 Z dsevilla $ El resto de Makefile incluye a este fichero, y describe las reglas para compilar cliente y servidor. 10 # Makefile # # Suponemos que la variable ORBACUS está definida en el entorno , y que # el compilador de IDL está dentro de los directorios listados en la variable # $PATH . Si no , otra alternativa serı́a utilizar $ORBACUS / bin / idl # DEBUG = - g CPPFLAGS = - I$ ( ORBACUS ) / include -I . $ ( DEBUG ) LDFLAGS = - L$ ( ORBACUS ) / lib LIBS = - lOB - lJTC - lpthread - ldl 12 IDLFILES = Sensor SensorListener SensorData 14 all : cliente servidor 2 4 6 8 25 16 include Make . rules 18 CLEANFILES +=*~ *. o cliente servidor . depend core Sensor . ref 20 cliente : cliente . o Sensor . o \ SensorData_skel . o SensorData . o SensorListener_impl . o \ SensorListener . o SensorListener_skel . o $ ( CXX ) -o $@ $ ( LDFLAGS ) $ ( LIBS ) $ ^ 22 24 servidor : servidor . o Sensor_skel . o SensorData . o \ SensorData_skel . o Sensor . o Sensor_impl . o SensorListener . o $ ( CXX ) -o $@ $ ( LDFLAGS ) $ ( LIBS ) $ ^ 26 28 # Parte genérica 30 32 . depend : $ ( CPP ) $ ( CPPFLAGS ) - MM - MG *. cpp 2 >/ dev / null > $@ 34 - include . depend 36 # $Id : Makefile 174 2004 -09 -28 23:33:43 Z dsevilla $ 5.7. Configuración de Threads en ORBacus ORBacus permite la configuración de las caracterı́sticas de los threads para cada aplicación. Además, se puede hacer de dos modos: a través de la lı́nea de órdenes ó a través de un API propietario de ORBacus. En cuanto a la lı́nea de órdenes, se pueden especificar varios parámetros: Modo de funcionamiento del ORB: -ORBreactive (sin threads), -ORBthreaded (con threads). Modo de funcionamiento del adaptador de objetos (OA): • -OAreactive, -OAthreaded: modos básicos. • -OAthread per client, -OAthread per request, OAthread pool n Se pueden especificar en cualquier aplicación. En cuanto al API propietario, en la inicialización del ORB se puede escribir algo como esto: OB::Properties_var dflt = OB::Properties::getDefaultProperties(); OB::Properties_var props = new OB::Properties(dflt); props->setProperty("ooc.orb.conc_model", "threaded"); props->setProperty("ooc.orb.oa.conc_model", "thread_pool"); props->setProperty("ooc.orb.oa.thread_pool", "5"); CORBA::ORB_var orb = OBCORBA::ORB_init(argc, argv, props); Este código establece el modo ((Thread Pool)) con un tamaño de 5 hilos. 26 6. Revisión $Id: orbacus.tex 3144 2009-10-14 19:22:21Z dsevilla $ 27