RMI: Remote Method Invocation (Invocación Remota de Métodos)

Introducción

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 no es que los objetos se comuniquen a través de la programación del usuario de protocolos estándares de red.   La idea es tener un objeto cliente, donde podamos podamos completar un requerimiento de datos. El cliente luego prepara el requerimiento que envía a un objeto ubicado en un servidor. El objeto remoto prepara la información requerida (accediendo a bases de datos, otros objetos, etc). Finalmente el 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 se puede anhelar 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 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 es 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.

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 de enviar los parámetros, el objeto remoto debe ser activado para ejecutar el método y los valores deben ser retornados de regreso al llamador, ver Figura 1.
Remote Invocation
Figura 1: Invocación remota de objetos

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 cliente al hacer un llamado remoto.
 Stub, Marshalling and Skeleton

Figura 2: Stub, Skeleton y codificación de parámetros y valores retornados

Aún cuando el proceso de la Figura 2 es completo, RMI lo hace en gran medida automático y en gran media transparente para el programador.
La sintaxis de llamados remotos es la misma de los llamados locales.

RMI posee 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 tener 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 jara.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 es el stub ofrecido por RMI el que implementa esta interfaz.

Programación lado Servidor

En el lado del servidor debemos implementar la clase que en efecto efectúa la tarea del método. Para este ejemplo será ProductImpl.java.
public class ProductImpl extends UnicastRemoteObject implements Product  // Las implementaciones deben extender la clase no abstracta UnicastRemoteObject
{
/**
Constructs a product implementation
@param n the product name
*/
public ProductImpl(String n) throws RemoteException {
name = n;
}

public String getDescription() throws RemoteException {
return "I am a " + name + ". Buy me!";
}

private String name;
}
En el caso que la clase servidora ya extienda otra clase, existen formas similares al caso de hebras para no heredar de UnicastremoteObject.

Finalmente para ubicar el objeto remoto, el servidor debe hacerlo disponible al cliente. Para esto RMI provee un mecanismo de registro en la máquina servidora.  El servidor registra objetos usando:
     ....
ProductImpl p1 = new ProductImpl("Blackwell Toaster");
Naming.rebind("toaster", p1);
....
Luego el cliente accede a los objetos registrados usando:
    Product c1 = (Product)Naming.lookup("rmi://yourserver.com/toaster");  // por omisión se contacta el puerto 1099
o
   Product c1 = (Product)Naming.lookup("rmi://yourserver.com:4321/toaster");  
Los códigos para este ejemplo están aquí.

Ejecución del servidor

Primero debemos correr el servicio de registro. En UNIX, o linux, esto se hace así:
agustin@agustin-DELL:~/WWW/elo330/2s09/lectures/RMI/examples$ rmiregistry &
Luego de compilar ejecutamos:
agustin@agustin-DELL:~/WWW/elo330/2s09/lectures/RMI/examples$ 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

Simplificaciones al correr en java 6.0

Desde la versión 5.0, Java trae algunas simplificaciones a la hora de trabajar con RMI. Versiones previas a la 5.0, requieren crear la clase stub para el lado del cliente usando el comando rmic. Este comando ya no está disponible en la versión 6.0, si se requiere compatibilidad con servidores previos a 5.0, ustede debe bajar esta aplicación y correrla para lograr compatibilidad con servicios previos al 6.0.

La interfaz Product.java se mantiene igual, pero hay cambios en el servidor y cliente.  En Java 6.0 podemos implementar la interfaz sin lanzar excepciones, pues éstas no son generadas por nuestra implementación (más lógico). Ver nuevo 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;
}
Al no heredar de la clase UnicastRemoteObject, la creación y registro del skeleton o stub servidor (skeleton o stub) cambian:
         :
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 o skeleton para este objeto.
// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry(); // nueva forma para ubicar el registro de objetos exportables
registry.bind("toaster", stub1); // registramos el stub.
:
Ver ProductServer.java.

Por el lado del cliente el proceso se separa entre contactar el registro remoto y solicitar stubs para cada objeto. Notar que ahora se pasa a través de la línea de comandos la máquina donde corre el servidor.
        :
Registry registry = LocateRegistry.getRegistry(host);
Product stub1 = (Product)registry.lookup("toaster");
Product stub2 = (Product)registry.lookup("microwave");
:
Ver ProductClient.java.
Como antes, cuando el cliente corre en una máquina distinta al servidor, el servidor debe tener un servidor web corriendo.