Una visión general de RMI

“Esto es una traduccion on the fly de http://java.sun.com/docs/books/tutorial/rmi/overview.html”

RMI (Java Remote Method Invocatión) es la tecnología que nos ofrece Java para ejecutar metodos en una maquina virtual remota.

Supongamos que tenemos nuestro programa ejecutándose en nuestro PC Local (Un pentium II 350) y necesitamos realizar llamar a un método que ejecuta un abultado numero de threads para calcular algo y en la oficina es todo maquinaria antigua excepto un UltraSPARC T1  que nos reduce el tiempo de espera en unos cuantos minutos.

En un escenario como el descrito anteriormente RMI se perfila como una alternativa a tener en cuenta.

Las aplicaciones que utilicen objetos remotos necesitan realizar las siguientes acciones:

  • Localizar Objetos Remotos Las aplicaciones pueden utilizar diversos mecanismos para obtener referencias de objetos remotos. Por ejemplo: Una aplicación puede registrar sus objetos remotos con las características del registro de RMI o de forma alternativa una aplicacion puede trabajar con referencias de objetos distribuidos en sus metodos locales.
  • Comunicarse con Objetos Remotos Los detalles de la comunicación entre objetos remotos son gestionadas por RMI, para el programador la comunicación remota actuá como las llamadas clásicas a métodos.
  • Cargar las definiciones de las clases para los objetos que recibe RMI permite que los objetos sean devueltos, por eso incluye mecanismos para cargar definiciones de objetos y transmitir los datos de los mismos.

La siguiente ilustración describe una aplicación distribuida que usa el registro RMI para obtener una referencia a un objeto remoto. El servidor llama al registro para asociar un nombre con un objeto remoto. El cliente busca el objeto remoto en el registro del servidor y despues llama a un metodo del objeto. La ilustración también muestra como RMI utiliza un servidor web existente para obtener las definiciones de las clases. en la comunicación cliente-servidor y viceversa cuando es necesario

esquema RMI

Ventajas de la carga dinámica de código

Una de las características centrales y únicas de RMI es la capacidad de descargar la definición de un objeto si la clase no esta definida en la maquina virtual del receptor. Todo los tipos y comportamientos de un objeto, anteriormente disponibles en una única maquina virtual, pueden ser transmitidos a otra maquina virtual. RMI transporta objetos con sus clases, el comportamiento de sus objetos no cambia cuando es enviada a otra maquina virtual, de esta manera amplia el comportamiento de la aplicación.

Interfaces remotas, objetos y metodos

Como cualquier otra aplicacion en Java una aplicacion distribuida esta compuesta de interfaces y clases. Una interfaz declara metodos, las clases implementan los metodos declarados por la interfaz y puede contener metodos adicionales. En una aplicacion distribuida algunas implementaciones pueden residir en algunas maquinas virtuales pero no en otras. Objetos con metodos que pueden ser invocados entre maquinas virtuales se llaman objetos remotos.

Un objeto se tranforma en remoto implementando la interfaz remote, que tiene las siguientes caracteristicas:

  • Una interfaz remota extiende la interz java.rmi.Remote.
  • Cada método de la interfaz declara java.rmi.RemoteException en su clausulas throws, en adicción a cualquier excepción especifica de la aplicación.

RMI trata un objeto remoto de una forma diferente a un objeto no remoto cuando el objeto es pasado entre maquinas virtuales. mas que hacer una copia de la implementacion del objeto en la maquina virtual receptora, RMI pasa un stub remoto para un objeto remoto. El stub actua como la representacion local, o proxe, para el objeto remoto y para el cliente como una referencia remota. El cliente invoca al metodo desde el stub local el cual es el reponsable de transportar la invocarcion al metodo en el objeto remoto.

Un stub para un objeto remoto implementa el mismo conjuto de interfaces remotas que el objeto remoto. Esto permite que el stub pueda ser modelado (casting) a cualquiera de las interfaces que el objeto remoto implementas. De todas formas, solo los metods definidos en la interfaz remota estan disponibles para ser invocados el la maquina virtual receptora.

Creando Aplicaciones distrribuidas usando RMI

Utilizar RMI para desarrolar una aplicacion distribuida nos obliga a incluir estos pasos generales

  • Diseñar e implementar los componentes de tu aplicación distribuida
  • Compilar los fuentes
  • Hacer las clases accesibles desde la red
  • iniciar la aplicación

Diseñar e implementar los componentes de tu aplicacion distribuida

Primeramente determina la arquitectura de tu aplicación, incluyendo que componentes son objetos locales y que componentes son acesibles de forma remota. Este paso incluye:

  • Definir las interfaces remotas. Una interfaz remota especifica que métodos puede ser invocados por el cliente. Los programas clientes usan esas interfaces remotas y no la implementación de esas interfaces. El diseño de las interfaces incluye la definicion de los tipos de objetos que seran usados como parametros y valores de retorno para esos metodos. Si alguna de esas intarfaces o clases aun no existe tu deberas definirlas.
  • Implementar los objetos remotos. Los bjetos remotos deben implementar una o mas interfaces romatas. La clase del objeto remoto debe incluir las implementaciones de las otras interfaces y metodos que esten disponibles solo de forma local. Si alguna clase local es usada para parametros o valores de retorno estas deben ser implementadas tambien.
  • Implementar los clientes. Los clientes que usan los objetos remotos pueden ser implimentadas en cualquier momentos despues de que las interfaces remotas esten definidas, incluso despues que los objetos remotos hayan sido desarrollados.

Compilar los Fuentes

Como en cualquier programa en Java, tu debes usar el compilador javac para compilar los fuentes, el codigo fuente contiene las declaraciones de las interfaces remotas, sus implementaciones, cualquier otra clase del servidor y los clases del cliente.

nota: Las versiones ateriores a la Hava Standard Edition 5.0, como paso adicional requieren la creacion de los stub usando rmic

Haciendo las Clases accesibles por la Red

En este paso tu haras que ciertas definiciones de clases sean accesibles en la red, como la definicion de interfaces remotas y sus tipos asocidaos, y la definicion para las clases que necesitan ser descargadas para los clientes o servidores. Las difiniciones de clases generalmente se hacen accesibles a traves de un servidor web.

Construyendo un Motor de computo generico

Nos centraremos en una simple, pero poderosa, aplicación distribuida llamada motor de computo. El motor decomputo es un objeto remoto en el servidor que toma tareas de los clientes, ejecuta las tareas y retorna los resultados. Las tareas estan corriendo en la misma maquina que el servidar. Este tipo de aplicaciones distribuidas puede activar un numero de maquinas cliente para hacer uso de una maquina particularmente potente o con hardware especializado.

Los aspectos noveles del motor de computo son que las tareas que ejecuta no necesitan ser definidas cuando el motor de computo esta escrito o iniciado. Nuevos tipos de tareas pueden ser creados en cualquier momento y después enviadas al motor de computo. El único requerimiento de las tareas es que sus clases implemente una interfaz en particular. El código necesario para cumplir la tarea puede ser descargado por el sistema RMI al motor de computo. Entonces, el motor de computo ejecuta la tarea usando los recursos de la maquina donde ser esta ejecutado dicho motor.

La capacidad para realizar tareas arbitrarias esta disponible por la naturaleza dinámica de la plataforma java, que es extendida a traver de la red gracias a RMI. RMI caga de forma dinámica el código de la tarea en el motor de computo de la maquina virtual de java y ejecuta la tarea sin conocimiento previo de la implementacion de la tarea. Como las aplicaciones  con la capacidad de descargar el codigo de forma dinamica, tambien llamadas aplicaciones basadas en comportamientos, estas aplicaciones requeres una infraestructura de agentes activos. Com RMI estas aplicaciones son parte del mecanismo basica para computacion distribuida de la plataforma java.

Escribiendo un servidor RMI

El servidor del motor de computo acceta tareas de los clientes, ejecuta la tarea y devuelve los resultados, el codigo del servidor consuste en una interfaz y una clases. La interfaz define los metodos que pueden ser invocados por el clientes. Esencialmente lainterfaz define la vista de los objetos remotos para los clientes, la clases nos provee de las implementaciones.

Diseñando Una Interfaz remota

El corazon del motor de computacion es un protocolo que permite que las tareas sean enviadas al mismo, el motor de computo ejecuta esas tareas y el resultado de la ejecución es enviado al cliente. Este protocolo esta introducido en las interfaces que son soportadas por el motor de computo.

Comunicación RMI

Cada interfaz contiene un único método. La interfaz remota del motor de computo, Compute, permite a las tareas ser enviadas al motor. La interfaz cliente, Task, define como el motor de computo ejecuta una tarea enviada.

La interfaz compute.Compute define la parte accesible de forma remota, el motor de computo en si mismo, Aqui pongo el codigo de la interfaz Compute:

package compute;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Compute extends Remote {
<T> T executeTask(Task<T> t) throws RemoteException;
}

Al extender la interfaz java.rmi.Remote, la interfaz compute se identifica como una interfaz cuyos métodos pueden ser invocados desde otra maquina virtual. Cualquier objeto que implementa esta interfaz puede ser un objeto remoto.

Como miembro de una interfaz remota, el método executeTask es un método remoto. De todas formas este método debe ser definido para ser capar de ignorar una java.rmi.RemoteException. Esta excepción es ignorada por el sistema RMI en la invocación de métodos remotos para indicar fallos de comunicación o errores del protocola. Una RemoteExcepcion es una excepción comprobada, pero cualquier código que invoque a un método remoto necesita manejar esta excepción con un catch o con una cláusula throws.

La segunda interfaz necesaria para el motor de computo es la interfaz Task, que es el tipo de parametro del metodo executeTask en la interfaz compute. Aqui teneis es codigo de la interfaz Task

package compute;

public interface Task<T> {
T execute();
}

La interfaz task define un único método, execute, que no tiene parámetros ni ignora ninguna excepción. Porque la interfaz no extiende a Remote y el método de esta interfaz no necesita ignoros la excepción java.rmi.RemoteException en su cláusula throws.

La interfaz Task tiene un tipo de parámetro T, que representa el tipo de resultado de las tareas a computar. Esta interfaz ejecuta métodos que devuelven el resultado del computo.

RMI usa los mecanismos de serializacion de objetos para transportar los objetos por valor entre maquinas virtuales, Para que un objeto pueda ser serializables, su definición debe implementar la interfaz java.io.Serializable. Por lo tanto las clases que implementen la inteaz Task tambien deben implementar la interfaz Serializable.

Los diferentes tipos de tareas que pueden ser ejecutadas por el Objeto Compute son implemntaciones del tipo Task. Las clases que son implementads por esta interfaz pueden contener cualquier informacion necesitada para la ejecucion de la tarea y cualquier otro metod necesario para la ejecución.

Esta es la forma por la cual RMI hace que este simple motor de computo posible. RMI asume que los objetos Task estan escritos en Java, las implementaciones de los objetos Task que eran desconocidos para el motor de computo son obtenidas por RMI baja demanda, esta capacidad permite a los clientes del motor de computo definir nuevos tipor de tareas para ejecutar en el servidor sin la necesidad de especificar nada en el codigo de la parte del servidor.

El motor de computo implementado por la clase ComputeEngine iplementa la interfaz Compute, permitiendo que distintas tareas sean enviadas a el cn llamadas al metodo executeTask. estas tareas se ejecutan usado de implementacion de Task del metodo execute y los resultados son enviados al cliente remoto.

Implementando una interfaz Remota

Una clase que implementa una intergaz emoto necesida hacer lo siguiente:

  • Declarar las interfaces remotas que implementara
  • Definir los constructores para cada objeto remoto
  • Implementar cada metodo remoto de las interfaces remotas

Un servidor RMI necesita crear los objetos remotos iniciales y exportarlos al entrono de ejecución RMI, lo que les permite al servidor recibir invocaciones remotas. Este paradigma de funcionamiento puede ser encapsulado en un metodo de la implementación del objeto remoto o ser incluida en otra clase. El paradigma de funcionamiento debe realizar la siguientes acciones:

  • Crear e instalar un gestor de seguridad
  • Crear y exportar uno o mas objetos remotos
  • Registrar al menos un objeto remoto con el registro RMI (o con otro servicio de nombe, como un sercio accesible a traves la interfaz Java Namin and Directory) como metodo de arranque.

A continuacion ponemos la implementacion del motor de computo. La clase engine.ComputeEngine implementa la interfaz remota Computo y tambien incluye el metodo main para iniciar el motor de computo. Aqui teneis el codigo fuentepara la clase ComputeEngine:

package engine;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import compute.Compute;
import compute.Task;

public class ComputeEngine implements Compute {

    public ComputeEngine() {
        super();
    }

    public <T> T executeTask(Task<T> t) {
        return t.execute();
    }

    public static void main(String[] args) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new SecurityManager());
        }
        try {
            String name = "Compute";
            Compute engine = new ComputeEngine();
            Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(name, stub);
            System.out.println("ComputeEngine bound");
        } catch (Exception e) {
            System.err.println("ComputeEngine exception:");
            e.printStackTrace();
        }
    }
}

A continuación explicaremos cada elemento de la implementacion del motor de computo.

Declarando las interfaces remotas que serán implementadas.

La implementacion del motor de computo es declarada:

public class ComputeEngine implements Compute

Esta declaracion indica que implementamos la interfaz remota Computo y que pude ser usada par los objetos remotos.

La clase ComputeEngine define una implementacion de la clase del objeto remoto que implementa una unica interfaz remota. La clase ComputeEngine tambien contiene dos elementos executables que solo pueden ser invocados de forma local. El primero de esos elementos es el contructor para las instancias de ComputeEngine. El segundo es el metodo main que se usa para clerar la instacion de ComputeEngine que  hace que este disponible para los clientes.

Definiendo el constructor para un objeto remoto.

La clase ComputeEngine tiene un unico contructor que no toma ningun argumento. El codigo del constructor esta a continuación:

public ComputeEngine() {
    super();
}

Este constructor solo invoca al constructor de la superclase, el cual es un constructor sin argumentos de la clase Object. De todas formas el constructor de la superclase es invocado si fuera omitido por el constructor ComputeEngine, esto esta incluido para facilitar la comprension del codigo.

Creando las implementaciones para cada método remoto.

La clase para un objeto remoto implementa cada metodo remoto de la interfaz remota. La interfaz Compute contiene un unico metodo, executeTask, implementacion de la cual se muestra  a continuación

public <T> T executeTask(Task<T> t) {
    return t.execute();
}

Este metodo implementa el protocolo entre el objeto remoto ComputeEngine y sus clientes, ComputeEngine recibe de cada cliente un objeto Task con su implementacion para la interfaz Task y su metodo de ejecución. El ComputeEngine ejecuta cada tarea del cliento y devielve lo resultados de la ejecucion al cliente.

Pasando Objetos en RMI

Los argumentos o valores de retorno de los métodos remotos pueden ser de cualquier tipo, incluyendo objetos locales, objetos remotos y tipos de datos primitivos. De forma mas preciso cualquier entidad de cualquier tipo puede ser pasada o recibida desde un objeto remoto, un objero remoto, o un objeto serializable (lo que significa que implementa la interfaz java.io.Serializable).

Algunos tipos de objeto no cumple alguno de estos criterios y por lo tanto no pueden ser pasados o recibidos por un método remoto, Muchos de esos objetos, como threads o descriptos de archivos, encapsulan información que solo tiene sentido en un único espacio de memorio, Muchas de las clases del núcleo de java incluyendo las clases en los paquetes java.lang y java.util implementan la interface Serializable.

Las reglas que rigen las condiciones para que los argumentos o valores de retorno sean validos son las siguientes:
*Los objetos remotos se pasa esencialmente por referencia. Una referencia a un objeto remote es un stub, que es un proxy del lado del cliente que implementa el conjunto completo de interfaces que implementa un objeto remoto.
*Los objetos locales son pasados como una copia, usando serializacion de objetos. Por defecto , todos los campos son copiados excepto los que están marcados como static o transient, El comportamiento predeterminado de la serializacion pude ser sobrescrito.

Pasar un objeto remoto por referencia significa que cualquier cambio realizado en el estado del objeto por la invocación remota queda reflejado en lo objeto remoto original. Cuando un objeto remoto es pasado, solo las interfaces que son interfaces remotas están disponibles para el receptor. Cualquier motoso definido en la implementación de la clase o definido en las interfaces no remotas implementadas por la clase no estan disponibles para el receptor.

Por ejemplo, if estuvieramos pasando una referencia a una instancia de la clase ComputeEngine, el receptor solo tendria acceso al metodo executeTask. El receptor no veria el constructor de ComputeEngine, su metodo main o sun implementaciones de cualquier metodo de java.lang.Object.

En los parámetros y valores de retorno de las invocaciones remotas a metodos, los objetos que no son objetos remotos son pasados por valor, osea , una copia del objeto es creada en la maquina virtual receptora. Cualquier cambio en el estado del objeto por el receptor se ve reflejado unacamente en la copia del receptor, no en la instancia original enviada. Cualquier cambio en el estado del objeto realizado por el enviador se ve reflejado solo en la instancia original del envio, no en la copia del receptor.

Implementando el metodo main del Servidor

El metodo mas complejo de la implementacion de ComputeEngine es el metodo main. El metodo main es usado para iniciar el motor de computo y necesita realizar la inicializacion para preparar el servidor para aceptar llamadas de los clientes. Este metodo no es un metodo remoto, lo que significa que no puede ser invocado por una maquina virtual diferente. A causa que que el metodo main esta declarado como static, el metodo no esta asociado con el objeto o mejor dicho con la clase ComputeEngine.

Creando e instalando un gestor de seguridad

La primera tarea del metodo main es crear e instalar un gestor de seguridad (Security Manager), ue protege el aceso a los recursos del sistema de codigo no verificado que transcurre entre maquinas virtuales. Un gestor de seguridad determina que codigo descargado tiene acceso al sistema de archivos locales o puede realizar cualquier otra operación privilegiada.

Si un programa RMI no instala un gestor de seguridad, RMI no descargara clases (que no esten en su class path) para los objetos recibidos como argumentos o valores de retorno de los metodos remotos. Esta restricción nos asegura que las operaciones realizadas con condigo descargado estan sujetas a una politica de seguridad.

Aqui esta el codigo que crea e instala un gestor de seguridad:

if (System.getSecurityManager() == null) {
 System.setSecurityManager(new SecurityManager());
}

Haciendo los objetos remotos disponibles para los clientes

El metodo main clea una instancia de ComputeEngine y lo exporta para el entono de ejecucion de RMI con las sigioentes lineas


Compute engine = new ComputeEngine();
Compute stub =

(Compute) UnicastRemoteObject.exportObject(engine, 0);

El metodo estatico UnicastRemoteObject.exportObject exporta el objeto remodo pasado como parametro para que pueda recibir invocaciones de sus metodos remotos a traves de clientes remotos. El segundo argumento, un int, especifica que puerto TCP sera usado para escuchar solicitudes para las invocaciones remotas. Generalmente el valor es cero, que especifica el uso de un puerto anónimo. El puerto actual sera seleccionado por RMI en tiempo de ejecución o por el sistema operativo. De todas formas un valor distinto a  cero puede ser usado para especificar el puerto de escucha. Una vez que la invocación  a exportObject se ha realizado de forma satisfactoria, el objeto remoto ComputeEngine esta listo para recibir invocaciones remotas.

El metodo exportObject devuelve un stub del objeto remoto esportado. Fijate que el tipo de variable stub debe ser Compute, no ComputeEngine, porque el stub de un objeto remoto solo implementa las interfaces remotasde los objetos remotos exportados implementados.

El exportObject motodo declara que puede ignorar una RemoteException, que es un tipo de excepcion comprobada. El metodo main maneja esta exception con sus bloques try/catch. Si la excepcion no esta manejada de esta manera el metodo main debera ignorar (throws) la RemoteException si lo recursos necesarios no estan disponibles, como que el puerto este ocupado para otros objetivos.

Antes de que el cliente pueda invocar un metodo de un objeto remoto, primero se debe obtener una referencia al objeto remoto.  Para obtener esta referencian se puede hacer de la misma forma por la que cualquier referenca a un objeto es obtenida por el programa, como obtener l referencia como parte de un valor de retorno o como parte de una estructura de datos que contiene dicha referencia.

El sistema nos provee de un tipo de objeto remoto particular, el registro RMI, para encontrar referencias a otros objetos remotos. El registro RMI es un servicio de nombres de objeto que permite a los clientes obtener una referencia a un objeto remoto por su nombre. El registro es usado generalmente para encontrar el primer objeto remoto que el cliente RMI necesita usar. Ese primer objeto puede ser usado para encontrar otros objetos.

La interfaz java.rmi.registry.Registry es el API para registrar y buscar objetos remotos en el registro. La clase java.rmi.registry.LocateRegistry contiene metodos estaticos para sintetizar una referencia remota a un registro en una direccion de red particular (host y puerto). Estos metodos crean la referencia remota a un objeto que contiene la direccion de red pero sin realizar ninguna comunicacion remota. LacaleRegistry tambien contiene metodos estaticos para crear un nuevo registro en la maquina virtual actual, de todas formas este ejemplo no usa esos metodos. Una vez que el objeto remoto esta registrado en el registro RMI del host local, los clientes de cualquier host puede bscar el objeto remoto por su nombre, obteniendo ss referencias y luego invocar los metodos remotos del objeto. El registro puede ser compartido por todos los servidores de un host, o un unico proceso servidor puede crear su propio registro.

La clase ComputeEngine crea un nombre para el objeto con la siguiente linea:

String name = "Compute";

Este codigo añade el nombre al registro RMI del servidor. Este para se reliza antes de los siguientes comando

Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);

La llamada a rebind realiza una llamada al registro RMI del host local. Como cualquier llamada remota, esta puede generar una RemoteExceptionque sera manejada en bloque catch del metodo main.

Ten en cuenta las siguientes condiciones el las llamadas a Registry.rebind:

  • La sobrecarga sin argumentos a LocateRegistry.getRegistry sintetiza una referencia a iun registroen el host local y en su puerto predeterminado,1099. Tu debes usar una sobrecarga que tiene un parametro int si el registro esta creado en un puerdo distinto al 1099.
  • Cuando se realia una invocacion al registro, se pasa un stub del objeto remoto en lugar de una copia del objeto remoto. Las implementaciones de objetos remotos, como las instancias de ComputeEngine, nunca abandonaran la maquina virtual donde fueron creadas. Asi que, cuando un cliente realiza una busqueda en un servidor de registro de objetos remotos, una copia del stub (boceto) es devuelta. Los objetos remotos generalente son mas efecientes pasados como una referencia remota en lugar que por valor
  • Por razones de seguridad, una aplicacion solo puede realizar operaciones bind, unbind o rebind con referencias a objetos con un registro ejecutandosse en el mismo host. Esta restriccion nos ayuda a evitar que un cliente remoto borre o sobreescriba alguna estrada del servidor de registro. De todas formas una lookup (busqueda), puede ser solicitada por cualquier host, local o remoto.

Una vez que el servidor ha sido registrado con el registro local RMI, muestra un mensaje indicando que esta listo para empezar a manejar llamadas. Entonces, el metodo main se completa. No es necesario usar threads para mantener el servidor manejando escuchas.Mientras exista referencia al objeto ComputeEngine en otra maquina virtual, local o remota, el objeto ComputeEngine no se destruirá ni le afectara el recolector de basura, Porque el programa maneja una referencia a ComputeEngine que esta en el registro, que es accesible por el cliente remoto. El sistema RMI mantiene mantiene los procesos ComputeEngine ejecutandose. El ComputeEngine esta disponible para aceptar llamadas y no será utilizado hasque que su enlace sea eliminado por el registro y los clientes no remotos rompan la referencia remota al objeto CompteEngine.

La ultima porción de código en el método  ComputeEngine.main manejo cualquier posible excepción. El único tipo de excepción comprobada que puede ser ignorada en el código es la RemoteException, a través la llamada a UnicastRemoteObject.exportObject o por la llamada al método rebind. De cualquier forma, el programa no puede hacer mucho mas que salir después de imprimir un mensaje de error. En algunas aplicaciones distribuidas es posible recuperarse del fallo para realizar llamadas remotas. Por ejemplo, la aplicación podría hacer un reintento o seleccionar otro servidor para continuar funcionando.

Creando un programa Cliente

El motor de computo (compute engine) es un programa relativamente simple. Ejecuta tareas que son manejadas para eso. Los clientes de motor de computo son mas complejos. Un cliente necesita llamar al motor de computo, pero también tiene que definir las tareas que se ejecutarán en el motor de computo.
Dos clases separadas forman el cliente de nuestro ejemplo. La primera clase, ComputePi, busca e invoca un objeto Compute. La segunda clase, Pi, implementa la interfaz Task y define el trabajo para ser realizado por el motor de computo. El trabajo de la clase Pi es computar el valor de algunos decimales de Pi .

La interfaz no remota Task esta definida a continuación:

package compute;

public interface Task<T> {
 T execute();
}

Aquí estas el código fuente para cliente-ComputePi (link) :

<pre>package client;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.math.BigDecimal;
import compute.Compute;

public class ComputePi {

 public static void main(String args[]) {
 if (System.getSecurityManager() == null) {
 System.setSecurityManager(new SecurityManager());
 }
 try {
 String name = "Compute";
 Registry registry = LocateRegistry.getRegistry(args[0]);
 Compute comp = (Compute) registry.lookup(name);
 Pi task = new Pi(Integer.parseInt(args[1]));
 BigDecimal pi = comp.executeTask(task);
 System.out.println(pi);
 } catch (Exception e) {
 System.err.println("ComputePi exception:");
 e.printStackTrace();
 }
 }
}

Igual que el servidor ComputeEngine, el cliente comienza instalando un gesto de seguridad. Este paso es necesario porque durante el proceso de recibir los borradores (stub) de los objetos remotos del servidor puede ser necesario descargar las definiciones de clases del servidor. Para que RMI descargue clases el gesto de seguridad debe ser forzado.
Después de instalar el gestor de seguridad, el cliente construye un nombre para realizar las búsquedas de los objetos remotos Compute, usando el mismo nombre que usa ComputeEngine para unirse con el cliente. También el cliente usa la API LocaleRegistry.getRegistry para sintetizar una referencia remota para registras el host remoto. El valor de primer argumento de los argumentos pasados por consola, args[0], es el nombre del host remoto en el que se ejecuta el objeto Compute. Entonces el cliente invoca al método lookup en el registro para buscar el objeto remoto por nombre en el registro del host servidor. La sobrecarga particular de LocateRegistry.getRegistry  que solo tiene un parámetro una String, devuelve una referencia al registro del host remoto en el puerto 1099. Si el puerto del servidor es distinto al 1099 debes usar la sobrecarga que tiene un parámetro int.

A Continuación el cliente crea un nuevo objeto Pi, pasando al constructor de pi el valor del segundo argumento de los argumentos de la linea de comandos, args[1], pasada como un entero. Este argumento indica el numero de valores decimales para ser calculados. Finalmente el cliente invoca al método executeTask recibiendo un objeto del tipo BigDecimal, que el programa almacena en la variable result. El siguiente gráfico describe el flujo entre el cliente ComputePi, el rmiregistry (registro mi) y el ComputeEngine (motor de computo).

Flujo elementos RMI

La clase Pi implementa la interfaz Task y calcula el valor de un numero de decimales de Pi. Para este ejemplo, el algoritmo no es importante. Lo que si es importante del algoritmo es su coste computacional lo que significa que se deberá ejecutar en un servidor capacitado.
Aquí esta el código fuente para el cliente.Pi la clase que implementa la interfaz Task:

</pre>
package client;
import compute.Task;

import java.io.Serializable;

import java.math.BigDecimal;
public class Pi implements Task<BigDecimal>, Serializable {
 private static final long serialVersionUID = 227L;
 /** constants used in pi computation */

private static final BigDecimal FOUR =

BigDecimal.valueOf(4);
 /** rounding mode to use during pi computation */

private static final int roundingMode =

BigDecimal.ROUND_HALF_EVEN;
 /** digits of precision after the decimal point */

private final int digits;
 /**

* Construct a task to calculate pi to the specified

* precision.

*/

public Pi(int digits) {

this.digits = digits;

}
 /**

* Calculate pi.

*/

public BigDecimal execute() {

return computePi(digits);

}
 /**

* Compute the value of pi to the specified number of

* digits after the decimal point.  The value is

* computed using Machin's formula:

*

*          pi/4 = 4*arctan(1/5) - arctan(1/239)

*

* and a power series expansion of arctan(x) to

* sufficient precision.

*/

public static BigDecimal computePi(int digits) {

int scale = digits + 5;

BigDecimal arctan1_5 = arctan(5, scale);

BigDecimal arctan1_239 = arctan(239, scale);

BigDecimal pi = arctan1_5.multiply(FOUR).subtract(

arctan1_239).multiply(FOUR);

return pi.setScale(digits,

BigDecimal.ROUND_HALF_UP);

}

/**

* Compute the value, in radians, of the arctangent of

* the inverse of the supplied integer to the specified

* number of digits after the decimal point.  The value

* is computed using the power series expansion for the

* arc tangent:

*

* arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 +

*     (x^9)/9 ...

*/

public static BigDecimal arctan(int inverseX,

int scale)

{

BigDecimal result, numer, term;

BigDecimal invX = BigDecimal.valueOf(inverseX);

BigDecimal invX2 =

BigDecimal.valueOf(inverseX * inverseX);
 numer = BigDecimal.ONE.divide(invX,

scale, roundingMode);
 result = numer;

int i = 1;

do {

numer =

numer.divide(invX2, scale, roundingMode);

int denom = 2 * i + 1;

term =

numer.divide(BigDecimal.valueOf(denom),

scale, roundingMode);

if ((i % 2) != 0) {

result = result.subtract(term);

} else {

result = result.add(term);

}

i++;

} while (term.compareTo(BigDecimal.ZERO) != 0);

return result;

}

}
<pre>

Notese que todas las clases serializables, aquellas que implementan las interfaces serializable directa o indirectamenta, deben declarar un campo private static final llamado seriaVersionUID para garantizar la compativilidad entre versiones. Si no en existe una versión previa de la clase, entonces el valor de este campo puede ser cualquier valor long, similar al de 227L usado por pi, segun la longitud del valor sera usado en versiones futuras. Si existiese una versión previa de la clase si una declaracion explicita de serialVersionUID , pero la compatibilidad con la versión anterior es importante, entonce el valor implícito predeterminado para computar sera el que debera ser usado para el valor de la nueva versión. La herramienta serialver puede determinar el valor predeterminado a usar.

La caracteristica mas importante de este ejemplo es que la implementación del objeto value nunca necesita la definición de la clase Pi hasta que el objeto es pasado como argumento al metodo executeTask. En ese punto, el codigo de la clase es cargado por RMI en la maquina virtual del Objeto Compute, el metodo execute es invocado, y el codigo de la tarea es ejecutado. El resultado, que en el caso de la tarea Pi es un objeto BigDecimal, es retornado al cliente que realizao la llamado, donde es usado para imprimir el resultado del computo.

El hecho de que el objeto Task  compute el valor de Pi es irrelevante para el objeto ComputeEngine. Tu tambien podrias implementar una task que, por ejemplo, generase un numero primo aleatoria. Cualquier tarea podria se intensiva desde el punto de vista computacional  seria una buena candidata para el ComputeEngine, pero requeriria un codigo muy diferente. Este codigo tambien podria ser descargado cuando un objeto Task es pasado a otro Compute. a causa de que el objeto Compute no necesita saber lo que la implementación de Task hace.

proximo post (como poner todo esto en marcha)