31 de Marzo de 2001 Vol.2 No.1

Java RMI

Rolando Menchaca Méndez.

Félix García Carballeira.

Palabras Clave: Objetos Distribuidos, Sistemas Distribuidos, Java RMI, Activación, Recolección Distribuida de Basura, Llamadas a Procedimientos Remotos, Sockets, Evaluación, .

Resumen

 

En el presente artículo se describe la arquitectura de una de las principales alternativas para el desarrollo de aplicaciones basadas en objetos distribuidos, Java RMI. Además se presenta de manera detallada un ejemplo donde se muestra una de las adiciones más significativas al modelo de objetos distribuidos de Java en su versión 1.2 con respecto a la 1.1, la capacidad de activar "sobre demanda" a los objetos remotos.

[English]

Artículo

En la actualidad, el cómputo distribuido ocupa un lugar preponderante tanto en las ciencias de la computación como en la industria, debido a que muchos de los problemas a los que se enfrentan son inherentemente distribuidos. De la misma manera, las tecnologías orientadas a objetos se han consolidado como una de las herramientas más eficaces en el desarrollo de software, debido principalmente a su capacidad de describir los problemas en el dominio del problema, más que en el dominio de la solución.

Dentro del ámbito del cómputo distribuido se incorpora fuertemente la tecnología orientada a objetos, debido a que en el paradigma basado en objetos el estado de un programa ya se encuentra distribuido de manera lógica en diferentes objetos, lo que hace a la distribución física de estos objetos en diferentes procesos o computadoras una extensión natural.

La invocación remota de métodos de Java es un modelo de objetos distribuidos, diseñado específicamente para el lenguaje Java, por lo que mantiene la semántica del modelo de objetos locales de Java, facilitando de esta manera la implantación y el uso de objetos distribuidos. En el modelo de objetos distribuidos de Java, un objeto remoto es aquel cuyos métodos pueden ser invocados por objetos que se encuentran en una máquina virtual (MV) diferente. Los objetos de este tipo se describen por una o más interfaces remotas que contienen la definición de los métodos del objeto que es posible invocar remotamente.

La invocación remota de un método (RMI) es la acción de invocar un método de una interfaz remota de un objeto remoto. La invocación de un método de un objeto remoto tiene exactamente la misma sintaxis de invocación que la de un objeto local

Objetos Remotos contra Procedimientos Remotos y sockets

Los sistemas distribuidos requieren que las partes que los componen y que se ejecutan en diferentes espacios de direcciones (posiblemente en diferentes máquinas), tengan la capacidad de comunicarse entre sí.

Una de las primeras soluciones a esta problemática fueron los sockets, que precisamente tienen la capacidad de comunicar dos procesos, ya sea mediante datagramas o flujos de datos (streams). Sin embargo, los sockets requieren que las aplicaciones implanten sus propios protocolos para codificar y decodificar los mensajes que intercambian, lo que introduce una problemática diferente a la naturaleza del problema a resolver y aumenta la posibilidad de errores durante la ejecución.

La primera alternativa que surgió al empleo de los sockets (y que se implementa en base a ellos), son las llamadas a procedimientos remotos (RPC) donde la comunicación entre los elementos que componen el sistema distribuido, se realiza mediante la invocación de funciones que se encuentran en espacios de direcciones diferentes. En este caso, el programador tiene la "impresión" de trabajar con procedimientos locales, mientras que en realidad el sistema RPC se encarga de empaquetar los argumentos y enviarlos al proceso que contiene el código que implementa a la rutina remota. Los sistemas codifican los parámetros de la invocación, así como los valores de vuelta en una representación externa de los datos. Un ejemplo de este tipo de representaciones externas es XDR (eXternal Data Representation) especificado dentro de la comunidad Internet en el documento RFC 1832.

Como es de esperar, en los lenguajes cuyo paradigma de programación no es el procedimental, sino el orientado a objetos, se requiere ya no invocar procedimientos remotos, sino a métodos de objetos remotos. El empleo de objetos distribuidos Java, en lugar de procedimientos remotos, implica varias ventajas como la orientación a objetos misma, movilidad de las aplicaciones Java, los patrones de diseño, la seguridad, la recolección de basura distribuida, etcétera.

La principal desventaja de los objetos distribuidos de Java, con respecto a las llamadas a procedimientos remotos y Sockets, es definitivamente el rendimiento. Esta desventaja es análoga a la del mismo lenguaje Java, con respecto a los lenguajes completamente compilados como C [7], pero de la misma manera que en ese caso, todas las características positivas del modelo de objetos distribuidos de Java, lo hacen una alternativa bastante interesante con respecto a sus contrapartes procedimentales de más bajo nivel.

Metas del Sistema RMI de Java

Las metas que se pretenden alcanzar al soportar objetos distribuidos en Java, son:

  • Proporcionar invocación remota de objetos que se encuentran en MVs diferentes.
  • Soportar llamadas a los servidores desde los applets.
  • Integrar el modelo de objetos distribuidos en el lenguaje Java de una manera natural, conservando en medida de lo posible la semántica de los objetos Java.
  • Hacer tan simple como sea posible la escritura de aplicaciones distribuidas.
  • Preservar la seguridad proporcionada por el ambiente Java.
  • Proporcionar varias semánticas para las referencias de los objetos remotos (persistentes, no persistentes y de "activación retardada" ). En [7] se describen los modelos de seguridad que implementan los sistemas en tiempo de ejecución Java.

Aplicaciones con Objetos Distribuidos en Java

Un programa orientado a objetos consta de una colección de objetos que interactúan entre sí, solicitando y proporcionando servicios mediante el intercambio de mensajes, por lo que se ajustan bastante bien al diseño de sistemas distribuidos, debido a que estos mensajes enviados y recibidos por los objetos, pueden ser extrapolados a mensajes enviados a objetos en diferentes máquinas a través de la red. Los objetos distribuidos radican en aplicaciones que pueden actuar como clientes, servidores o ambos, dependiendo de si contienen objetos remotos, referencias de ellos o ambos.

Una aplicación servidora que utiliza RMI, generalmente crea cierto número de objetos remotos, permite que se creen referencias a dichos objetos y espera a que algún cliente invoque de manera remota los métodos de dichos objetos. Una aplicación cliente típica obtiene referencias de uno o más objetos remotos, e invoca sus métodos por medio del sistema RMI de Java, el que se encarga de proporcionar los mecanismos mediante los cuales, tanto clientes como servidores, pueden comunicarse.

Las funciones esenciales que deben desarrollar las aplicaciones distribuidas, son:

  • Localizar objetos remotosLas aplicaciones cliente tienen dos alternativas para obtener referencias de objetos remotos. Una aplicación puede registrar sus objetos remotos ante un servidor de nombres llamado rmiregistry, o la aplicación puede pasar referencias a objetos remotos como parámetro de una invocación o como valor de retorno.
  • Comunicarse con los objetos remotosLos detalles de la comunicación entre los objetos remotos, son manejados por el sistema RMI. Para el programador, la comunicación entre objetos se asemeja a la utilizada normalmente en programas Java.
  • Cargar el código de operación que implementa a las clases que son pasadas por valor.Debido a que RMI permite pasar objetos Java puros, como parámetros en la invocación de métodos de objetos remotos, proporciona los mecanismos necesarios para, por medio de un servidor HTTP o FTP, cargar el código y los datos de dichos objetos.(figura 1.)En la figura 1 se muestra una aplicación distribuida, basada en RMI, que utiliza al servidor de nombres rmiregistry para obtener referencias de objetos remotos. El servidor que implementa los objetos remotos, invoca al rmiregistry para asociarle un nombre a un objeto remoto. El cliente busca al objeto remoto utilizando su nombre como argumento y, finalmente, invoca alguno de sus métodos. La figura 1 también muestra cómo el sistema RMI puede usar un servidor web para cargar códigos de operación, de clientes a servidores y de servidores a clientes, mediante el empleo de cualquier protocolo URL . Lazy activation. Como se verá más adelante, consiste en activar un objeto hasta que se invoca alguno de sus métodos. Un localizador de recursos uniforme (Uniform Resourse Locator) es una representación compacta de la localización y del medio de acceder a algún recurso disponible vía Internet. El URL proporciona un apuntador a cualquier objeto que sea accesible en cualquier máquina conectada a Internet. Debido a que los objetos son accesibles de diferentes maneras (ftp, http, gopher, file, etc.), el URL indica además el método de acceso que se debe utilizar para obtener el objeto deseado. (ver figura 1.)

Coincidencias y diferencias entre el modelo de objetos local y distribuido de Java

Como mencionamos, el modelo de objetos distribuidos de Java se desarrolló teniendo como meta acercarlo lo más posible al modelo de objetos locales de Java. Como es de esperar, un objeto remoto no puede ser exactamente igual a uno local, pero es similar en dos aspectos muy importantes:

  • Se puede pasar una referencia a un objeto remoto, como argumento o como valor de retorno en la invocación de cualquier método, ya sea local o remoto.
  • Se puede forzar una conversión de tipos de un objeto remoto a cualquier interfaz remota, mediante la sintaxis normal de Java que existe para este propósito.

Sin embargo el modelo de objetos distribuidos difiere con el modelo de objetos locales en los siguientes aspectos:

  • Los clientes de los objetos remotos interactúan con las interfaces remotas y nunca con las clases que implementan dichas interfaces.
  • Los argumentos de los métodos remotos, así como los valores de retorno, son pasados por copia y no por referencia.
  • Los objetos remotos se pasan por referencia y no mediante la copia de la implantación del objeto.
  • La semántica de algunos métodos definidos por la clase java.lang.Object, está especializada para el caso de los objetos remotos.
  • Los clientes deben tener en cuenta excepciones adicionales referentes a la invocación remota de los métodos.

Esqueletos y Cabos (Stubs)

RMI utiliza el mismo mecanismo que los sistemas RPC para implementar la comunicación con los objetos remotos, el basado en esqueletos y cabos. Los cabos forman parte de las referencias y actúan como representantes de los objetos remotos ante sus clientes. En el cliente se invocan los métodos del cabo, quien es el responsable de invocar de manera remota al código que implementa al objeto remoto. En RMI un cabo de un objeto remoto implementa el mismo conjunto de interfaces remotas que el objeto remoto al cual representa.

Cuando se invoca algún método de un cabo, realiza las siguientes acciones:

  • Inicia una conexión con la MV que contiene al objeto remoto.
  • Aplana (marshals) y transmite los parámetros de la invocación a la MV remota.
  • Espera por el resultado de la invocación.
  • Desaplana (unmarshals) y devuelve el valor de retorno o la excepción.
  • Devuelve el valor a quien lo llamó.

Los cabos se encargan de ocultar el aplanado de los parámetros, así como los mecanismos de comunicación empleados. En la MV remota, cada objeto debe poseer su esqueleto correspondiente (en los ambientes donde se utilice únicamente JDK 1.2 o alguna versión posterior, los esqueletos no son necesarios). El esqueleto es responsable de despachar la invocación al objeto remoto.

Cuando un esqueleto recibe una invocación, realiza las siguientes acciones:

  • Desaplana los parámetros necesarios para la ejecución del método remoto.
  • Invoca el método de la implantación del objeto remoto.
  • Aplana los resultados y los envía de vuelta al cliente.

A partir del JDK 1.2 se introdujo un protocolo adicional a los cabos, para eliminar la necesidad de los esqueletos en el lado de las implantaciones de los objetos remotos, y en lugar de ellos, se utiliza código genérico para realizar todas las operaciones que eran responsabilidad de los esqueletos en el JDK 1.1. Tanto cabos como esqueletos, son generados por un compilador llamado rmic.

Utilización de hilos en la invocación de métodos remotos

El sistema de tiempo de ejecución de RMI no garantiza que la activación de las invocaciones de los objetos remotos se proyecte sobre diferentes hilos de ejecución. Pero debido a que es posible que las invocaciones remotas de un mismo objeto se ejecuten concurrentemente, es necesario que las implantaciones de los objetos sean thread-safe .

Recolección de Basura de Objetos Distribuido

En un sistema de objetos distribuidos, como en un sistema local, es deseable contar con un servicio que automáticamente elimine todos aquellos objetos que no posean una referencia activa hacia ellos. RMI utiliza un algoritmo de recolección de basura similar al de los objetos de red de Modula-3 , el cual está basado en un contador de referencias.

Para realizar la recolección de basura, basada en un contador de referencias, el sistema en tiempo de ejecución de RMI da seguimiento a todas las referencias activas de los objetos remotos. La obtención de la primera referencia a un objeto dentro de una MV remota, provoca el envío de un mensaje al servidor que contiene el objeto remoto, indicando que el objeto tiene al menos una referencia activa (referenced) en esa MV. Cuando posteriormente se crean nuevas referencias activas al objeto remoto en dicha MV, en ella se incrementa un contador local de referencias. Cuando se identifican referencias que ya no están activas, el contador local se disminuye, y cuando el contador llega a cero, se envía un mensaje al servidor, indicándole que el objeto remoto no posee referencias activas en esa MV remota (unreferenced). Existen varias sutilezas en este protocolo para asegurar la entrega ordenada de los mensajes "referenceed" y "unreferenced", debido a que pueden provocar que un objeto sea eliminado prematuramente.

Cuando un objeto remoto no posee referencias activas, el sistema en tiempo de ejecución de RMI utiliza una referencia débil (weak reference) hacia él, para de esta forma, en el momento en que no existan referencias locales, el objeto sea susceptible de ser eliminado por el recolector de basura. El algoritmo de recolección de basura distribuido, interactúa con el recolector de basura de la MV local, manteniendo referencias normales y débiles a los objetos. Cuando se pasa un objeto remoto como parámetro o como valor de retorno, el identificador de la MV destino se añade al conjunto de referencias.

Cuando se necesite una notificación de que un objeto remoto dado ya no tiene referencias activas, se debe implementar la interfaz java.rmi.server.Unreferenced, que contiene al método unreferenced que se invoca en el momento en que dejen de existir las referencias remotas, es decir, cuando el conjunto de referencias es el vacío. Es importante notar que un objeto puede ser eliminado prematuramente, debido a un fallo en la red y por lo tanto no es posible garantizar integridad referencial a las referencias remotas. En otras palabras, es posible que una referencia remota haga referencia a un objeto que en realidad no exista.

Carga Dinámica de Clases

RMI utiliza el mecanismo de serialización de objetos de Java para transmitir datos entre máquinas, pero además agrega la información de localización necesaria para permitir que las definiciones de las clases se puedan cargar a la máquina que recibe los objetos.

Cuando se desaplanan los valores de retorno o los parámetros de una invocación para convertirlos en objetos activos dentro de la MV que los recibe, es necesario poseer las definiciones de las clases de todos estos objetos. En primera instancia, el proceso de desaplanado intenta resolver las clases por su nombre en el contexto del cargador de clases local (el contexto del cargador de clases del hilo actual). En caso de no encontrar las definiciones de manera local, RMI proporciona la facilidad de cargar dinámicamente la definición de las clases de los objetos recibidos, desde las direcciones especificadas por la información contenida en la invocación. Esto incluye cargar dinámicamente las clases de los cabos, así como cualquier tipo pasado por valor en la llamada RMI.

Para soportar la carga dinámica de clases, RMI utiliza subclases especiales de java.io.ObjectOutputStream y java.io.ObjectInputStream, para el manejo de flujos de objetos aplanados. Estas subclases especializan al método annotateClass de la clase ObjectOutputStream y al método resolveClass de la clase ObjectInputStream, para incluir información sobre la localización de los archivos de clase, que contienen la definición de los objetos contenidos en el flujo.

Utilización de RMI a través de Firewalls por medio de Proxies

Normalmente, la capa de transporte de RMI intenta abrir sockets directamente a puertos de los servidores a los que se desea conectar, pero en el caso de que los clientes se encuentren detrás de algún firewall que no permita este tipo de conexiones, la capa de transporte estándar de RMI proporciona un mecanismo alterno basado en el protocolo HTTP (confiable para el firewall), para permitir a los clientes que se encuentran detrás del firewall, invocar métodos de objetos que se encuentren del otro lado de él.

Para atravesar el firewall, la capa de transporte de RMI incluye la llamada remota dentro del protocolo HTTP, como el cuerpo de una solicitud POST, mientras que los valores de retorno se reciben en el cuerpo de la respuesta HTTP. La capa de transporte de RMI puede formular la solicitud POST, de alguna de las dos maneras siguientes:

  1. Si el proxy firewall permite entregar una solicitud HTTP directamente sobre cualquier puerto de la máquina destino, la solicitud se dirige directamente al puerto donde el servidor RMI se encuentra escuchando. La capa de transporte RMI en la máquina destino escucha con un socket que es capaz de entender y decodificar llamadas RMI, que se encuentren dentro de una solicitud POST.
  2. Si el proxy firewall sólo entrega solicitudes HTTP a ciertos puertos HTTP bien conocidos, la llamada se envía a un servidor HTTP que se encuentre escuchando en el puerto 80, donde se puede ejecutar un guión (script) CGI que se encargue de enviar la llamada RMI al puerto específico del servidor.

El sistema de transporte de RMI especializa la clase java.rmi.server.RMISocketFactory, para proporcionar una implantación por defecto de una fábrica de sockets que se encargue de proveer tanto a clientes como servidores de dichos recursos. La fabrica de sockets por defecto, crea sockets que proporcionan el mecanismo para hacer transparente el paso por firewalls.

  • Los sockets clientes automáticamente intentan conexiones HTML cuando el servidor no puede contestar directamente.
  • Los sockets del lado del servidor, automáticamente detectan si es que una nueva conexión se trata de una solicitud POST de HTTP. En ese caso, devuelven un socket que únicamente expone el cuerpo de la solicitud al sistema de transporte y da formato a sus salidas, como una respuesta HTML.

Activación de Objetos Remotos

Los sistemas de objetos distribuidos están diseñados para soportar objetos persistentes de larga vida. Debido a que dichos sistemas pueden estar constituidos por miles o quizás millones de objetos, no es razonable que sus instancias siempre estén activas y consumiendo recursos, aún cuando no se encuentren participando en ninguna tarea. Por otro lado, los clientes necesitan tener la capacidad de almacenar referencias persistentes de objetos, de manera tal que se pueda restablecer la comunicación con ellos después de un fallo en el sistema, o simplemente en una posterior ejecución del cliente.

El mecanismo para proveer referencias persistentes y administrar la ejecución de las implantaciones de los objetos, es la activación automática de dichos objetos. En RMI es posible activar dinámicamente los objetos en el momento en que son necesitados, es decir, cuando se accede a un objeto remoto "activable" vía la ejecución de alguno de sus métodos, el sistema se encarga de ejecutar el objeto en una MV Java apropiada.

Terminología de Objetos Activables

Un objeto remoto activo, es aquel que es instanciado y exportado en una MV Java. Un objeto remoto pasivo, es aquel que no ha sido instanciado o exportado, pero es susceptible de pasar al estado activo. El proceso de llevar un objeto pasivo a activo se conoce como activación. La activación requiere la asociación de un objeto con una MV. Dicha asociación incluye la carga del código que implementa la clase en la MV, así como la restauración del estado persistente del objeto, si es que lo tuviese.

En el sistema RMI se utiliza la activación tardía (lazy activation), que consiste en retrasar la activación del objeto hasta que algún cliente lo utilice por primera vez, es decir, la primera vez que se invoque alguno de sus métodos.

Activación Tardía (Lazy Activation)

La activación tardía de objetos remotos se implanta utilizando lo que se conoce como una "referencia remota responsable" (faulting remote reference), algunas veces denominada "bloque responsable". Una referencia remota responsable a un objeto remoto, "responde" por la referencia del objeto activo durante la primera invocación de algún método del objeto. Cada referencia responsable mantiene tanto un manipulador persistente (un identificador de activación) y una referencia remota transitoria al objeto remoto destinatario. El identificador de activación del objeto remoto contiene información suficiente para iniciar la activación del objeto remoto y la referencia transitoria es la referencia "viva" real al objeto remoto activo, que puede utilizarse para comunicarse con él.

En una referencia responsable, si la referencia viva a un objeto remoto es nula, no se sabe si el objeto destinatario está activo. Durante la invocación del método, la referencia responsable se compromete en el protocolo de activación a obtener una referencia "viva", que es una referencia remota para el objeto recién activado. Una vez que la referencia responsable obtiene la referencia "viva", le reenvía la invocación del método para que esta última a su vez reenvíe la invocación al objeto remoto.

En términos más concretos, una referencia de objeto remoto contiene un tipo referencia remota responsable, que contiene dos cosas:

  • Un identificador de activación para un objeto remoto y
  • Una referencia "viva" (posiblemente nula) que contiene el tipo referencia remota activa del objeto remoto.

Protocolo de Activación

Durante la invocación de un método remoto, si no se conoce la referencia "viva" para el objeto destino, la referencia responsable se compromete en el protocolo de activación. El protocolo de activación involucra muchas entidades: la referencia responsable, el activador, un grupo de activación en una MV Java y el objeto remoto que será activado.

El activador (generalmente uno por máquina) es la entidad que supervisa la activación, y actúa como:

  • Una base de datos que contiene las asociaciones entre los identificadores de activación y la información necesaria para activar al objeto (la clase del objeto, su ubicación -un camino URL-, de donde se puede obtener la clase, información que el objeto podría necesitar al momento de arrancar, etc. ) y
  • Un administrador de máquinas virtuales, que se encarga de ejecutar MVs (cuando sea necesario) y de encaminar solicitudes de activación de objetos (junto con la información necesaria) al grupo de activación correcto dentro una MV remota.

Un grupo de activación (uno por cada MV Java) es la entidad que recibe la solicitud de activación de un objeto en una MV y devuelve el objeto activado al activador.

El protocolo de activación es como sigue. Una referencia responsable utiliza un identificador de activación y llama al activador (mediante una interfaz RMI interna) para activar al objeto asociado con el identificador. El activador busca el descriptor de activación (activation descriptor) del objeto (previamente registrado). El descriptor del objeto contiene:

  • Un identificador del grupo del objeto (especifica la MV en que será activado),
  • El nombre de la clase del objeto,
  • Un URL que contenga el camino de dónde obtener el código de la clase del objeto,
  • Datos de inicialización aplanados específicos al objeto (por ejemplo el nombre del archivo que contiene el estado persistente del objeto).

Si el grupo de activación en el que debe de residir el objeto ya existe, el activador transfiere la solicitud de activación a dicho grupo. Si no existe, el activador inicia una nueva MV y ejecuta un grupo de activación, para luego transferirle la solicitud de invocación. El grupo de activación carga las clases del objeto y lo inicializa haciendo uso de un constructor especial que utiliza varios parámetros, incluyendo el descriptor de activación que había sido registrado previamente. Cuando finalmente se activa al objeto, el grupo de activación devuelve al activador una referencia del objeto aplanado (marshalled object reference). El activador almacena el par identificador de activación, referencia activa, y devuelve una referencia activa (viva) a la referencia responsable. La referencia responsable (dentro de la referencia) encamina la invocación de los métodos, vía la referencia viva, directamente al objeto remoto.

En el JDK, RMI proporciona una implantación de las interfaces del sistema de activación por medio del demonio rmid, por lo que es necesario que se esté ejecutando para poder realizar una activación.

Construcción de un Objeto Remoto Activable

A continuación detallaremos los pasos necesarios para construir un objeto remoto activable, utilizando JDK 1.2, así como para registrarlo ante el sistema de activación de RMI. Además se mostrará cómo construir un cliente para este objeto y de esta manera tener un ejemplo completo de la utilización de objetos remotos activables.

Definición de la Interfaz Remota

El primer paso para construir un objeto remoto es definir su interfaz. En ella se plasmarán todos los métodos que los clientes serán capaces de invocar de manera remota. Las interfaces remotas deben cumplir con las siguientes características:

  • Deben ser public. En caso contrario los clientes generaran errores al intentar cargar un objeto remoto (su referencia) que implemente a la interfaz remota, a menos, claro, que pertenezcan al mismo paquete.
  • Extender a la interfaz java.rmi.Remote.
  • Cada método de la interfaz remota, adicionalmente a cualquier excepción que pueda lanzar, debe declarar la excepción java.rmi.RemoteException (o alguna de sus superclases) en su cláusula throws.
  • El tipo de dato de cualquier objeto remoto que se pasa como argumento o valor de retorno (ya sea directamente o dentro de otro objeto), se debe declarar del tipo de su interfaz remota y no de la clase que lo implementa.

package ejemplosrmi.activacion; import java.rmi.*;

public interface InterfazRemota extends Remote {public Object vaYviene(String s) throws RemoteException;}


Figura 2. Definición de la interfaz remota.

En la figura 2 se muestra la definición de la interfaz para el ejemplo que comenzamos a desarrollar. En ella definimos un solo método, que recibe como parámetro un objeto cadena y devuelve un objeto del tipo Object, que en realidad es la misma cadena que se recibió como parámetro de la invocación.

Implantación del Objeto Activable

Una vez definida la interfaz del objeto remoto, desarrollaremos la clase que la implemente, de manera tal que los objetos instanciados a partir de ella tengan la capacidad de ser activados bajo demanda. Para esto la definición de la clase debe:

  • Importar java.rmi.* y java.rmi.activation.*.
  • Especializar a la clase java.rmi.activation.Activatable. También es posible utilizar el método estático Activatable.exportObject en el constructor de una clase que no especialice a java.rmi.activation.Activatable para poner sus métodos disponibles a clientes remotos.
  • Declarar un constructor de dos argumentos, cuyo propósito es activar y exportar al objeto en un puerto específico.
  • Implementar los métodos de la interfaz remota (todos los métodos deben declarar la excepción java.rmi.RemoteException o alguna de sus superclases en su cláusula throws, adicionalmente a las excepciones particulares a la aplicación).

En la figura 3, podemos observar la implantación del objeto del tipo ImplantacionActivable. En ella podemos ver que el método vaYviene únicamente devuelve la cadena que recibió como parámetro de la invocación.


package ejemplosrmi.activacion;

import java.rmi.*;

import java.rmi.activation.*;

public class ImplantacionActivable extends Activatable

implements ejemplosrmi.activacion.InterfazRemota {

// Constructor para activar y exportar al objeto remoto. El método ActivatorInstantiator.newInstance

// llama a este constructor durante la activación del objeto para construirlo.

public ImplantacionActivable(ActivationID id, MarshalledObject datos)

// Registra al objeto ante el sistema de activación y lo exporta a un puerto anónimo (por enviarle un cero)

super(id, 0);

}

// Implementa al metodo declarado en la interfaz remota "InterfazRemota"

public Object vaYviene(String s) throws RemoteException {

return s;

}

}


Figura 3. Definicin de la clase ImplantacionActivable que implementa la interfaz remota InterfazRemota.

Creación de una clase de establecimiento (setup)

La función de la clase de establecimiento es crear toda la información necesaria para la clase activatable, sin tener que crear una instancia del objeto remoto. La clase de establecimiento pasa la información referente a la clase activatable al demonio rmid, registra una referencia remota del objeto (una instancia de la clase de cabo de la clase activatable) y un identificador (nombre) ante el rmiregistry. Para crear una clase de establecimiento debe de:

  • Importar java.rmi.*, java.rmi.activation.* y java.util.Properties.
  • Crear una instancia de un descriptor de grupo de activación (ActivationGroup), cuya tarea es proporcionar a rmid toda la información que necesitará para contactar a la MV apropiada para el objeto activatable o en su defecto crearle una nueva.
  • Instalar el administrador de seguridad (RMISecurityManager).
  • Crear una instancia de un descriptor de activación (ActivationDesc), cuyo trabajo es proporcionar a rmid toda la información que necesitará para crear una instancia de la clase que implementa al objeto remoto.
  • Declarar una instancia de la interfaz remota y registrar el descriptor de activación ante el demonio rmid.
  • Asociar la referencia devuelta por el método Activatable.register a un nombre en el rmiregistry.
  • Salir de la aplicación establecimiento.

En la figura 4 se muestra el código que implementa a la clase de establecimiento para registrar al objeto del tipo Activatable.


package ejemplosrmi.activacion;

import java.rmi.*;

import java.rmi.activation.*;

import java.util.Properties;

public class Establecimiento {

public static void main(String[] args) throws Exception {

System.setSecurityManager(new RMISecurityManager());

// Debido a que en el modelo de seguridad del JDK 1.2, se deben especificar políticas de seguridad

// para la MV del ActivationGroup, el primer argumento del método put (heredado de Hashtable)

// de las propiedades (Properties) es la llave (key) y el segundo es su valor.

Properties props = new Properties();

props.put("java.security.policy","e:/rmm/java/progs/policy");

ActivationGroupDesc.CommandEnvironment propsGrupoActivacion = null;

ActivationGroupDesc descriptorGrupoActivacion =

new ActivationGroupDesc(props, propsGrupoActivacion);

// Una vez que se crea el ActivationGroup, se debe registrar ante el sistema de activación

// (activation system) para obtener su identificador

ActivationGroupID agi =

ActivationGroup.createGroup(agi, descriptorGrupoActivacion, 0);

// La cadena "loaction" especifica una URL de donde se puede obtener la definición de la

// clase en el momento en que se activa al objeto.

String ubicacion = "file:e:/rmm/java/progs/";

// Para crear el resto de los parámetros que se pasarán al constructor del ActivationDesc

MarshalledObject datos = null;

// El segundo argumento del constructor del ActivationDesc se utiliza únicamente para identificar

// a la clase; su localización es relativa a la cadena con formato URL contenida en ubicacion.

ActivationDesc desc = new ActivationDesc("ejemplosrmi.activacion.ImplantacionActivable",

ubicacion, datos);

// Lo registra ante el demonio rmid

InterfazRemota iR = (InterfazRemota)Activatable.register(desc);

System.out.println("Se obtuvo la referencia de la ImplantacionActivable");

// Asocia la referencia con su nombre en el registro (rmiregistry)

Naming.rebind("ImplantacionActivable", iR);

System.out.println("Se exportó la ImplantacionActivable");

System.exit(0);

}

}


Figura 4. Cdigo de la clase de establecimiento.

Cliente del Objeto Activable

Ahora que tenemos, tanto la implantación del objeto remoto, como la clase que se encargará de registrarlo ante el sistema de activación, nos evocaremos a escribir un cliente que sea capaz de invocar de manera remota a los métodos de la interfaz InterfazRemota. El cliente debe de:

  • Importar java.rmi.*.
  • Instalar el administrador de seguridad (RMISecurityManager).
  • Obtener una referencia del objeto remoto, mediante una llamada al método Naming.lookup, pasándole como parámetro una cadena con formato URL que contiene la máquina donde reside el objeto, así como su nombre.
  • Invocar al método remoto (vaYviene) del objeto remoto, mediante la referencia remota devuelta por el método Naming.lookup.

El código del cliente puede observarse en la figura 5.

package ejemplosrmi.activacion;

import java.rmi.*;

public class Cliente {

public static void main(String args[]) {

// Establece un administrador de seguridad con el propósito

// de que el cliente pueda obtener la referencia del objeto.

System.setSecurityManager(new RMISecurityManager());

try {

String ubicacion = "rmi://orca.cic.ipn.mx/ImplantacionActivable";

// Debido a que no se puede crear una instancia de una interfaz, lo que se obtiene del método lookup es una

// referencia remota a un objeto que implementa a la interfaz InterfazRemota.Posteriormente convertimos

// mediante un "cast" a la referencia remota devuelta por Naming.lookup (una instancia del cabo serializado)

// en una "InterfazRemota" de manera que podamos llamar a los métodos de la interfaz.

InterfazRemota iR = (InterfazRemota)Naming.lookup(ubicacion);

System.out.println("Obtuve una referencia remota del objeto ImplantacionActivable");

// La cadena "resultado" será cambiada por el objeto remoto debido a la invocación del método "vaYviene".

String resultado="Error en la invocación remota";

System.out.println("Realizando la invocación remota...");

resultado = (String)iR.vaYviene("Saludos México en el 2001");

System.out.println("El objeto remoto devolvio: " + resultado);

} catch (Exception e) {

System.out.println(e.getMessage());

e.printStackTrace();

}

}

}

 


Figura 5. Cliente del objeto remoto.

Compilación y ejecución del código

En este momento, únicamente nos resta compilar y ejecutar los programas que componen nuestro ejemplo. Para esto son necesarios 6 pasos:

  1. El primero es compilar los cuatro archivos Java de la siguiente manera:javac InterfazRemota.javajavac ImplantaciónActivable.javajavac Establecimiento.javajavac ClienteEsto generará los cuatro archivos de clase (.class) correspondientes a cada uno de los archivos fuente (.java).
  2. Crear los archivos de cabo y esqueleto para el objeto remoto, mediante el uso del compilador rmic, que recibe como parámetro el nombre completamente calificado de la clase que contiene la implantación del objeto remoto.En Java 2 SDK v1.2, el rmic funciona por defecto con la opción -vcompact, y debido a esto los cabos y esqueletos generados soportan acceso a objetos remotos no activables (Unicast), desde clientes de la versión 1.1 y todos los tipos de objetos remotos, desde clientes de la versión 1.2.Para generar los archivos de esqueleto y cabo del ejemplo, se debe ejecutar el compilador rmic de la siguiente manera:rmic ejemplosrmi.activacion.ImplantacionActivable
  3. Una vez compilados todos los archivos que componen el ejemplo, es necesario ejecutar el servicio de nombres de RMI (rmiregistry), mediante el cual los clientes pueden obtener una referencia remota de un objeto, preguntando por su nombre. Para ejecutar el rmiregistry se necesita introducir el siguiente comando:rmiregistry & (en el caso de Unix) ostart rmiregistry (en el caso de Windows)Por defecto, el registro de nombres de RMI escucha las peticiones de los clientes en el puerto 1099, pero es posible indicarle un puerto específico desde la línea de comandos. Por ejemplo, para ejecutar el registro en el puerto 5000 se debe introducir:start rmiregistry 5000Si se ejecuta el registro en un puerto diferente al designado por defecto, es necesario que en los clientes, al momento de hacer uso de los servicios de la clase java.rmi.Naming, se especifique el puerto en el que está escuchado el registro. Por ejemplo:Naming.bind("//orca.cic.ipn.mx:5000/ImplantacionActivable");
  4. Arrancar el demonio de activación rmid.start rmid
  5. El siguiente paso es ejecutar el programa de establecimiento. Aquí es importante definir la propiedad de ubicación llamada codebase, donde se especifica el lugar de donde se pueden obtener los cabos del objeto remoto. Además de definir el codebase, también es necesario hacer lo mismo con las políticas de seguridad que especifican los permisos o privilegios que poseerán los códigos obtenidos desde diferentes fuentes. Estas políticas están representadas por una subclase de java.security.Policy que proporciona una implantación de sus métodos abstractos. La implantación por defecto de Policy obtiene la información necesaria a partir de archivos de configuración estáticos, donde se definen los permisos para el código.Existen dos maneras de crear los archivos de configuración. La primera utilizando un editor de texto cualquiera o usando la herramienta llamada policytool. Para fines del ejemplo, utilizaremos un archivo que contenga las siguientes líneas:grant {// Habilita todos los permisos. permission java.security.AllPermission;};Es muy importante notar que habilitar todos los permisos puede resultar sumamente riesgoso, debido a que se deshabilitan todos los mecanismos de seguridad de la MV Java. Políticas de este estilo sólo se deben implementar en fases de depuración o en situaciones donde las aplicaciones o applets sean completamente confiables.Ahora estamos listos para ejecutar el programa que dará de alta nuestro objeto remoto en el sistema de activación de RMI. Para esto hay que introducir:java -Djava.security.policy=e:\rmm\java\progs\policy -Djava.rmi.server.codebase=file:/e:/rmm/java/progs/ ejemplosrmi.activacion.Establecimiento Lo que producirá la siguiente salida:Se obtuvo la referencia de la ImplantacionActivable Se exportó la ImplantacionActivable
  6. El último paso para completar el ejemplo, es ejecutar el programa cliente (donde se invoca de manera remota al método vaYviene). Para esto se ejecuta la siguiente instrucción:java -Djava.security.policy=e:\rmm\java\progs\policy ejemplos.activation.Cliente De donde obtendremos la siguiente salida:Obtuve una referencia remota del objeto ImplantacionActivableRealizando la invocación remota...El objeto remoto devolvió: Saludos México en el 2001

Evaluación

En esta sección se describe una evaluación realizada a la invocación de métodos remotos de Java. Las pruebas intentan medir:

  • El tiempo necesario para obtener una referencia a un objeto remoto.
  • El tiempo necesario en invocar un método remoto vacío, es decir, un método que no realiza ninguna tarea. Esta prueba intenta calcular la latencia en la invocación de métodos remotos.
  • El ancho de banda que se obtiene en el intercambio de información entre un cliente y un servidor.

Para la realización de las pruebas se utilizaron dos máquinas con procesador Pentium III dual a 550 MHz con 128 Mbytes de memoria. Estas máquinas se encuentran conectadas por una Fast Ethernet (100 Mbits/s) y ejecutan el sistema operativo Linux.

La tabla 1 muestra el tiempo necesario (en milisegundos) para obtener una referencia a un objeto. La tabla muestra el resultado en el caso de que el cliente y el servidor ejecuten en la misma máquina (ejecución local), y el resultado obtenido cuando el cliente y el servidor ejecutan en máquinas distintas (ejecución remota). La tabla compara distintas alternativas para construir aplicaciones cliente-servidor: uso de sockets en C y Java, empleo de RPC y RMI. En el caso de los sockets, se midió el tiempo necesario para realizar la conexión entre el cliente y el servidor. En el caso de las RPC, se midió el tiempo necesario para localizar al servidor. Con RMI se midió el tiempo empleado en obtener una referencia al objeto remoto.



Sockets en C


Sockets en Java


RPC


RMI


Ejecución local


1,05 ms


38 ms


1,56 ms


237 ms


Ejecución remota


5,1 ms


100 ms


5,8 ms


418 ms


La tabla 2 presenta el tiempo obtenido en ejecutar un método vacío. La tabla también contiene el tiempo necesario en ejecutar un procedimiento remoto utilizando RPC.



RPC


RMI


Ejecución local


0.12 ms


1 ms


Ejecución remota


0.21 ms


4 ms


El ancho de banda que se obtiene en la comunicación entre un cliente y un servidor, se midió mediante el intercambio de un par de mensajes (un vector de bytes) entre ellos. El cliente mide el tiempo que transcurre entre el envío de un mensaje y la recepción del mismo, y calcula la latencia del intercambio de un mensaje como la mitad de este tiempo. Utilizando RPC, el procedimiento remoto acepta un vector de bytes y devuelve el mismo vector. En el caso de RMI, el método remoto recibe un vector de bytes y devuelve el mismo vector de bytes. A continuación se calcula el ancho de banda, en función del tamaño del mensaje y la latencia anteriormente calculada. Los resultados para la ejecución local (cliente y servidor en la misma máquina) se muestran en la figura 6,(ver fig. 6) y los resultados para la ejecución remota, en la figura 7. (ver fig.7)


Es la capacidad de denotar entidades (objetos, componentes) en el dominio de la solución que representen fielmente (al menos mejor que otros estilos) a conceptos y entidades presentes en el dominio del problema. Hay un mapeo mucho más directo entre los objetos de negocios (del problema) y los objetos de datos (de la solución) que lo modelan.
En [7] se describen los modelos de seguridad que implementan los sistemas en tiempo de ejecución Java.
Lazy activation. Como se verá más adelante, consiste en activar un objeto hasta que se invoca alguno de sus métodos.
Un localizador de recursos uniforme (Uniform Resourse Locator) es una representación compacta de la localización y del medio de acceder a algún recurso disponible vía Internet. El URL proporciona un apuntador a cualquier objeto que sea accesible en cualquier máquina conectada a Internet. Debido a que los objetos son accesibles de diferentes maneras (ftp, http, gopher, file, etc.), el URL indica además el método de acceso que se debe utilizar para obtener el objeto deseado.
La otra parte fundamental de la referencia de un objeto remoto, son los datos necesarios para identificar dentro del sistema de objetos distribuidos, a la instancia particular que desea invocar.
Comúnmente conocido como proxy.
Se dice que un código es thread-safe cuando puede ser ejecutado simultáneamente por varios hilos de manera correcta.
Este sistema incluye un sistema de recolección de basura basado en un contador de referencias. Para cada objeto de red, el sistema en tiempo de ejecución almacena el conjunto de clientes que tienen referencias (substitutos en los términos de modula-3) a él, lo que se conoce como el conjunto sucio (dirty set). Mientras este conjunto sea diferente del vacío, el sistema en tiempo de ejecución mantiene un puntero al objeto lo que lo protege de ser eliminado por el recolector de basura local al objeto.
Las subclases de ObjectOutputStream pueden implementar este método para almacenar datos de la clase (por ejemplo los bytes del archivo de clase) dentro del flujo.
Las subclases de ObjectInputStream pueden implementar este método para utilizar un mecanismo de carga alterno al utilizado por defecto.
También es posible utilizar el método estático Activatable.exportObject en el constructor de una clase que no especialice a java.rmi.activation.Activatable para poner sus métodos disponibles a clientes remotos.



Conclusiones

La utilización de objetos en el desarrollo de sistemas distribuidos, presenta varias ventajas que permiten ocultar las dificultades inherentes a la distribución en niveles de abstracción inferiores, dejando así a los programadores la tarea de resolver únicamente su problema en particular.

La invocación remota de métodos en Java parte del hecho de correr sobre una plataforma homogénea. Está diseñada para tomar ventaja de esta característica, lo que le permite presentar propiedades que otros modelos de objetos como CORBA no poseen. Ejemplo de esto lo tenemos en la capacidad que tiene RMI de migrar dinámicamente a las implantaciones de los objetos, lo que le puede permitir a un cliente enviar un objeto para que se ejecute en una máquina con mayor poder de cómputo. Por otro lado, RMI posee todas las características de seguridad que hereda de la plataforma Java misma. Pero a diferencia de CORBA, que posee una arquitectura que proporciona independencia del lenguaje de programación, RMI está diseñada exclusivamente para Java.

Por último, podemos mencionar que Java RMI se utiliza como infraestructura fundamental de otras tecnologías Java sumamente importantes, como los Enterprise JavaBeans y JINI.

Bibliografía

RMI Specification.. http://web2.java.sun.com/products/jdk/1.2/docs/guide/rmi/spec/rmiTOC.doc.html.

JavaTM 2 Platform, Standard Edition, v1.2.2 API Specification.. http://java.sun.com/products//jdk/1.2/docs/api/.

Getting Started Using RMI.. http://web2.java.sun.com/products/jdk/1.2/docs/guide/rmi/getstart.doc.html.

Remote Object Activation.. http://web2.java.sun.com/products/jdk/1.2/docs/guide/rmi/activation.html

 

Default Policy Implementation and Policy File Syntax. http://java.sun.com/products/jdk/1.2/docs/guide/security/PolicyFiles.html.

Policy Tool - Policy File Creation and Management Tool.. http://java.sun.com/products/jdk/1.2/docs/tooldocs/win32/policytool.html.

Rolando Menchaca Méndez, Félix García Carballeira (2000) "Arquitectura de la Máquina Virtual Java" [en línea]. Revista Digital Universitaria. 30 de septiembre de 2000, Vol. 1, No.2. http://www.revista.unam.mx.



[ Este nĂºmero ]



Direccin General de Servicios de Cmputo Acadmico-UNAM
Ciudad Universitaria, M
éxico D.F.