RMI (Remote Method Invocation) es un mecanismo que permite realizar llamadas a métodos de objetos remotos situados en distintas (o la misma) máquinas virtuales Java, compartiendo así recursos y carga de procesamiento a través de varios sistemas.
RMI permite exportar objetos como objetos remotos para que otro proceso remoto pueda acceder directamente como un objeto Java. Todos los objetos de una apliación distribuida basada en RMI deben ser implementados en Java. Esta es una de las principales ventajas de RMI, ya que RMI forma parte del API de Java, con lo que la integración de objetos remotos en aplicaciones distribuidas se realiza sin necesidad de usar recursos adicionales (como por ejemplo un lenguaje de descripción de interfaces o IDL). De hecho, se utiliza la misma sintasis para una llamada a un objeto remoto o un objeto local.
La Figura 1.1 ilustra los componentes implicados en un sistema RMI: un interfaz remoto, un cliente, y uno o más servidores (objetos remotos) residentes en un host.
Figura 1.1. Componentes de un sistema RMI.
El cliente invoca a los objetos remotos mediante la interfaz remota. Un servicio de nombres (registro RMI) reside en el host proporcionando el mecanismo que el cliente usa para encontrar uno más servidores iniciales RMI.
La interacción con el objeto remoto se lleva a cabo a través de la interfaz remota. Esencialmente, ésta describe los métodos que pueden ser invocados de forma remota, y que el objeto remoto implementa. Cuando se obtiene una referencia a un objeto remoto, el objeto no se envía a través de la red al cliente que lo solicita. En su lugar se genera un objeto proxy o stub que consituye el proxy de la parte del cliente del objeto remoto. Todas las interacciones del cliente se realizarán con esta clase stub, la cual es responsable de gestionar los datos entre el sistema local y el remoto. Muchos clientes pueden tener referencias a un único objeto remoto. Cada cliente tiene su propio objeto stub que representa al objeto remoto, pero dicho objeto remoto NO se replica.
En la parte del servidor, una clase skeleton es la responsable de gestionar las llamadas al método y los datos enviados al objeto real referenciado. Éste es el proxy de la parte del servidor para el objeto remoto. El sistema completo puede verse como un modelo de cuatro capas, tal y como se ilustra en la Figura 1.2.
Figura 1.2. Arquitectura RMI: modelo de cuatro capas.
La primera capa es la de aplicación, y se corresponde con la
implementación real de las aplicaciones cliente y servidor. Aquí
tienen lugar las llamadas a alto nivel para acceder y exportar objetos remotos.
Cualquier aplicación que quiera que sus métodos estén disponibles
para su acceso por clientes remotos debe declarar dichos métodos en una
interfaz que extienda java.rmi.Remote
. Dicha interfaz se usa básicamente
para "marcar" un objeto como remotamente accesible. Una vez que los
métodos han sido implementados, el objeto debe ser exportado. Esto puede
hacerse de forma implícita si el objeto extiende la clase UnicastRemoteObject
(paquete java.rmi.server
), o puede hacerse de forma explícita
con una llamada al método exportObject()
del mismo paquete.
La capa 2 es la capa proxy, o capa stub-skeleton. Esta capa es la que interactúa directamente con la capa de aplicación. Todas las llamadas a objetos remotos y acciones core sus parámetros y retorno de objetos tienen lugar en esta capa.
La capa 3 es la de referencia remota, es responsable del manejo de la parte semántica de las invocaciones remotas. También es responsable de la gestión de la replicación de objetos y realización de tareas específicas de la implementación con los objetos remotos, como el establecimiento de las persistencias semánticas y estrategias adecuadas para la recuperación de conexiones perdidas. En esta capa se espera una conexión de tipo stream (stream-oriented connection) desde la capa de transporte.
La capa 4 es la de transporte. Es la responsable de realizar las conexiones necesarias y manejo del transporte de los datos de una máquina a otra. El protocolo de transporte subyacente para RMI es JRMP (Java Remote Method Protocol), que solamente es "comprendido" por programas Java.
RMI reduce la complejidad de la programación distribuida, convirtiendo las tareas de localizar el servidor, realizar la conexión a la red, transferencia de datos, sincronización y propagación de errores en una simple llamada a un método y manejador de excepciones en el cliente.
La sintaxis de RMI es la siguiente:
try { result= remoteInterface.method(args) } catch (RemoteException ex) { //manejo de excepciones remotas }
Un objeto remoto es un objeto cuyos métodos pueden ser
invocados (via una interfaz remota) desde otra máquina virtual de Java.
Tiene las propiedades usuales de un objeto Java, dadas por su estado y métodos,
pueden hacer referencias a otros objetos, e incluso tienen implementaciones
de los métodos Object.clone
, Object.equals
,
Object.hashCode
y Object.toString.
Una interfaz remota es una interfaz Java que extiende java.rmi.Remote
.
Todos sus métodos deben indicar que pueden lanzar la excepción
RemoteException
.
Debido a que las interfaces Java no pueden especificar métodos static
,
un método remoto no puede ser static
.
Cualquier objeto, incluso un objeto local, puede ser visto como un servidor, y sus usuarios son sus clientes.
Un objeto remoto es accedido via un stub remoto. Un stub remoto es un objeto que implementa las mismas interfaces remotas que el objeto remoto al que hace referencia. La clase asociada al stub se genera por el correspondiente objeto remoto por el sistema RMI en tiempo de compilación.
El cliente usa el stub remoto como una instancia de la interfaz remota implementada por el objeto remoto. El stub remoto no es en sí el objeto remoto, ni una instancia de la clase remota, realmente es un proxy para el objeto remoto.
El stub remoto es el responsable de iniciar la llamada al objeto remoto que representa a través de la capa de referencia remota. También es responsable de organizar adecuadamente los argumentos del método a través de un marshal stream, obtenido también a través de la capa de referencia remota. Un marshal stream no es más que un objeto que se utiliza para transportar parámetros, excepciones, y errores necesarios para la ejecución del método y el retorno de los resultados. Finalmente, la clase stub informa a la capa de referencia remota que la llamada se ha completado.
La clase skeleton es similar a la clase stub, pero en el lado del servidor, de forma que trata directamente con la implementación del método remoto que está siendo utilizado. El skeleton es responsable de enviar los parámetros al método remoto y recoger los valores de los resultados y excepciones para enviárlos al cliente que realizó la llamada. Constituye el proxy del lado del servidor del objeto remoto.
Cuando un proceso servidor desea exportar algún servicio basado en RMI
a los clientes, debe hacerlo registrando uno o más objetos a través
de su registro local RMI (representado por la interfaz Registry
).
Cada objeto es registrado con un nombre que puede utilizarse por los clientes
para referenciarlo (es lo que se denomina servicio de nombres).
Un cliente puede obtener una referencia al stub correspondiente al objeto
remoto utilizando la interfaz Naming
. El método Naming.lookup()
obtiene el nombre del objeto remoto y localiza a dicho objeto en la red. El
nombre del objeto tiene una sintaxis similar a una dirección URL e incluye
el nombre del host del objeto y el nombre registrado de ese objeto.
El servicio de activación de objetos permite a los objetos servidores ser activados según las necesidades. Sin una activación remota, el objeto servidor tiene que ser registrado mediante el servicio de nombres de RMI (naming/registry service) desde una máquina virtual de Java en ejecución. Un objeto remoto registrado de esta forma solamente está disponible durante el tiempo que permanezca en ejecución la máquina virtual que lo registró. Si el servidor de la máquina virtual termina o se interrumpe por alguna razón, el objeto servidor deja de estar disponible, y cualquier referencia desde un cliente al objeto se convierte en inválida. Cualquier intento de los clientes de llamar a los métodos remotos utilizando estas referencias inválidas generarán una excepción RMI, que será entregada al cliente.
El servicio de activación de objetos permite activar el objeto servidor de forma automática cuando un cliente lo solicita. Esto implica la creación de un objeto servidor desde una máquina virtual existente o nueva, y obtener una referencia a este nuevo objeto para el cliente que provocó la activación. Un objeto servidor que quiera ser activado automáticamente necesita registrar un método de activación a través del demonio de activación de RMI ejecutándose en su host.
Aunque la sintaxis de una llamada a un objeto remoto es idéntica a la de un objeto local, su semántica es algo diferente. Las diferencias semánticas de una llamada remota respecto a una local son las siguientes:
Remote
que éste implementa.RemoteException
.Remote
.equals()
, hashCode()
, y toString()
,
son sobreescritos por java.rmi.RemoteObject
. Así, el método
equals()
comprueba si dos referencias a objetos son iguales,
no si los contenidos son iguales. El método hashCode()
devuelve la misma clave para cualquier referencia al mismo objeto remoto.
El método toString()
se ha modificado para que incluya
información sobre el transporte del objeto, como el protocolo de red
subyacente, nombre del host, y número de puerto de donde proviene
el objeto.Vamos a considerar la implementación de un sencillo servicio echo que utiliza una interfaz intermedia entre el cliente y el servidor, y una clase adicional para crear objetos servidores.
public interface Echo { Object echo(Object object); } class EchoServer implements Echo { public Object echo(Object object) {return object;} } class EchoFactory { public static Echo getEcho() {return new EchoServer();} } class EchoClient { public static void main(String[]args) throws Exception { Echo echo = EchoFactory.getEcho(); System.out.println(echo.echo("Feliz Navidad")); } }
Una versión RMI del servicio echo anterior se consigue realizando los sisguientes cambios:
Echo
extiende java.rmi.Remote
, y
sus métodos lanzan la excepción RemoteException
EchoServer
extiende java.rmi.server.UnicastRemoteObject
EchoServer
tiene un procedimiento main para que pueda
ejecutarse en su propia JVM EchoFactory
ahora obtiene su objeto Echo
via el registro RMI en lugar de instanciar directamente un objeto EchoServer
try...catch
en los métodos main
import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.server.*; import java.rmi.Naming; public interface RemoteEcho extends Remote { Object echo(Object object) throws RemoteException; } class RemoteEchoServer extends UnicastRemoteObject implements RemoteEcho { public RemoteEchoServer() throws RemoteException { } public Object echo(Object object) throws RemoteException {return object;} public static void main(String[] args) throws Exception { String direcc= "//198.162.34.12/:1200"; RemoteEchoServer server = new RemoteEchoServer(); Naming.rebind(direcc+RemoteEcho.class.getName(),server); } } public class RemoteEchoFactory { String direcc="//198.162.34.12/:1200"; public static RemoteEcho getEcho() throws Exception {return (RemoteEcho)Naming.lookup(RemoteEcho.class.getName());} } class RemoteEchoClient { public static void main(String[]args) throws Exception { RemoteEcho echo = RemoteEchoFactory.getEcho(); System.out.println(echo.echo("Feliz Navidad")); } }
La variable direcc
, contiene la dirección IP del servidor,
seguida del número de puerto por el que el servidor estará "escuchando".
Por ejemplo, si la dirección IP es 198.162.34.12 y el puerto es el 1200,
entonces direcc
tomará el valor:
"//198.162.34.12/:1200"
Ambas versiones pueden ejecutarse en una única JVM. La versión RMI puede ejecutarse en una única JVM, en dos JVMs sobre el mismo ordenador, o en dos JVMs en dos máquinas conectadas mediante la red.