Java RMI
Java RMI
Los textos e imágenes publicados en esta obra están sujetos –excepto que se indique lo contrario– a una licencia de
Reconocimiento-Compartir igual (BY-SA) v.3.0 España de Creative Commons. Se puede modificar la obra, reproducirla, distribuirla
o comunicarla públicamente siempre que se cite el autor y la fuente (FUOC. Fundació per a la Universitat Oberta de Catalunya), y
siempre que la obra derivada quede sujeta a la misma licencia que el material original. La licencia completa se puede consultar en:
http://creativecommons.org/licenses/by-sa/3.0/es/legalcode.ca
CC-BY-SA • PID_00187406 Java RMI
Índice
Introducción............................................................................................... 5
Objetivos....................................................................................................... 6
1. Introducción a RMI........................................................................... 7
1.1. Qué es RMI .................................................................................. 7
1.2. Objetivos de RMI ........................................................................ 8
1.3. Características RMI ...................................................................... 9
1.3.1. Serialización ................................................................... 10
1.3.2. Paso de parámetros y valores de retorno ....................... 19
1.3.3. Localización ................................................................... 21
1.3.4. Activación de objetos remotos ...................................... 22
1.3.5. Recolector de basura distribuido ................................... 24
1.3.6. Excepciones .................................................................... 26
1.3.7. Seguridad ........................................................................ 27
2. Arquitectura RMI............................................................................... 29
2.1. El servidor ................................................................................... 30
2.2. El cliente ...................................................................................... 31
2.3. Stubs y skeletons............................................................................ 32
2.3.1. Evolución de los stubs y skeletons en Java ..................... 33
2.3.2. Proceso de invocación cliente/servidor ......................... 33
2.4. RMIRegistry.................................................................................... 34
Resumen....................................................................................................... 49
Actividades.................................................................................................. 51
CC-BY-SA • PID_00187406 Java RMI
Ejercicios de autoevaluación.................................................................. 51
Solucionario................................................................................................ 53
Glosario........................................................................................................ 55
Bibliografía................................................................................................. 57
CC-BY-SA • PID_00187406 5 Java RMI
Introducción
En este módulo estudiaremos el mecanismo de invocación remota RMI de Ja- Ved también
va. Tal como hemos visto en el módulo "Introducción a las plataformas dis-
Trataremos la tecnología de
tribuidas", Java sitúa RMI en el centro de su modelo de objetos distribuido y componentes Java en el módu-
representa la capa subyacente de comunicación distribuida de la tecnología lo "Java EE".
de componentes.
Objetivos
2. Entender las ventajas y las limitaciones de RMI comparado con otros pa-
radigmas de programación distribuida.
1. Introducción a RMI
(1)
En este apartado introduciremos el mecanismo de invocación remota de Java, Del inglés remote method invoca-
1 tion.
llamado RMI . Veremos sus características intrínsecas y objetivos esenciales,
que convierten esta tecnología en una de las más simples de usar para construir
Un poco de historia
aplicaciones distribuidas.
RMI forma parte de Java des-
de la versión 1.1 del Java de-
Entraremos a fondo en las características más importantes de RMI, que con- velopment kit (JDK), liberado
en 1997, y fue desarrollado en
sigue hacer transparente al desarrollador de aplicaciones distribuidas la com- la empresa Sun Microsystems.
plejidad interna de la red, tanto en la codificación de los datos por transmitir RMI fue revisado y se le intro-
dujeron importantes mejoras a
como en la gestión de las conexiones remotas. Esta transparencia representa partir del JDK 1.5, liberado en
el 2004, mejoras que perduran
la piedra angular del soporte RMI a las aplicaciones distribuidas. en nuestros días. En enero del
2010, la empresa Oracle ad-
quirió Sun Microsystems, y a
A partir del conocimiento adquirido en este apartado, estaremos en disposi- partir de ese momento adqui-
rió todos los derechos del len-
ción de tomar decisiones durante el desarrollo de nuestras aplicaciones distri- guaje Java, incluido RMI.
buidas y, de este modo, potenciar la capacidad distribuida.
Nota
1.1. Qué es RMI
Una JVM (Java virtual machine,
en inglés) es una máquina vir-
El mecanismo de invocación remota de Java, RMI, permite invocar métodos tual ejecutable en diferentes
de objetos que se encuentran en JVM diferentes del objeto que invoca (invo- plataformas, capaz de interpre-
tar y ejecutar instrucciones de
cación� remota) siguiendo el mismo procedimiento que una invocación de código binario especial (el by-
tecode Java), que es generado
métodos de objetos que se encuentran en la misma máquina y JVM (invoca- por el compilador del lenguaje
Java.
ción�local). Una invocación remota implica comunicar procesos separados si-
tuados en una misma máquina o en máquinas separadas situadas en diferen-
tes puntos geográficos.
CORBA
RMI representa el modelo de objetos distribuidos que propone Java co-
mo solución para desarrollar aplicaciones distribuidas. Java también da soporte al
modelo de objetos distribui-
dos de CORBA, mediante RMI-
IIOP. Repasad estos conceptos
en el módulo "Introducción a
En el contexto de una invocación remota, el objeto que invoca se denomina las plataformas distribuidas"
de este material didáctico. Sin
cliente, mientras que el objeto remoto, cuyos métodos se están invocando, se
embargo, en este módulo no
denomina servidor. En el caso general, el objeto local que efectúa la invoca- prestamos atención al modelo
CORBA desde Java, ya que tie-
ción interpreta el rol de cliente invocando los métodos de un objeto remoto ne un uso marginal.
que interpreta el rol de servidor. Aun así, a veces el objeto local y el remoto
pueden intercambiar sus roles de forma dinámica (por ejemplo, durante las
Nota
notificaciones y avisos al cliente, es el servidor el que inicia la interacción para
informar al cliente). Fijaos en que la respuesta del
servidor a la invocación de un
cliente no comporta ningún
cambio de rol, al estar la res-
Durante una invocación local (dentro de una misma JVM), un objeto local puesta del servidor incluida en
hace referencia directa a otro objeto local para invocar sus métodos. En con- el proceso mismo de invoca-
ción del cliente.
traposición, en una invocación remota, el objeto cliente nunca hace referencia
CC-BY-SA • PID_00187406 8 Java RMI
directamente al objeto servidor, sino a una interfaz remota, la cual establece Ved también
una clara separación entre la definición y la implementación del objeto servi-
Explicaremos el funcionamien-
dor. De este modo, se establece una separación entre las firmas de los métodos to interno de RMI en detalle en
y el código de dichos métodos. el apartado "Arquitectura RMI"
de este módulo.
En la parte negativa, RMI ha sido criticado por utilizar un protocolo de comu- Ved también
nicación restrictivo conocido como Java remote method protocol (JRMP). Este
Repasad el protocolo JRMP en
protocolo, al estar implementado en Java, obliga a que cliente y servidor es- el módulo "Introducción a las
tén implementados en Java. Esta restricción resta flexibilidad al modelo RMI plataformas distribuidas". En
este módulo damos por asumi-
en relación con otras tecnologías que admiten distintas tecnologías de imple- do que, cuando hablamos de
la comunicación RMI, se reco-
mentación. noce el protocolo de comuni-
cación JRMP.
Figura 1
Cuando se desarrolló RMI, se planteó como una solución para entornos exclu-
sivos Java con la finalidad última de simplificar el desarrollo de aplicaciones
distribuidas orientadas a objetos y escritas en Java. Se quería que RMI propor-
CC-BY-SA • PID_00187406 9 Java RMI
Para lograr tales retos, los creadores de RMI se fijaron los objetivos siguientes:
En los apartados siguientes veremos con detalle las características que ofrece
RMI a la programación distribuida, con las que consigue lograr los objetivos
expuestos.
RMI es una solución cliente-servidor basada en el conocido protocolo RPC, Ved también
al que añade el paradigma de la programación orientada a objetos y permite
Revisad el protocolo remote
la comunicación entre objetos remotos que se encuentran en JVM separadas. procedure call (RPC) en la asig-
RMI está construido sobre la base siguiente: natura Redes y aplicaciones In-
ternet.
Los puntos primero y segundo, el modelo orientado a objetos de Java y la Ved también
programación por sockets, ya han sido tratados en asignaturas anteriores. El
Revisad la orientación a obje-
tercer punto clave, el mecanismo de la serialización, se tratará a continuación tos de Java en la asignatura Di-
de forma detallada. seño y programación orientada
a objetos, y la programación
por sockets, en la asignatura
Redes y aplicaciones Internet.
También analizaremos otras características importantes de RMI, como el paso
de parámetros, el proceso de activación de objetos, el recolector de basura dis-
tribuido, el proceso de localización de objetos remotos, el uso de excepciones
en RMI y el soporte a la seguridad.
razones. En estos casos, deberá escoger la opción "manual" que Java siempre La escalabilidad de la aplica-
pone a disposición del programador e implementar estas características con su ción puede ser otra razón por
la que no se puedan utilizar las
propio código. Aun así, para la mayoría de los casos, al igual que para los que opciones RMI por defecto.
veremos en esta asignatura, se utilizará la implementación RMI por defecto
que proporciona Java.
1.3.1. Serialización
este proceso, obtendremos una copia del objeto original a partir de estos mis- El proceso de serialización
mos bytes de datos, y haremos lo que se conoce como deserialización. también se conoce como
marshalling, mientras que la
deserialización se conoce co-
mo unmarshalling.
Estos procesos se llevan a cabo sin apenas intervención del programador, lo
cual proporciona un elevado grado de transparencia, gracias a la abstracción
de los streams que proporciona Java, que representan un flujo o secuencia de Nota
bytes. Los streams permiten que dos programas se intercambien datos en forma
Los streams de Java se explican
de secuencia de bytes. Dichos programas pueden estar ejecutándose en dos con detalle a continuación.
máquinas o sistemas de comunicación diferentes y estar conectados en red
mediante otra abstracción muy importante, los sockets.
CC-BY-SA • PID_00187406 11 Java RMI
En RMI, los datos (objetos) que pasamos por parámetro durante la invo-
cación de un método remoto son serializados y convertidos en un stream
de bytes por el proceso que invoca. Este proceso escribe en un socket que
retransmite el stream por la red hasta llegar al socket de destino.
Figura 2
En la figura 2 vemos la relación entre RMI, sockets y streams durante una co-
nexión típica de red cliente/servidor. RMI abstrae la funcionalidad de los soc-
kets, que a su vez usan streams para enviar secuencias de datos entre programas
durante una comunicación en red a bajo nivel. Las flechas punteadas bidirec-
cionales representan la comunicación lógica en ambas direcciones. La capa
de transporte y red TCP/IP representa la comunicación activa entre cliente y
servidor.
Aunque ni los sockets ni los streams forman parte directamente de RMI, para Ved también
comprender el funcionamiento de la comunicación en red de Java y el meca-
La comprensión de los sockets
nismo de serialización de RMI, primero es preciso comprender estos dos ele- y de la comunicación en red
mentos. A continuación describimos los streams. de Java queda fuera del alcan-
ce de esta asignatura. Revisad
tales conceptos en la asignatu-
ra Redes y aplicaciones Internet.
CC-BY-SA • PID_00187406 12 Java RMI
Streams
Nota
Los datos (típicamente, bytes) solo se pueden añadir al stream uno por uno,
y solo se recuperan del mismo modo. Así pues, es preciso empezar a recorrer
la secuencia siempre desde el inicio hasta el final. No es posible recorrer la
secuencia de otro modo (ir hacia atrás, saltar sitios o empezar en medio). Una
vez recuperado un dato del stream, hay que moverse a la posición siguiente de
la secuencia y, una vez escrito un dato, no se puede borrar.
Aunque puede parecer que esta forma de tratar la información es pesada y poco
útil, los streams presentan una característica muy importante: la simplicidad.
El acceso secuencial a la información hace que los streams sean compatibles
con cualquier dispositivo, ya sea físico (por ejemplo, una impresora) o abstrac-
to (por ejemplo, un archivo). Esta simplicidad permite uniformar el acceso al
dispositivo por medio de un proceso trivial: primero, se asocia un stream a un
dispositivo y, después, la información es leída o escrita secuencialmente en
dicho dispositivo por medio del stream.
Las secuencias son una primitiva para representar información, y toda forma Ved también
más compleja de representación se puede ver como una aplicación recursiva de
Repasad las secuencias como
estas. Por ejemplo, para manejar bloques de diez datos, primero se recuperan tipos de datos en la asignatura
diez datos consecutivos de una secuencia y después se crea un bloque con ellos. Diseño de estructuras de datos.
Java ofrece dos clases principales para trabajar con streams: OutputS-
tream e InputStream.
OutputStream es una clase abstracta que representa un flujo de bytes de sa- Dispositivo de salida
lida. Un output stream acepta bytes de salida, que escribe en un dispositivo
Un dispositivo de salida se
genérico de salida. El método más importante de esta clase es write(byte), considera apropiado mientras
que va escribiendo en el dispositivo un byte cada vez que es llamado. Las sub- sea capaz de recibir o almace-
nar los datos que le llegan de
clases de OutputStream implementan la salida de bytes en dispositivos espe- un OutputStream (por ejem-
plo, archivos, pipes, servidores,
cíficos (por ejemplo, FileOutputStream y PipeOutputStream involucran etc.).
archivos y pipes, respectivamente).
CC-BY-SA • PID_00187406 13 Java RMI
De forma simétrica, InputStream es una clase abstracta que representa un Dispositivo de entrada
flujo de datos de entrada. Un input stream acepta bytes de entrada, que lee de
En este caso, un dispositivo
un dispositivo genérico de entrada. El método más importante de esta clase de entrada se considera apro-
es read(), que recupera un byte del dispositivo cada vez que es llamado. Las piado si puede trabajar como
fuente de datos y coincide con
subclases de InputStream implementan la entrada de bytes desde dispositi- los dispositivos de salida que
hemos visto.
vos específicos.
2)�Streams�intermedios. Estos nunca hablan directamente con los dispositi- Ejemplo de streams
vos. Su función es la de envolver un stream existente (puede ser un stream intermedios
primitivo o intermedio) para ganar en funcionalidad. Hablamos entonces de Un ejemplo de flujos interme-
streams�contenedores y streams�contenidos. Los propósitos más habituales dios son las clases Buffere-
dOutputStream y Buffere-
de los streams intermedios son para tareas como el almacenamiento en la me- dInputStream, muy emplea-
das para proporcionar capaci-
moria intermedia, pipeling, y compresión y serialización de datos. dad de almacenamiento tem-
poral (buffering).
Mecánica de la serialización
Para los casos especiales, se suelen manejar los atributos de la superclase en las
subclases mediante la redefinición de los métodos writeObject() y readOb-
ject() que hemos visto. Sin embargo, para simplificar y evitar la serialización
manual, en los casos más habituales es mejor serializar las superclases.
Usos de la serialización
Las clases que serializamos para enviar por la red, que después son cargadas Nota
tanto en el cliente como en el servidor, deben ser las mismas versiones.
Imaginemos los problemas
que tendríamos si cliente y ser-
vidor trabajaran con versiones
diferentes de las mismas clases
Java garantiza la unicidad de las clases mediante la generación en tiem- y en una se hubieran hecho
po de compilación de un identificador de versión llamado identifica- modificaciones.
Sin embargo, este identificador que genera Java por defecto es altamente sen-
sible al tipo de compilador que estamos utilizando y puede generar diferentes
identificadores aunque no se hayan hecho modificaciones en la clase. Para
evitar este problema, se recomienda generar un identificador explícitamente
dentro del código de la clase. Esto evitará que Java genere su identificador pro-
pio por defecto y nos aseguramos de que es el mismo identificador, con inde-
pendencia del compilador que estemos usando. El serialVersionUID se guarda
como un atributo más de la clase que queremos serializar, con estos modifica-
dores de acceso y nombre:
Aunque el identificador puede ser cualquier número, para evitar duplicados y serialver
problemas de formato, lo mejor es generarlo de forma automática utilizando la
El JDK incorpora una versión
herramienta llamada serialver, que viene con el JDK. Esta herramienta generará gráfica del serialver. Escribi-
un número único de versión para la clase que se basa en los propios elementos mos en consola serialver -
show, y nos pedirá la localiza-
que contiene la clase. ción de la clase que queremos
versionar. A continuación nos
dará un identificador único pa-
ra la clase.
Finalmente, es importante que mantengamos el modificador private para este
atributo. La razón para ello es que el identificador de versión debe ser exclusivo
de la clase que estamos serializando y no se propague a sus subclases.
En un contexto de una invocación remota (en la misma máquina o en má- Ved también
quinas diferentes), invocador e invocado se encuentran siempre en diferentes
En el subapartado "Paso de pa-
procesos. Si el proceso invocador enviara referencias suyas al proceso invoca- rámetros y valores de retorno"
do, tendría que producirse cada vez un cambio de proceso (atravesando la red trataremos con detalle el paso
por parámetros en el contexto
si los procesos estuvieran separados en máquinas diferentes) para efectuar una de una invocación remota.
Veamos en la figura 3 un ejemplo ficticio de la situación que hemos creado. Ejemplo de la calculadora
En nuestra calculadora remota, si pasamos los operandos enteros como refe-
Fijaos en que, en este ejem-
rencias a objetos de tipo Integer en el servidor, este, al querer recuperar el plo, lo más lógico sería trabajar
int que contiene, deberá invocar el método intValue() del objeto Integer con operandos primitivos de
tipo int, que son serializables
que se encuentra en el cliente. Por lo tanto, será necesario atravesar la red de por defecto. Hemos forzado el
ejemplo a operandos de tipo
nuevo y crear una nueva invocación, esta vez en el cliente. Vemos que en total Integer para mostrar la proble-
mática.
se harán seis invocaciones para efectuar la operación de suma.
Figura 3
CC-BY-SA • PID_00187406 17 Java RMI
Figura 4
Por otro lado, es necesario que las copias sean completas, con todas las copias
de los objetos y tipos primitivos afectados. Esto significa que, al pasar un ob-
jeto, los atributos de dicho objeto pueden referenciar internamente a otros
objetos o tipos primitivos, lo cual obliga recursivamente a incluir una copia
para cada uno de tales objetos y tipos primitivos internos.
Podemos ver el estado de un objeto como un grafo en forma de árbol en el que Tipo objeto
los nodos son los atributos de tipo objeto y las hojas son los atributos de tipo
Por tipo�objeto nos referimos
primitivo. Por lo tanto, una copia completa de un objeto significa copiar todo a instancias de la clase Ob-
el grafo del objeto con todos sus objetos internos, que estarán representados ject de Java o cualquiera de
sus subclases, mientras que los
en forma de subgrafo. De este modo, en el servidor habrá con facilidad muchos tipos�primitivos son los tipos
primitivos de Java (int, char,
long, etc.).
CC-BY-SA • PID_00187406 18 Java RMI
objetos duplicados, ya que cada llamada a un mismo método remoto que tenga
un argumento de tipo objeto significará enviar cada vez una nueva copia del
mismo objeto.
Con todo lo visto hasta ahora, podemos serializar objetos para la comunica-
ción en red con RMI.
Hay pocas excepciones a esta regla general, aunque en ciertos casos habrá datos Casos especiales
que no tenga sentido serializar. Para estos casos especiales, disponemos del
Un caso especial es FileIn-
modificador transient para evitar la serialización de aquellos atributos que no putStream; si lo serializáse-
está permitido o que no queremos serializar. mos, al deserializarla más ade-
lante en el servidor, podría re-
ferenciar algún archivo no vá-
lido o sin sentido en el nuevo
Todos los tipos primitivos de Java son serializables por defecto, ya que se pue- contexto. Otro caso es un ob-
jeto de tipo Thread, en cuyo
den convertir a bytes directamente (por ejemplo, el tipo int es una secuencia caso no tiene sentido serializar-
de cuatro bytes). Por este motivo, los tipos primitivos no necesitan ninguna lo al depender su estado de la
plataforma subyacente.
transformación para ser transmitidos por la red. En cuanto a los tipos objeto,
hay que hacerlos serializables explícitamente siguiendo las indicaciones que
exponemos a continuación: Serializable
• Los atributos del objeto a serializar tienen que ser o bien de tipo primiti-
vo, o bien de tipo objeto serializable (es decir, que este objeto, a su vez,
sea serializable). Si no es así, conseguiremos una excepción de tipo No-
tSerializableException. Aquellos atributos que no queramos incluir
en la serialización se tienen que modificar con transient. Fijaos en que los
CC-BY-SA • PID_00187406 19 Java RMI
atributos static tampoco se serializarán, ya que su estado tiene que ver con
la clase, y no con la instancia que queremos serializar.
• Para evitar el problema que hemos identificado de la duplicación de co- equals() y hashcode()
pias de objetos pasados por parámetro en sucesivas invocaciones del mis-
Los métodos equals() y
mo objeto, debemos asegurarnos de que trabajamos siempre con la copia hashcode() forman parte de
correcta. Puede ser necesario comprobar si dos objetos son el mismo me- la clase raíz Object.
Finalmente, hay que mencionar los casos especiales en que el rendimiento de API Reflection
la aplicación distribuida es crítico y el uso de la serialización que ofrece RMI
Consultad la API Reflection en
por defecto puede resultar lento. Esto se debe principalmente al uso que se el paquete java.lang.reflect de
hace de la API Reflection de Java para descubrir información sobre el objeto la API general de Java.
Para estos casos, Java proporciona la interfaz Externalizable, que debe ser Externalizable
implementada por las clases en las que queremos un control total en las tareas
Externalizable es otra in-
de serialización y deserialización. Estas tareas se programarán manualmente terfaz del paquete java.io que
ad hoc para los objetos que interesa serializar. Externalizable, de forma pa- hereda de Serializable.
Acabamos de ver que la interfaz Serializable es una manera de marcar Ved también
una clase para informar de que se trata de un objeto local que serializamos. El
En la asignatura Fundamentos
objetivo es informar al compilador y al entorno de ejecución de Java de que de programación habéis visto el
tendrá que pasar (paso de parámetros) por valor copias de los objetos de esta paso de parámetro por valor y
por referencia.
clase desde la JVM local a la JVM remota en un entorno RMI.
CC-BY-SA • PID_00187406 20 Java RMI
En una invocación remota, también cabe la posibilidad de pasar por parámetro Remote
tipos de objetos remotos. Los tipos de objetos remotos se identifican con la
Encontraréis más información
interfaz Remote. sobre Remote en el paquete
java.rmi de la API de Java.
(2)
Para entender mejor los tipos objetos remotos2, podemos imaginar un sistema Tened en cuenta que los tipos
objetos remotos son poco habitua-
distribuido complejo en el que hay varios entornos RMI funcionando al mis- les en una invocación remota.
mo tiempo. Durante las invocaciones es posible pasar como parámetro tipos
de objetos remotos pertenecientes a otros entornos RMI diferentes de aquel
en el que nos encontramos. No es posible serializar un objeto de este tipo y
pasar una copia suya a nuestro servidor remoto, ya que estaríamos enviando
la copia a una ubicación diferente de la original, lo cual no tiene sentido. La
única manera posible es pasar una referencia de este objeto, que siempre se
encontrará en su ubicación original y, al ser remoto, esto significa que tiene
capacidad de ser accedido remotamente. En consecuencia, los parámetros de
tipos objetos remotos los pasaremos por referencia.
1)�Primitivos (int, float, char, etc.), objetos que pasaremos siempre por valor,
puesto que, como hemos indicado, son serializables por defecto (un dato de
tipo int es ya una secuencia de cuatro bytes).
Local PV PR PR
Remota PV PV PR
Han surgido críticas al diferente tratamiento que se da al paso de parámetros al Transparencia de acceso
poner en duda el carácter de transparencia de acceso en RMI. El programador
La transparencia�de�acceso se
debe ser consciente en todo momento de si los parámetros pasados en una puede definir como la capaci-
invocación remota son de tipo objeto remoto o no remoto. En el primer caso, dad de un sistema de ofrecer
siempre las mismas interfaces
tendrá que ser serializado para poder enviar una copia del objeto no remoto al hacer las llamadas a sus ser-
vicios, ya que en caso de no
por la red. En el segundo caso, solo se enviará la referencia del objeto remoto. ofrecer esta transparencia sería
necesario adaptar la llamada
cada vez que cambie la inter-
Finalmente, cuando el objeto servidor devuelve un valor como resultado de faz.
1.3.3. Localización
Existen diferentes soluciones a este problema, pero pocas que sean simples y Transparencia de
efectivas. Tenemos la opción de que la aplicación cliente use directamente la localización
dirección física del servidor. No obstante, esto rompería el carácter de trans- La transparencia�de�localiza-
parencia de localización, puesto que implicaría actualizar y recompilar cons- ción (también conocida como
transparencia de ubicación) se
tantemente la aplicación a cada cambio de localización del servidor. Otra po- puede definir como la capaci-
dad de un sistema de ofrecer
sibilidad es que el usuario de la aplicación cliente conozca y proporcione la un servicio sin preocuparse de
dónde se ubica físicamente di-
dirección del servidor como entrada de datos, pero esto comporta un trabajo
cho servicio.
incómodo para el usuario.
Una solución mejor para localizar un objeto en un entorno distribuido es crear DNS
un servicio de nombres, como un directorio telefónico, en el que un nombre El conocido sistema de nom-
(dirección lógica) se corresponda con un servidor (dirección física). Este servi- bres de dominio (domain name
system, en inglés) es un ejem-
cio se encontraría en un servidor conocido que, en caso de cambio de locali- plo de servicio de nombres.
zación de los servidores, actualizaría constantemente la parte de la informa-
CC-BY-SA • PID_00187406 22 Java RMI
Este servicio es válido para aplicaciones poco complejas con pocos objetos Ved también
remotos, pero no resulta adecuado para aplicaciones distribuidas grandes y
Veremos el RMIRegistry con
dinámicas, por la carencia de escalabilidad y de independencia de la máquina más detalle en el subapartado
donde se encuentra. "RMIRegistry" de este módulo.
Así pues, son necesarios mecanismos adicionales que eviten las situaciones
descritas. Por un lado, es preciso que los objetos remotos consuman recursos
del servidor justo el tiempo necesario que puedan ser invocados. Por otro lado,
en el supuesto de que el objeto se destruya de forma involuntaria, éste debería
ser recuperado por completo y mantener el estado previo.
Un objeto remoto inactivo no se encuentra en la memoria del servidor y, por Objetos locales
lo tanto, no consume recursos. El estado que tenía el objeto cuando estaba
Esta diferencia es clara en
activo se guarda en soporte persistente, junto con datos de localización del cuanto a los objetos locales,
código de la clase dentro del servidor, entre otros datos. Todos estos datos se los cuales solo se encuentran
en memoria temporal.
actualizan constantemente mientras el objeto se encuentra activo, y se man-
tienen persistentes en el servidor. Podemos ver que los objetos remotos se en-
cuentran en la memoria temporal, pero al mismo tiempo también se mantiene
una copia persistente del objeto.
El cliente, por su parte, mantiene una referencia a la información persistente Objeto inactivo
sobre los objetos del servidor. Esto le permite activar el objeto remoto en caso
Se dice que si el objeto pasa a
de que este no esté instanciado (inactivo). Esta información permite al cliente inactivo o se ha destruido de
buscar, activar (es decir, instanciar de nuevo) e inicializar un objeto remoto forma involuntaria, no muere,
sino que pasa a stand by.
con los datos de su último estado conocido cuando éste se encuentra inactivo.
Así, se recupera el objeto remoto con el mismo estado que tenía y vuelve a
estar activo y listo para dar servicio a los clientes.
Cuando un objeto pasa cierto tiempo sin ser invocado por un cliente, éste
es desactivado. Por eso, hay que prever estrategias y políticas de activación
(umbral de inactividad, qué objetos son más críticos y tienen que estar activos
más tiempo que otros, etc.). Estas estrategias se deben decidir y diseñar a priori
y, en algunos casos, los usuarios tienen que ser conscientes de ellas. Por otro
lado, un objeto remoto inactivo se tiene que activar antes de ser invocado por
los clientes. Esto comporta más tareas y un sobrecoste en comparación con la
invocación de objetos local, que se traduce en una mayor latencia del proceso
de invocación.
CC-BY-SA • PID_00187406 24 Java RMI
(3)
Java, como otros lenguajes orientados a objetos, dispone de un mecanismo El recolector de basura se cono-
ce generalmente como garbage co-
automático para recoger estos objetos eliminados llamado recolector�de�basu-
llector.
ra3, que es transparente al programador. Otros lenguajes no disponen de este
sistema automático y delegan este proceso en el programador, que debe deter- Lenguaje C++
minar cuándo y cómo se lleva a cabo. Esto comporta muchos problemas, a
En lenguajes como C++, al
causa de la dificultad de gestionar manualmente los objetos y el impacto que programar un cliente, hay que
tener en cuenta si hay otros
tiene la no eliminación de objetos no necesarios en la eficiencia global.
clientes en el sistema con refe-
rencias al mismo objeto remo-
to.
En los sistemas de eliminación automáticos, todos los objetos son visitados
constantemente, y aquellos que no son referenciados por ningún otro objeto
se eliminan. Este mecanismo se aplica tanto en entornos locales como distri-
buidos. Veamos el funcionamiento en cada caso:
• En entornos�distribuidos, las referencias a los objetos son más comple- Software intermediario
jas. Una referencia debe aportar información, como por ejemplo la locali-
El software o capa intermedia-
zación donde se encuentra el objeto en el sistema, datos sobre el tipo de ria (middleware) se encuentra
objeto e información de seguridad. Toda esta información se almacena en por lo general entre el siste-
ma operativo y las aplicaciones
forma de referencia de tamaño considerable, tanto en el software interme- del sistema, y permite resolver
de forma transparente las ta-
diario del sistema distribuido como en la parte servidora. En este caso, es reas de bajo nivel de las apli-
caciones. También se aplica a
todavía más importante eliminar aquellos objetos del sistema distribuido los sistemas distribuidos como
no referenciados, ya que también resulta necesario eliminar la referencia, una abstracción de todos los
sistemas individuales que los
que ocupa mucho espacio de memoria. conforman.
todas las referencias dentro de cada JVM. Cada vez que se produce una nueva
referencia a un objeto remoto desde alguna JVM, el contador de referencias
para ese objeto se incrementa. Así, el contador refleja la suma de las referencias
de todas las JVM clientes que acceden al mismo objeto remoto.
(4)
Sin embargo, se dan algunos problemas4 con este mecanismo. En caso de que la Hay soluciones a estos proble-
mas, a pesar de que son complejas
red entre clientes y servidor esté separada, la capa de transporte puede no ver a y quedan fuera del alcance de este
los clientes y puede creer que han caído. Esto puede provocar que el recolector módulo.
1.3.6. Excepciones
Ante cualquier incidencia surgida, RMI lanza una excepción de tipo Re-
moteException para avisar al cliente de que algo va mal en el entorno
RMI.
(checked exception). Una consecuencia negativa de esta decisión es que obliga Exception
al programador a generar código en el lado del cliente para tratar excepciones
Todas las excepciones que he-
imprevisibles, lo cual da como resultado código sin demasiada utilidad. redan de Exception se deno-
minan checked exception y se
tienen que tratar obligatoria-
Además, la obligación es doble, ya que solo se permite lanzar objetos exclusi- mente. Java también permite
el uso de unchecked exceptions
vamente de tipo RemoteException (no se permiten las subclases). Esto sig- (heredan de RuntimeExcep-
tion) para evitar tal obliga-
nifica que el tratamiento de otras excepciones que no sean de comunicación ción.
(es decir, las propias de la aplicación) se debe llevar a cabo dentro de la Re-
moteException misma, como una excepción anidada, aprovechando que el
constructor RemoteException(String s, Throwable ex) permite anidar Ved también
1.3.7. Seguridad
administradores de seguridad. Sin embargo, este nivel de seguridad es insufi- No es el propósito de la asig-
ciente para aplicaciones distribuidas con RMI. natura explicar el modelo de
seguridad de Java. Aquellos
que estéis interesados, podéis
consultar la obra: J.�Jawors-
Los cargadores�de�clase solo permiten cargar clases que se encuentren en la
ki;�P.�J.�Perrone (2001). Se-
máquina local. Para cargar clases desde ubicaciones remotas es imprescindible guridad en Java. Madrid: Pear-
son Alhambra.
un administrador de seguridad específico que permita cargar las clases remo-
tamente.
Por otro lado, hemos visto que RMI se basa en la comunicación por sockets, Ved también
mediante los cuales se envían objetos serializados por la red, en principio sin
Revisad el protocolo secure soc-
ningún tipo de cifrado. Aun así, los datos críticos de los objetos serializados y, ket layer (SSL) y la seguridad
en general, toda comunicación que circule por la red, deberían viajar siempre por sockets en la asignatura Ad-
ministración de redes y sistemas
cifrados. Para ello, Java ofrece SSLSocket, una subclase de Socket que per- operativos.
Para este propósito, Java proporciona una serie de tipos de permisos. En una
aplicación típica RMI, el permiso más importante es el de la clase Socket-
Permission, que gestiona permisos de un socket en la red. Un SocketPer-
mission se construye con un nombre de servidor y una serie de acciones per-
mitidas para dicho servidor. Esto se consigue creando un archivo de pólizas
que especifica en forma de permisos cuáles son las políticas de seguridad que
se aplicarán al socket.
2. Arquitectura RMI
Como hemos visto, el modelo de objetos distribuido que propone Java permite Objeto remoto y objeto
que los objetos que se ejecutan en una JVM invoquen los métodos de objetos servidor
que se ejecutan en otras JVM. El objeto que hace la invocación se denomina Utilizamos los términos obje-
objeto�cliente, mientras que el objeto remoto que recibe la invocación de sus to remoto y objeto servidor co-
mo sinónimos, a pesar de que
métodos se denomina objeto�servidor. El objeto remoto tiene que estar regis- generalmente se entiende que
los objetos servidores adminis-
trado en un servidor de nombres. tran los objetos remotos.
Figura 5
durante el registro del objeto, así como durante la consulta del stub para en-
contrar el objeto. El protocolo de red TCP/IP representa el nivel de transporte
de la red entre la JVM local y la remota.
En los apartados siguientes, describiremos en detalle cada uno de estos com- Ved también
ponentes que conforman la arquitectura RMI.
La arquitectura mostrada en la
figura 5 nos servirá de guía pa-
2.1. El servidor ra el resto del módulo.
Como se ha dicho anteriormente, los clientes no referencian directamente los Más información
objetos remotos, sino una interfaz remota que es implementada por el objeto
Encontraréis más información
servidor. En el caso más simple, en el servidor debe haber una clase interface de las clases que se mencionan
que extienda la interfaz Remote con la firma de todos los métodos remotos (es en este apartado en el paquete
java.rmi de la API de Java.
decir, aquellos que lancen RemoteException en su cláusula throws). Estos
métodos son los que el cliente puede invocar.
En el caso más simple, se graba el objeto servidor mismo, que ya contiene el objeto re-
moto.
2.2. El cliente
El cliente es el que invoca los métodos del objeto servidor. En este caso, para
invocar un método remoto, el cliente debe llevar a cabo las tareas siguientes:
do devuelve un objeto de tipo Object, al cual es necesario hacer un cast En la asignatura Diseño y pro-
para convertirlo en el tipo del objeto remoto. gramación orientada al objeto
habéis visto el mecanismo de
cast de Java.
CC-BY-SA • PID_00187406 32 Java RMI
• Las invocaciones� a� los� métodos� remotos se tienen que llevar Ved también
a cabo desde dentro de un bloque try/catch para capturar co-
En el subapartado "Excepcio-
mo mínimo la RemoteException lanzada desde el servidor. En ca- nes" de este módulo hemos
so de problemas en el proceso de localización del objeto remo- hablado de las excepciones de
Java en las invocaciones remo-
to, varias excepciones del tipo java.net.MalformedURLException y tas.
Es decir, un stub tiene implementados los mismos métodos que el objeto re-
moto, pero el código de esta implementación consiste solo en una referencia
completa con todo lo necesario para comunicarse con los métodos del objeto
remoto (el código real de implementación de los métodos se encuentra en el
objeto remoto).
Tanto el stub como el skeleton representan clases que se generan automática- rmic
mente durante la compilación (por lo general, con el compilador de RMI rmic)
rmic es una aplicación que vie-
a partir de las clases que implementan la interfaz remota. Durante la invoca- ne de serie con el JDK, y que
ción, se trabaja con instancias de stubs y skeletons, con lo que se mantiene el permite generar stubs y skele-
tons. Una vez generados, es
modelo de objetos que propone Java. Aun así, en las últimas versiones de Java preciso colocar el stub en el
lado del cliente y el skeleton
los stubs y skeletons ya no son necesarios, como se explica en el subapartado en el lado del servidor (o en el
mismo directorio si hace fun-
siguiente. ciones de cliente y servidor en
aplicaciones simples).
API Reflection
A pesar de lo que hemos explicado hasta ahora, la generación de los skeletons
solo fue necesaria antes de llegar a la versión 1.2 de Java. A partir de esta ver- Consultad la API Reflection en
el paquete java.lang.reflect de
sión se suprimió la necesidad de generar los skeletons sustituyéndolos por la la API general de Java.
capacidad de reflexión que ofrece la API Reflection de Java. Este mecanismo per-
mite conocer e invocar los métodos del objeto remoto en tiempo de ejecución.
Aun así, los skeletons son necesarios para la compatibilidad hacia atrás con
aplicaciones escritas en versiones antiguas de Java.
Aun así, tanto los stubs como los skeletons resultan necesarios para mantener
la compatibilidad hacia atrás, además de que utilizarlos es conceptualmente
más didáctico. Por estas razones, nosotros los utilizaremos en este módulo, así
como rmic para generarlos.
Los stubs y skeletons son la clave del modelo que ofrece RMI, puesto que aho-
rran al programador todo el trabajo que hacen ellos de manera automática
(crear y mantener los sockets, serializar y deserializar la información transmiti-
da, controlar la conexión, etc.). Al cumplir todas estas funciones transparentes
al programador, se consiguen muchos de los objetivos que hemos menciona-
do de RMI.
(5)
Conozcamos5 este proceso automático que sigue una invocación típica clien- Para seguir mejor este proceso,
observad la figura 5.
te-servidor, a bajo nivel. Una vez que el servicio RMIRegistry está activo, los
objetos remotos están correctamente registrados y el cliente ha encontrado el
objeto remoto, el proceso básico para invocar un método de este objeto es el
siguiente:
CC-BY-SA • PID_00187406 34 Java RMI
1) El cliente obtiene una instancia del stub, que, como hemos dicho, representa
todos los métodos remotos.
2) El cliente llama a un método del stub en una invocación del mismo tipo
que se haría en una invocación local. Es decir, el stub es una representación
del objeto remoto para el cliente, que se encuentra en su misma JVM, lo cual
permite acceder a estos métodos de manera local.
3) El stub en el lado del cliente crea internamente un socket con el skeleton Ved también
en el lado del servidor, lo cual permite tenerlos a los dos conectados. Como
Repasad lo que hemos estudia-
sabemos, el socket enviará los datos a través de la red en forma de stream. do sobre streams y la serializa-
ción en el subapartado "Seriali-
zación" de este módulo.
4) El stub serializa toda la información asociada con la invocación (nombre
del método invocado, tipos de parámetros, etc.) y envía esta información por
medio del socket al skeleton.
2.4. RMIRegistry
Dado que tanto RMIRegistry como los objetos remotos tienen que encontrarse URL
en la misma máquina, el stub conoce la máquina donde se encuentra el objeto
La URL que usarán los clientes
(que es la misma máquina donde se encuentra el servidor). De este modo, si para llamar a RMIRegistry ten-
trasladamos la parte servidora a otra máquina, el stub solo tendrá que llamar a drá normalmente este forma-
to:
la URL de RMIRegistry de aquella misma máquina servidora, cuya dirección ya rmi://
direccion_servidor:
conoce. Además, puesto que el objeto remoto ha quedado ligado al stub du- puerto/identificador
rante el registro, aunque el objeto cambie de localización dentro de la máqui-
na servidora, será transparente para el stub, que lo encontrará de todas formas.
Para este caso de estudio, implementaremos una calculadora primitiva con una
funcionalidad elemental que nos permitirá sumar, restar, multiplicar y dividir
dos operandos enteros. Su única particularidad será que la podremos usar de
manera remota en un entorno cliente-servidor. El ordenador que contendrá el
objeto que hará los cálculos (la lógica de las operaciones) cumplirá funciones
de servidor, mientras que el ordenador con el objeto que representa el teclado
y el visor de la calculadora (escribe los operandos y operador y muestra el
resultado) tendrá las funciones de cliente.
Para llevar a cabo un cálculo, se crearán dos procesos: proceso servidor y pro-
ceso cliente. El proceso cliente enviará los operandos y el operador al proceso
servidor, que efectuará el cálculo y devolverá el resultado al proceso cliente,
que a su vez lo mostrará al usuario. Así pues, el cliente no dispondrá de la
lógica de cálculo de la calculadora, pero podrá igualmente efectuar cálculos.
CC-BY-SA • PID_00187406 37 Java RMI
Los dos procesos serán independientes el uno del otro y estarán en máquinas Procesos cliente y servidor
diferentes en las que haya instalada una JVM y estén conectadas mediante una
Los dos procesos también pue-
red TCP/IP (por lo general, Internet). Solo es necesario que el cliente conozca den correr como dos procesos
la dirección IP (o nombre DNS) y el puerto del ordenador servidor. independientes dentro de la
misma máquina. En este caso,
la dirección IP (local) y el puer-
to tanto del servidor como del
En los subapartados siguientes explicaremos el desarrollo completo del servi- cliente son los mismos.
dor y el cliente en una aplicación típica RMI. Procederemos primero a desa-
rrollar el servidor y después el cliente, para disponer del objeto remoto antes
Nota
de que pueda ser invocado.
Durante el desarrollo del ejem-
plo no repetiremos las especifi-
Mostraremos todo el código fuente de nuestra calculadora, que hará las fun- caciones de la arquitectura bá-
sica RMI vista en el apartado
ciones de guía durante todo el apartado.
anterior, aunque insistiremos
en aquellos puntos clave que
ayuden a la comprensión del
3.1.1. Desarrollar el objeto remoto código.
1) La interfaz� remota, que definirá los métodos que pueden ser invocados
por el cliente.
Vamos a describir cada una de las tres partes junto con el código generado de
nuestra calculadora.
Tendremos tantas interfaces como objetos remotos haya en nuestra aplicación. Calculadora
Cada interfaz se tratará en un archivo de código aparte. En nuestro ejemplo
Veamos cómo se declaran las
solo hay un objeto remoto y, por lo tanto, una única interfaz. Esta interfaz está firmas de los cuatro métodos
contenida en el archivo Calculadora.java, con el código siguiente: remotos que se corresponden
con las cuatro operaciones que
efectúa la calculadora remota.
importe java.rmi.*;
public interface Calculadora extends Remote {
public int sumar(int a, int b) throws RemoteException;
public int restar(int a, int b) throws RemoteException;
public int multiplicar(int a, int b) throws RemoteException;
public float dividir(int a, int b) throws RemoteException;
}
import java.rmi.*;
public class CalculadoraImpl extends
java.rmi.server.UnicastRemoteObject implements Calculadora {
//Contructor
public CalculadoraImpl() throws RemoteException {
super();
}
public int sumar(int a, int b) throws RemoteException {
return a + b;
}
public int restar(int a, int b) throws RemoteException {
return a - b;
}
public int multiplicar(int a, int b) throws RemoteException {
return a * b;
}
public float dividir(int a, int b) throws RemoteException {
ArithmeticException
La clase servidora
grant {
permission java.net.SocketPermission
"*:1024-65535","connect,accept,listen,resolve";
};
Una vez creado este archivo con la información pertinente, se tiene que en-
contrar en el mismo directorio que la clase servidora, para que el entorno
RMI lo encuentre en el momento de ejecutar la clase CalculadoraServi-
dor. Esto se consigue configurando una propiedad del sistema a partir del par
nombre_propiedad=valor donde nombre_propiedad = java.security.policy
y valor= java.policy.
Una vez acabada la parte del servidor, desarrollaremos el cliente, que solo lle-
vará a cabo tres tareas esenciales:
1)� Localizar� el� objeto� remoto. El cliente buscará el objeto remoto a partir Puerto predeterminado
del método Naming.lookup() y el identificador del objeto. La situación es
Aunque el puerto predetermi-
simétrica a la que acabamos de ver al desarrollar la clase servidora. Primero nado por el que RMIRegistry
se tiene que contactar con el servicio de nombres (en nuestro caso es RMIRe- escucha es el 1099, se puede
cambiar por otro siempre que
gistry) en la misma dirección en la que hemos registrado el objeto durante el lo modifiquemos tanto en el
cliente como en el servidor.
desarrollo del servidor. Pasando el identificador por parámetro de lookup(),
el servicio devolverá el objeto remoto de tipo Object que tendremos que mo-
delar (cast) al tipo que nos interesa (Calculadora). Dado que la ejecución en
principio se da en un contexto local, la dirección de RMIRegistry será rmi://
localhost:1099, igual que en el servidor.
CC-BY-SA • PID_00187406 41 Java RMI
El código cliente que se muestra a continuación espera tres datos por línea de
orden de la consola, por este orden:
A continuación se muestra el código del cliente, formado por dos clases. Calculadora
Primero, mostramos el código del archivo ExcepcionDivisionPorCero.java, que
Fijaos en el hecho de que,
es la clase que permite recoger y tratar en el lado del cliente la excep- en el código, el mensaje
ción anidada que está dentro de la excepción lanzada de forma remota. A anidado se encuentra al fi-
nal del mensaje remoto lan-
continuación mostramos el código del cliente propiamente dicho (archivo zado por RemoteExcep-
tion. La parte precedente
CalculadoraCliente.java), que permite trabajar con la calculadora: del mensaje no nos interesa.
Sin embargo, podéis impri-
mirlo completo sustituyendo
//Creamos la clase de excepción anidada para recogerla en el cliente substring(indiceAnidado
public class ExcepcionDivisionPorCero extends Exception { +1) por substring(0) y ver
int dividendo = 0; el mensaje remoto entero.
public ExcepcionDivisionPorCero (int pDividend) {
dividendo = pDividendo;
}
public String getMessage() {
return "%" + dividendo + "/0";//% es un marcador
}
}
//Implementación de la clase cliente
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.net.MalformedURLException;
CC-BY-SA • PID_00187406 42 Java RMI
import java.rmi.NotBoundException;
public class CalculadoraCliente {
public static void main(String[] args) {
int op1, op2;
String operador;
if (args.length != 3) {
System.out.println(
"Error. Uso: CalculadoraCliente operando1 operador operando2");
return;
}
try {
op1 = Integer.parseInt(args[0]);
op2 = Integer.parseInt(args[2]);
operador = args[1];
Calculadora c = (Calculadora)Naming.lookup(
"rmi://localhost:1099/ServicioCalculadora");
if (operador.equals("+"))
System.out.println(c.sumar(op1, op2));
else if (operador.equals("-"))
System.out.println(c.restar(op1, op2));
else if (operador.equals("x"))
System.out.println(c.multiplicar(op1, op2));
else if (operador.equals("/"))
System.out.println(c.dividir(op1, op2));
else
System.out.println("Error. Uso: CalculadoraCliente operando1
operador operando2");
}
catch (MalformedURLException murle) {
System.out.println("MalformedURLException" + murle);
}
catch (RemoteException re) {
//comprobamos si lleva la marca de excepción anidada
int indiceAnidado = re.getMessage().indexOf('%');
if(indiceAnidado!=-1)//lleva anidamiento: ExcepcionDivisionPorCero
System.out.println("ExcepcionDivisionPorCero: "
+ re.getMessage().substring(indiceAnidado+1));
//imprimimos solo el mensaje anidado
else //no lleva anidamiento: RemoteException
System.out.println("RemoteException" + re);
}
catch (NotBoundException nbe) {
System.out.println("NotBoundException" + nbe);
}
catch (ArithmeticException ae) {
System.out.println("java.lang.ArithmeticException"+ ae);
}
catch (Exception e) {
System.out.println("java.lang.Exception" + e);
}
}
}
Una vez completado el código fuente de la aplicación, vamos a compilarla y Ved también
ejecutarla siguiendo una serie de pasos muy simples y ordenados. Ante todo,
En el subapartado "Ejecución
debemos asegurarnos de que todos los archivos de código fuente se encuen- en un entorno distribuido" ve-
tran en el mismo directorio dentro de nuestro sistema. Dada la simplicidad remos cómo podéis organi-
zar los archivos en un entorno
de la aplicación, no es necesario disponerla en diferentes directorios. Estos de ejecución distribuido y más
complejo.
archivos son: Calculadora.java, CalculadoraImpl.java, CalculadoraServidor.java,
CalculadoraCliente.java y el archivo de pólizas java.policy.
En la consola, nos situamos en el directorio de trabajo que hemos creado y Para el trabajo en consola, uti-
lizaremos una notación gené-
ejecutamos por línea de órdenes el compilador de Java javac para compilar rica, que podréis adaptar fácil-
mente a vuestro sistema ope-
todos los archivos de código fuente que hemos creado hasta el momento, co-
rativo.
rrespondientes a la interfaz remota, el servidor y el cliente.
Una vez tenemos todo el código compilado, disponemos de los archivos .class
de código binario en el mismo directorio de trabajo. Entre estos archivos se
encuentra CalculadoraImpl.class, con la implementación de los objetos remo-
tos, a partir de la cual se generan los stubs y skeletons mediante el compilador
de stubs que proporciona el entorno Java rmic.
Como hemos visto con anterioridad, a partir de la versión Java 1.5 ya no es ne- Stubs
cesario utilizar explícitamente rmic para generar los stubs. En lo concerniente
De hecho, los stubs se generan
a los skeletons, tampoco son necesarios, al ser sustituidos por la capacidad refle- siempre durante la ejecución,
xion de Java. Sin embargo, en las últimas versiones de Java se ofrece la opción aunque sea de manera implí-
cita y aunque no generen nin-
de generar tanto los stubs como los skeletons para mantener la compatibilidad gún archivo en nuestro direc-
torio de trabajo.
con versiones anteriores. En esta sección vamos a generar tanto unos como
otros solo para entender mejor su funcionamiento.
CC-BY-SA • PID_00187406 44 Java RMI
Ejecutamos la orden rmic -keep -v1.1 CalculadoraImpl para forzar Ved también
la generación tanto de los stubs como de los skeletons. La opción -v1.1 sir-
Por su valor didáctico, se reco-
ve para forzar la generación del skeleton y hacer compatible la aplicación mienda el estudio del código
con todas las versiones desde Java 1.1. La opción -keep sirve para conservar de los stubs y skeletons junto
con su funcionamiento, expli-
los archivos temporales (stubs y skeletons) en código fuente. Podemos com- cado en el apartado "Arquitec-
tura RMI" del presente módu-
probar en nuestro directorio de trabajo que se han generado los archivos lo.
CalculadoraImplStub.java y CalculadoraImplSkel.java. Como sabemos, estas cla-
ses son necesarias para efectuar la comunicación entre cliente y servidor.
En este momento, ya podemos iniciar la ejecución del servidor. Primero es ne- Precaución
cesario arrancar el servicio de registro remoto, que equivale a arrancar RMIRe-
Por precaución, antes de
gistry, para que el servidor pueda registrar el objeto remoto y el cliente pueda arrancar el servicio, inicializa-
acceder a él. remos la variable de entorno
CLASSPATH para evitar pro-
blemas durante el registro de
objetos remotos en caso de
Arrancamos RMIRegistry ejecutando este servicio por línea de órdenes de la que apunte al directorio de
trabajo.
consola mediante la instrucción rmiregistry. Este servicio quedará activo
(ventana de consola abierta) y escuchando por medio del puerto 1099.
Servidor virtual
Al actuar como un servidor virtual, podríamos arrancar RMIRegistry desde cualquier di-
rectorio del sistema.
Para llevar a cabo la ejecución, abrimos una segunda consola y nos situamos
de nuevo en nuestro directorio de trabajo. Desde ahí, ejecutamos el servidor
mediante la orden:
nombres, una vez que se arranca el servidor, queda activo (ventana de consola
abierta) y pendiente para recibir invocaciones de métodos remotos por parte
del cliente.
Para llevar a cabo la ejecución, abrimos una tercera ventana de consola. Nos Precaución
situamos otra vez en nuestro directorio de trabajo y, desde ahí, ejecutamos el
No debemos cerrar ninguna
cliente con esta orden: java CalculadoraCliente 1 + 2. de las dos ventanas de con-
sola que tenemos abiertas, ya
que esto ocasionaría cancelar
El resultado de la ejecución se mostrará en la misma consola que hemos ejecu- el servicio de registro o apagar
el servidor.
tado, bien con el resultado de la operación aritmética, bien con la información
sobre un problema detectado durante la invocación.
(6)
Llegados a este punto, hemos conseguido llevar a cabo todas las tareas nece- Del inglés computer aided softwa-
re engineering.
sarias para compilar y ejecutar una aplicación distribuida en un entorno RMI
local. Esta aplicación nos ha sido útil para mostrar paso a paso todo el proceso
Herramientas CASE
básico que sigue cualquier aplicación RMI desarrollada y ejecutada a mano,
sin la ayuda de herramientas CASE6 de apoyo al desarrollador. Las herramientas�CASE son
herramientas que ayudan a lle-
var a cabo las diferentes activi-
dades de la ingeniería del soft-
A pesar de ser una aplicación trivial, hemos visto que el proceso manual es ware. Las hay muy populares,
relativamente laborioso e implica un mínimo de cinco pasos, en los cuales como Eclipse y NetBeans, que
dan soporte a las actividades
es necesario abrir varias consolas para arrancar y ejecutar la parte servidora y de implementación, desplie-
gue, ejecución y prueba.
cliente. Además, en caso de que quisiéramos lanzar varios clientes al mismo
tiempo, sería necesario abrir una consola nueva por cada nuevo cliente. Todo
ello vuelve muy pesado este proceso manual, especialmente cuando tenemos
que repetirlo varias veces durante un proceso de prueba.
Una manera habitual de automatizar las tareas es crear un archivo por lotes Archivos por lotes
que ejecute el intérprete de órdenes del sistema operativo, con todas las tareas
En Windows, los archivos por
que hemos visto, y además ejecute tantos clientes como sea necesario. De este lotes se denominan batch (ar-
modo, ejecutar este archivo una única vez será equivalente a hacer todos los chivos bat). En Unix se deno-
minan shell scripts.
pasos explicados.
A continuación, crearemos un archivo por lotes que automatice todo el pro- Nota
ceso de compilación y ejecución de la calculadora remota creando dos clientes
Nos basamos en Windows pa-
consecutivos. En función del sistema operativo, pondremos extensión al ar- ra crear el archivo por lotes.
chivo y lo guardaremos en nuestro directorio de trabajo, junto con los archi- Una vez creado, lo ejecutamos
escribiendo el nombre por lí-
vos de código fuente de la aplicación, desde donde ejecutaremos el archivo. nea de órdenes. Para Unix, el
contenido del script y la ejecu-
ción son muy parecidos.
@echo off
rem Archivo calculadora.bat
rem Caso Estudio RMI: Calculadora remota
echo Se está compilando todo el código...
javac *.java
echo Se crean los stubs y skeletons...
rmic -keep CalculadoraImpl
echo Se está inicializando CLASSPATH...
SET CLASSPATH=
echo Se está arrancando rmiregistry...
start rmiregistry
echo Se está arrancando el servidor CalculadoraServidor...
start java -Djava.security.policy=java.policy CalculadorServidor
echo Pausa para dar tiempo a que arranque el servidor
pause
echo Ejecuta un cliente que pide sumar 1 + 2
java CalculadoraCliente 1 + 2
echo Ejecuta otro cliente que pide dividir 2 / 0
java CalculadoraCliente 2 / 0
echo Fin programa
pause
CC-BY-SA • PID_00187406 47 Java RMI
Hasta ahora, hemos ejecutado el caso de estudio de la calculadora remota en- Entorno distribuido
tre dos JVM en un mismo ordenador (ejecución local). En este subapartado,
Para llevar a cabo la simulación
veremos cómo podemos ejecutar la misma aplicación en un entorno distri- de un entorno distribuido, po-
buido, como mínimo entre dos ordenadores diferentes, uno como cliente y demos considerar, por ejem-
plo, trabajar con el ordenador
otro como servidor. Si tenemos acceso a más de dos ordenadores, podemos de casa y el de la oficina, el or-
denador de un amigo, etc.
acercarnos más a la realidad de las aplicaciones distribuidas conectando varios
clientes con un servidor.
En el caso más simple, para ejecutar la aplicación en dos ordenadores diferen- Dirección IP pública
tes, primero tenemos que decidir qué ordenador representará el rol de cliente
Podéis conseguir la IP públi-
y cuál el de servidor. A continuación, necesitamos la dirección IP pública del ca accediendo desde el or-
ordenador servidor para poder acceder a él. denador servidor a una pági-
na web externa como http://
www.whatismyip.com.
java.rmi.Naming.rebind("rmi://localhost:1099/ServicioCalculadora", c);
por:
por:
La decisión del directorio del sistema donde tienen que ir instalados tanto en
la máquina servidora como en la máquina cliente es totalmente irrelevante. Al
ejecutar RMIRegistry, se procederá a registrar los objetos, y se creará un vínculo,
de forma que el cliente encontrará siempre los objetos remotos en el servidor,
con independencia del directorio donde se encuentren.
Resumen
Hemos visto que la programación distribuida con RMI hace posible el desarro-
llo de aplicaciones distribuidas en Java de manera simple y con un alto grado
de transparencia.
Por otra parte, hemos estudiado con detalle la arquitectura básica de RMI, lo
que nos ha permitido comprobar una vez más la simplicidad y transparencia
en el funcionamiento de invocación remota de Java, muy cercana a la invo-
cación local.
Actividades
1. Poned dos ejemplos de beneficios y dos ejemplos de limitaciones al trabajar con Java RMI, a
partir de las características mencionadas en el apartado "Características RMI" de este módulo.
2. Dados los siguientes sistemas de software por desarrollar, indicad en cada caso si es ade-
cuado el uso de Java RMI y, en caso afirmativo, la forma en que podemos aprovechar su
potencial.
c) Un sistema de gestión del correo electrónico vía web, como por ejemplo Hotmail.
a) Añadir la operación de raíz cuadrada (hay que controlar la excepción en caso de propor-
cionar números negativos para el cálculo de la raíz cuadrada).
d) Desplegad la nueva calculadora remota en un entorno real. Escribid un archivo .bat para
la automatización de las tareas de despliegue de la calculadora remota.
4. Una compañía de suministro de gas quiere una aplicación distribuida para controlar al-
gunos parámetros de sus tanques de gas, como la temperatura y la presión. Actualmente, la
compañía controla estos parámetros in situ y modifica (incrementa/decrementa) los valores
de los parámetros según sea necesario.
Con la aplicación distribuida, la compañía quiere hacer un control remoto desde sus oficinas,
para evitar desplazamientos, y también, por el peligro que representa tener que controlar los
parámetros en los tanques. Para ello, vamos a suponer que el tanque de gas dispone de dos
sensores, uno que permite leer y modificar la presión y otro que permite leer y modificar la
temperatura. Los valores de los parámetros tienen que estar siempre en los intervalos [Pmin,
Pmax] y [Tmin, Tmax], respectivamente, donde Pmin, Pmax y Tmin, Tmax son valores críticos. En
caso de que el valor de un parámetro se saliera del intervalo, la alarma se dispararía, lo cual
avisaría a un empleado de intervenir por medio de la aplicación para modificar el valor de
un valor permitido. Utilizad las excepciones remotas anidadas para controlar los valores no
permitidos de los parámetros de presión y temperatura.
6. Implementad un diccionario remoto para consultar los términos informáticos más comu-
nes. Por ejemplo, si un cliente introduce "CPU", se le devolvería la consulta como "central
processing unit". Usad las excepciones remotas para controlar entradas inexistentes en el dic-
cionario.
7. Implementad un sistema de votación remoto que permita a los clientes de una compañía
responder una pregunta con la respuesta "Sí"/"No". En el lado del servidor deberá ser posible
conocer el número total de votantes, el número de votos "Sí" y el número de votos "No".
Ejercicios de autoevaluación
1. Definid RMI e indicad cuáles son sus objetivos principales.
CC-BY-SA • PID_00187406 52 Java RMI
4. ¿Qué tipos de parámetros son posibles en RMI y cómo se hace el paso de parámetros en
cada uno durante una invocación remota?
7. ¿Qué es una interfaz remota y cómo se consigue en RMI? ¿Qué clase de Java extiende una
clase servidora en RMI?
8. ¿Qué es rmic, para qué sirve y en qué etapa del desarrollo de la aplicación distribuida RMI
se aplica?
9. ¿Cómo se registra un objeto remoto en RMI? ¿Cómo se localiza un objeto remoto en RMI?
10. ¿Qué soluciones proporciona Java dentro de su modelo de seguridad para aplicaciones
distribuidas con RMI?
CC-BY-SA • PID_00187406 53 Java RMI
Solucionario
Ejercicios de autoevaluación
1. RMI representa el modelo de objetos distribuidos que propone Java como solución para
desarrollar aplicaciones distribuidas de manera simple, intuitiva, versátil y transparente.
2. En RMI, los datos que pasamos por parámetro durante la invocación de un método remoto
son serializados y convertidos en un stream de bytes por el proceso que invoca. Este proceso
escribe en un socket que retransmite el stream por la red, hasta llegar al socket de destino.
4. Durante una invocación a un método de un objeto remoto, los parámetros que contiene
la invocación pueden ser de tres tipos: tipos primitivos (int, char, float, etc.), que se pasan
por valor; objetos serializables (o no remotos), que se pasan por valor, y objetos remotos, que
se pasan por referencia.
5. Para llevar a cabo una invocación de un método remoto, la arquitectura básica en el en-
torno RMI está formada por un objeto cliente, los stubs en el lado del cliente, el objeto ser-
vidor y los skeletons en el lado del servidor, los sockets para la comunicación entre stubs y
skeletons, RMIRegistry para la localización del objeto remoto y el protocolo de red TCP/IP en
el nivel de transporte de la red entre la JVM local y la remota.
6. Un stub se encuentra en el lado del cliente y es una representación local de un objeto re-
moto del servidor, que referencia todos los métodos remotos de este objeto. De forma equi-
valente, un skeleton representa un skeleton del objeto remoto y se encuentra en el lado del
servidor.
La función principal de un stub es crear y mantener una conexión de sockets abierta con el
skeleton del servidor y responsabilizarse de la tarea de serializar y deserializar los datos en
el lado del cliente. De forma parecida, el skeleton se responsabiliza del mantenimiento de la
conexión de sockets con el stub, así como de serializar y deserializar los datos en el lado del
servidor.
7. Los clientes de una aplicación distribuida RMI no referencian directamente los objetos
remotos, sino una interfaz remota que es implementada por el objeto servidor. En el caso
más simple, en el servidor hay una clase interfaz remota que extiende la interfaz Remote con
la firma de todos los métodos remotos que pueden ser invocados por el cliente (es decir,
aquellos que lancen RemoteException en su cláusula throws).
8. rmic es un compilador que permite generar los stubs y skeletons. Una vez tenemos todo
el código fuente (interfaz remota, objeto cliente y servidor) compilado y disponemos de los
archivos .class de código binario, a partir de dichos archivos se generan los stubs y skeletons
mediante el compilador de stubs que proporciona el entorno Java rmic.
Aun así, a partir de la versión Java 1.5, ya no es necesario utilizar rmic explícitamente para
generar los stubs. Por lo que respecta a los skeletons, tampoco son necesarios, al ser sustituidos
por la capacidad reflexion de Java. Sin embargo, es recomendable generar tanto los stubs como
los skeletons para mantener la compatibilidad con versiones anteriores.
RMI proporciona de serie un servicio de nombres muy simple llamado RMIRegistry, que per-
mite al servidor grabar los objetos remotos para que los clientes puedan encontrarlos.
CC-BY-SA • PID_00187406 54 Java RMI
10. El modelo de seguridad de Java para aplicaciones distribuidas con RMI es de tres tipos:
administradores de seguridad RMI, cifrado a escala de sockets y permisos personalizados.
CC-BY-SA • PID_00187406 55 Java RMI
Glosario
activación de objetos remotos f Proceso que instancia objetos remotos que han sido
previamente desactivados o destruidos. La desactivación de objetos remotos hace el proceso
inverso.
interfaz remota f Interfaz de los métodos del objeto remoto que establece una separación
entre las firmas de los métodos y el código de dichos métodos.
Java virtual machine f Entorno donde se ejecuta el código compilado (bytecode) de Java.
Hay versiones de JVM para la mayoría de los sistemas operativos, lo cual proporciona calidad
de independencia a la plataforma de Java. A partir de crear varias instancias de una misma
JVM, se puede simular una invocación remota en una misma máquina.
Sigla JVM
objeto cliente m Objeto que invoca objetos remotos y puede recibir los resultados de las
invocaciones.
objeto remoto m Objeto servidor administrado por un servidor que puede ser invocado
por un objeto cliente.
objeto servidor m Objeto que gestiona los objetos remotos que los objetos clientes buscan
para invocar sus métodos y toda la infraestructura necesaria para recibir las invocaciones y
devolver los resultados a los objetos clientes.
parámetro por referencia m Referencias directas de las instancias de los objetos pasados
por parámetro durante una invocación.
parámetro por valor m Copias de las instancias originales de los objetos pasados por
parámetro durante una invocación. Los tipos primitivos de datos también se pasan por valor
durante una invocación.
stream intermedio m Stream que envuelve otros streams existentes (streams primitivos o
intermedios) para ganar en funcionalidad.
stream primitivo m Stream que habla con los dispositivos directamente leyendo o escri-
biendo los datos tal como llegan de manera secuencial.
CC-BY-SA • PID_00187406 56 Java RMI
stub m Fragmento de un objeto que se encuentra en el lado del cliente y es una represen-
tación local de un objeto remoto del servidor que referencia todos los métodos remotos de
dicho objeto.
Bibliografía
Bibliografía básica
Como bibliografía básica para complementar este módulo, recomendamos las obras siguien-
tes:
Caballé, S.; Xhafa, F. (2008). Programación distribuida en Java con RMI. Madrid: Delta Pu-
blicaciones Universitarias.
Pitt, E.; McNiff, K. (2001). Java.rmi. The Remote Method Invocation Guide. Addison Wesley.
Bibliografía complementaria
Referencias bibliográficas
Oráculo (2011). "RMI". The Java Tutorials. [Fecha de consulta: agosto del 2011]: http://
download.oracle.com/javase/tutorial/rmi/.
Wollrath, A.; Riggs, R.; Waldo, J. (1996). A Distributed Object Model for the Java System.
Sun Microsystems. [Fecha de consulta: enero del 2012]: http://pdos.csail.mit.edu/6.824/pa-
pers/waldo-rmi.pdf.