Primeros pasos con ORBacus 4

Anuncio
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
Descargar