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 (Remote Method Invocation) y CORBA (Common Object Request Broker Architecture).
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 evitar que los objetos se comuniquen a través de la programación de protocolos estándares de redes (como TCP/IP). 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, al cual le podemos invocar métodos. Ante tal invocación, el soporte RMI del objeto cliente prepara el requerimiento y lo envía a un objeto ubicado en un servidor. El objeto remoto ejecuta en método invocado corriendo código que él mantiene. Finalmente el soporte RMI del objeto remoto envía los valores retornadoos al cliente con el mismo efecto de una invocación con implementación local. 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 ha sido superado por la tecnología .NET en particular Windows Communication Foundation (or WCF).
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.
Si bien este tipo de tecnologías ha cambiado en el tiempo (desde RPC hasta las que hoy se ofrecen), los problemas que RMI atiende aquí también son abordados por las tecnologías actuales. El entendimiento de qué problemas se resuelven y la forma cómo lo hace RMI es el principal aporte de esta sección.

Invocación Remota de Objetos (RMI)

La idea suena simple, si hay objetos en otras máquinas, deseamos 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.
Remote Invocation
Figura 1: Invocación remota de objetos: visión general

Terminología
(ver Figura 2):
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.
 Stub, Marshalling and Skeleton

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.

En un programa Java estándar los objetos son típicamente creados en tiempo de ejecución. En este caso el objeto con la implementación en el lado servidor ddebe existir previo a la ejecución del cliente.  Para que el cliente pueda vincular su llamado a un objeto específico en el servidor,  RMI cuenta con un "directorio" donde se registran los objetos disponibles en el servidor. Entonces, para acceder a un método remoto: a) el servidor debe crear un objeto "exportable", b) el servidor debe registrar el objeto en el "directorio" o servicio de registro, c) el cliente debe saber dónde está el "directorio" de objetos exportados.

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 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 (en el "directorio"). 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(); // Con esto ubicamos el registro de objetos exportables
registry.bind("toaster", stub1); // registramos el stub o skeleton.
:
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 remota.

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/Productv6.0/server$ rmiregistry &
Es muy importante que previo a correr este servidor de registro se configure 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 la aplicación que implementa los servicios que pueden ser accedidos remotamente:
agustin@agustin-DELL:~/WWW/elo330/Java/RMI/Productv6.0/server$ 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 permisos declarados en un archivo de política (policy file). Éste debe conceder los permisos para conectarse a 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
Luego de ocuparse de estas tareas se tiene:
agustin@agustin:~/WWW/elo330/Java/RMI/Productv6.0/client$ java ProductClient
I am a Blackwell Toaster. Buy me!
I am a ZapXpress Microwave Oven. Buy me!
agustin@agustin:~/WWW/elo330/Java/RMI/Productv6.0/client$