RMI: Remote Method Invocation (Invocación Remota de Métodos)
Introducción
Antes
de la era de la programación orientada a objetos, se creó RPC (Remote
Procedure Call) para invocar procedimientos cuya implementación se
encuentra en máquinas remotas.
Con la llegada de la Programación Orientada a Objetos se crean esquemas análogos usando objetos. Es así como surge RMI y CORBA.
Java
Remote Method Invocation (RMI) es un sistema que permite a un objeto
que corre en una máquina virtual invocar métodos de un objeto que corre
en otra máquina virtual.
RMI
es una tecnología desarrollada por Sun para permitir la colaboración de
objetos que están localizados remotamente. Esta tecnología se enmarca
en la idea de permitir colaboración entre Objetos Remotos. La idea
es evittar quue los objetos se comuniquen a través de la programación
de protocolos estándares de red (como TCP/IP) hecha por el
usuario. La idea es invocar métodos "locales" cuya implementación es
remota. La infraestructura RMI ofrece los mecanismos para lograr tal
abstracción. Se tiene así un
objeto cliente, donde podemos efectuar un requerimiento a un método.
Ante una solicitud, el soporte RMI del cliente prepara el requerimiento
y lo envía a un objeto
ubicado en un servidor. El objeto remoto prepara la información
requerida (accediendo a bases de datos u otros objetos). Finalmente
el soporte RMI del objeto remoto envía la respuesta al cliente. En
lo posible esta
interacción debería ser lo más semejante posible a requerimientos
hechos localmente.
En
principio desearíamos la colaboración de
objetos escritos en cualquier lenguaje (no es el caso de RMI). Esta
idea no es simple de lograr, corresponde al esfuerzo del grupo OMG
(Object Management Group, www.omg.org) los cuales propusieron CORBA
(Common Object Request Broker Architecture) el cual partió en la década
del 1990. CORBA define un
mecanismo común para descubrir servicios e intercambiar datos. CORBA
usa Object Request Broker (ORB) como traductores universales para la
comunicación entre objetos. Los objetos remotos hablan a través de
estos ORB. El protocolo de comunicación entre objetos y ORB es llamado
Internet Inter-ORB Protocol o IIOP.
La opción propuesta por
Microsoft para comunicar objetos remotos partió con COM (Component Object
Model). Hoy este modelo parece haber sido superado por la tecnología
.NET.
Cuando el cliente y servidor son escritos en Java, la
generalidad y complejidad de CORBA no es requerida. En este caso Sun
desarrolló RMI, un mecanismo más simple especialmente pensado para
comunicación entre aplicaciones Java.
Cuando los objetos reciden en plataformas diferentes (objetos implementados en distintos lenguajes) utilizar la tecnología Java IDL.
Invocación Remota de Objetos (RMI)
La
idea suena simple, si tenemos acceso a objetos en otras máquinas,
podemos llamar a métodos de ese objeto remoto. RMI maneja los detalles
para enviar los parámetros, para activar el objeto remoto, invocar el método y retornar los valores al
llamador, ver Figura 1.
Figura 1: Invocación remota de objetos: visión general
Terminología (ver Figura 2):
- Objeto cliente: objeto cuyo método hace el llamado remoto.
- Objeto servidor: Objeto remoto llamado
Notar
que los roles de cliente y servidor aplican sólo a un llamado. Un
objeto servidor luego puede ser también cliente al hacer otro llamado remoto.- Marshalling: es el proceso de codificación de los parámetros.
- Stub:
es un objeto que encapsula el método que deseamos invocar remotamente.
Así el llamado remoto es semejante a un llamado local. Éste prepara
información con la identificación el objeto remoto a invocar, el
método a invocar y codificación de los parámetros (Marshalling).
- Skeleton:
es el objeto en el lado servidor que decodifica los parámetros, ubica
el objeto llamado, llama el método deseado, codifica el valor
retornado, y envía la información de regreso al stub.
Figura 2: RMI en más detalle: Stub, Skeleton y codificación de parámetros y valores retornados
Aún
cuando el proceso de la Figura 2 es complejo, RMI lo hace en gran
medida automático y en gran medida transparente para el programador.
La sintaxis de llamados remotos es la misma de los llamados locales.
RMI
posee un mecanismo para cargar clases dinámicamente desde otro
lugar. Esto es requerido, por ejemplo, cuando el valor retornado
corresponde a una instancia de una clase derivada de la clase conocida
por el cliente. Aquí se ocupa un mecanismo similar al usado por applets.
Configuración de Invocación Remota de Objetos
El
cliente debe manipular objetos que están en el servidor, para esto el
cliente debe saber qué puede hacer con estos objetos. Esto se expresa
en una interfaz que reside en el cliente y el servidor.
Consideremos
como ejemplo la consulta de la descripción de un producto remoto. Para
esto, ambos lados deben implementar la interfaz Product.java.
public interface Product extends Remote // Notar la interfaz debe extender Remote del paquete java.rmi
{
/**
Gets the description of this product.
@return the product description
*/
String getDescription() throws RemoteException; // notar que todos los métodos remotos deben lanzar esta excepción
}
Se debe lanzar una excepción en todos los métodos para señalar posibles problemas con la comunicación a través de la red.
En el lado cliente el stub ofrecido por RMI implementa esta interfaz.
Programación lado Servidor
En el lado servidor debemos implementar la clase que en efecto efectúa la tarea del método. A partir de En
Java 6.0 la implementación de la interfaz en el servidor sigue el siguiente modelo. Ver
código ProductImpl.java
public class ProductImpl implements Product
{
/**
Constructs a product implementation
@param n the product name
*/
public ProductImpl(String n)
{
name = n;
}
public String getDescription()
{
return "I am a " + name + ". Buy me!";
}
private String name;
}
Finalmente
para que clientes puedan ubicar el objeto remoto, una clase servidora debe dejarlo disponible al
cliente. Para ello debemos crear un programa servidor
encargado de instanciar la clase que implementa el servicio y registrarlo
para su visibilidad remota. Para esto RMI provee un mecanismo de
registro en la máquina
servidora. El servidor registra objetos usando:
:
ProductImpl p1 = new ProductImpl("Blackwell Toaster"); // creamos la instancia de la clase que implementa la interfaz.
Product stub1 =(Product) UnicastRemoteObject.exportObject(p1, 0); // creamos el stub servidor o skeleton para este objeto.
// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry(); // forma para ubicar el registro de objetos exportables
registry.bind("toaster", stub1); // registramos el stub.
:
Luego el cliente accede a los objetos registrados usando:
:
Registry registry = LocateRegistry.getRegistry(host);
Product stub1 = (Product)registry.lookup("toaster");
:
Los códigos para este ejemplo están aquí.
Resumen: crear interfaz, implementar interfaz en servidor, crear programa servidor que registra el servicio, luego es posible
compilar e instalar los servicios para su invocación remotamente.
Ejecución del servidor
Primero debemos correr el servicio de registro. En UNIX, o linux, esto se hace así (en mi caso):
agustin@agustin-DELL:~/WWW/elo330/Java/RMI/Product$ rmiregistry &
Es
muy importante que previo a correr este servidor de registro se
configura la variable CLASSPATH del entorno donde corre el registro. El
CLASSPATH debe contener los directorios donde se encuentren las clases
que implementan las interfaces accesibles remotamente.
Luego de compilar, ejecutamos:
agustin@agustin-DELL:~/WWW/elo330/Java/RMI/Product$ java ProductServer &
[2] 18819
Constructing server implementations...
Binding server implementations to registry...
Waiting for invocations from clients...
Debemos correrlo en trasfondo porque UnicastRemoteObject crea un hilo que queda corriendo.
Para verificar los objetos que tenemos registrados, podemos usar métodos estáticos de la clase Naming, como en ShowBindings.java.
Cuando el cliente corre en una máquina distinta al servidor, el servidor debe tener un servidor web corriendo.
Programación lado Cliente
Para
que no haya problemas de seguridad, el programa cliente debería
instalar un administrador de seguridad para controlar la actividad del
stub cargado dinámicamente. En este caso usamos RMISecurityManager:
System.setSecurityManager(new RMISecuritymanager());
El programa cliente se puede ver en ProductClient.java
Ejecución del Cliente
Para
permitir al cliente conectarse a través de la red, el administrador de
seguridad requiere tales permisos declarados en un archivo de política
(policy file). En éste se debe conceder los permisos para conectarse
los puertos requeridos. Ver client.policy.
grant
{
permission java.net.SocketPermission
"*:1024-65535", "connect,accept";
};
En el programa cliente leemos el archivo de políticas de seguridad usando:
System.setProperty("java.security.policy", "client.policy");
o definiendo la variable java.security.policy en la ejecución:
java -Djava.security.policy=client.policy ProductClient