Manual - Programacion - Java - Rmi
Manual - Programacion - Java - Rmi
Manual - Programacion - Java - Rmi
Indice de contenidos
● Trabajar con RMI
❍ Arrancar la Aplicación.
● Ejecutar el Ejemplo
❍ Una Nota sobre la Seguridad
❍ Arrancar el Servidor
❍ Arrancar el Cliente
Leer comentarios (0) | Escribir comentario | Puntuación: (1 voto) Vota
TutorJava recomienda...
En esta página:
● Trabajar con RMI
RMI proporciona comunicación remota entre programas escritos en Java. Si unos de nuestros
programas está escrito en otro lenguaje, deberemos considerar la utilización de IDL en su lugar.
Esta sección ofrece una breve descripción del sistema RMI que pasea a través de un ejemplo
completo cliente/servidor que utiliza la capacidades únicas de RMI para cargar y ejecutar tareas
definidas por el usuario en tiempo de ejecución. El servidor del ejemplo implementa un motor de
cálculo general. El cliente utiliza el motor de cálculo para calcular el valor del número pi.
Introducción a las Aplicaciones RMI Describe el sistema RMI y lista sus ventajas. Además, esta
lección proporcionar una descripción de una aplicación típica de RMI, compuesta por un servidor y
un cliente, y presenta los términos importantes.
Escribir un Servidor RMI Muestra el código del servidor del motor de cálculo. A través de este
ejemplo, aprenderemos cómo diseñar e implementat un servidor RMI.
Crear un Programa Cliente Echa un vistazo a un posible cliente del motor de cálculo y lo utiliza
para ilustrar las características importantes de un cliente RMI.
Compilar y Ejecutar el Ejemplo Muestra cómo compilar y ejecutar tanto el servidor del motor de
cálculo como su cliente.
TutorJava recomienda...
En esta página:
● Introducción a las Aplicaciones RMI
❍ Ventajas de la Carga Dinámica de Código
❍ Interfaces, Objetos y Métodos Remotos
❍ Crear Aplicaciones Distribuidas utilizando RMI
❍ Diseñar e implementar los componentes de nuestra aplicación distribuida.
❍ Compilar los Fuentes y Generar stubs.
❍ Hacer accesibles las Clases en la Red.
❍ Arrancar la Aplicación.
❍ Construir un Motor de Cálculo Genérico
Un stub para un objeto remoto implementa el mismo conjunto de interfaces remotos que el objeto
remoto. Esto permite que el stub sea tipado a cualquiera de los interfaces que el objeto remoto
implementa. Sin embargo, esto también significa que sólo aquellos métodos definidos en un
interface remoto están disponibles para ser llamados en la máquina virtual que lo recibe.
Arrancar la Aplicación.
Arrancar la aplicación incluye ejecutar el registro de objetos remotos de RMI, el servidor y el cliente.
El resto de este capítulo muestra cómo seguir estos pasos para crear un motor de cálculo.
El aspecto novedoso del motor de cálculo es que las tareas que ejecuta no necesitan estar definidas
cuando se escribe el motor de cálculo. Se pueden crear nuevas clases de tareas en cualquier
momento y luego entregarlas el motor de cálculo para ejecutarlas. Todo lo que una tarea requiere
es que su clase implemente un interface particular. Por eso una tarea puede ser enviada al motor
de cálculo y ejecutada, incluso si la clase que define la tarea fue escrita mucho después de que el
motor de cálculo fuera escrito y arrancado. El código necesita conseguir que una tarea sea
descargada por el sistema RMI al motor de cálculo, y que éste ejecute la tarea utilizando los
recursos de la máquina en la que está ejecutando el motor de cálculo.
La habilidad para realizar tareas arbitrarias esta permitida por la naturaleza dinámica de la
plataforma Java, que se extiende a través de la red mediante RMI. El RMI carga dinámicamente el
código de las tareas en la máquina virtual del motor de cálculo y ejecuta la tarea si tener un
conocimiento anterior de la clase que implementa la tarea. Una aplicación como ésta que tiene la
habilidad de descargar código dinámicamente recibe el nombre de "aplicación basada en
comportamiento". Dichas aplicaciones normalmente requieren infraestructuras que permitan
agentes. Con RMI, dichas aplicaciones son parte del macanismo básico de programación distribuida
de Java.
TutorJava recomienda...
En esta página:
● Escribir un Servidor RMI
Diseñar un Interface Remoto Esta página muestra cómo el interface Compute es el pegamento que
conecta el cliente y el servidor. También aprenderemos sobre el API de RMI que soporta esta
comunicación.
Implementar un Interface Remoto En esta página exploraremos la clase que implementa el interface
Compute, que implementa un objeto remoto. Esta clase también propociona el resto del código que
configura el programa servidor: un método main que crea un ejemplar del objeto remoto, lo
registra con la facilidad de nombrado, y configura un controlador de seguridad.
TutorJava recomienda...
En esta página:
● Diseñar un Interface Remoto
Cada uno de los interfaces contiene un sólo método. El interface del motor de cálculo Compute,
permite que los trabajos sean enviados al motor, mientras que el interface Task define cómo el
motor de cálculo ejecuta una tarea enviada.
El interface compute.Compute define la parte accesible remotamente - el propio motor de cálculo.
Aquí está el interface remoto con su único método.
package compute;
import java.rmi.Remote;
import java.rmi.RemoteException;
El segundo interface necesitado por el motor de cálculo define el tipo Task. Este tipo es utilizado
como un argumento del método executeTask del interface Compute. El interface compute.Task
define el interface entre el motor de cálculo y el trabajo que necesita hacer, proporcionando la
forma de iniciar el trabajo.
package compute;
import java.io.Serializable;
Se pueden ejecutar diferentes tipos de tareas en un objeto Compute siempre que sean
implementaciones del tipo Task. Las clases que implementen este interface pueden contener
cualquier dato necesario para el cálculo de la tarea, y cualquier otro método necesario para ese
cálculo.
Así es cómo RMI hace posible este sencillo motor de cálculo. Como RMI puede asumir que los
objetos Task están escritos en Java, las implementaciones de los objetos Task que anteriormente
eran desconocidas para el motor de cálculo son descargadas por el RMI dentro de la máquina virtual
del motor de cálculo cuando sea necesario. Esto permite a los clientes del motor de cálculo definir
nuevos tipos de tareas para ser ejecutadas en el servidor sin necesitar que el código sea instalado
explícitamente en dicha máquina. Además, como el método executeTask devuelve un
java.lang.Object, cualquier tipo de objeto Java puede ser pasado como valor de retorno en una
llamada remota.
En esta página:
● Implementar un Interface Remoto
❍ Declarar los Interfaces Remotos que están siendo Implementados
❍ Definir el Constructor
❍ Proporcionar una Implementación para cada Método Remoto
❍ Pasar Objetos en RMI
❍ El método main() del Servidor
❍ Crear e Instalar un Controlador de Seguridad
❍ Poner el Objeto Remoto a Disposición de los Clientes
package engine;
import java.rmi.*;
import java.rmi.server.*;
import compute.*;
UnicastRemoteObject es una clase de conveniencia, definida en el API público del RMI, que puede ser utilizada
como superclase para la implementación de objetos remotos. La superclase UnicastRemoteObject suministra
implementación para un gran número de métodos de java.lang.Object (equals, hashCode, toString) para que
estén definidos apropiadamente para objetos remotos. UnicastRemoteObject también incluye constructores y
métodos estáticos utilizados para exportar un objeto remoto, es decir, hacer que el objeto remoto pueda recibir
llamadas de los clientes.
Una implementación de objeto remoto no tiene porque extender UnicastRemoteObject, y ninguna implementación
que lo haga debe suministrar las implementaciones apropiadas de los métodos de java.lang.Object. Además, una
implementación de un objeto remoto debe hacer una llamada explícita a uno de los métodos exportObject de
UnicastRemoteObject para que el entorno RMI se de cuenta del objeto remoto para que éste pueda aceptar
llamadas.
Al extender UnicastRemoteObject, la ComputeEngine puede ser utilizada pra crear un sólo objeto remoto que
soporte comunicación remota (punto a punto) y que utilice el transporte de comunicación basado en sockets que
tiene por defecto el RMI.
Si elegimos extender un objeto remoto de otra clase distinta de UnicastRemoteObject, o, alternativamente, los
extendemos de la nueva clase java.rmi.activation.Activatable del JDK 1.2 (utilizada pra construir objetos remotos
que puedan ser ejecutados sobre demanda), necesitamos exportar explícitamente el objeto remoto llamando a uno
de los métodos UnicastRemoteObject.exportObject o Activatable.exportObject desde el constructor de nuestra
clase (o cualquier otro método de inicialización, cuando sea apropiado).
El ejemplo del motor de cálculo define un objeto remoto que implementa un sólo interface remoto y ningún otro
interface. La clase ComputeEngine también contiene algunos métodos que sólo pueden ser llamados localmente. El
primero de ellos es un constructor para objetos ComputeEngine; el segundo es un método main que es utilizado
para crear un objeto ComputeEngine y ponerlo a disposición de los clientes.
Definir el Constructor
La clase ComputeEngine tiene un único constructor que no toma argumentos.
Durante la construcción, un objeto UnicastRemoteObject es exportado, lo que significa que está disponible para
aceptar peticiones de entrada al escuchar las llamadas de los clientes en un puerto anónimo.
Nota:
En el JDK 1.2, podríamos indicar el puerto específico que un objeto remoto utiliza para aceptar peticiones.
Por ejemplo, si pasarámos por referencia un ejemplar de la clase ComputeEngine, el receptor tendría acceso sólo al
método executeTask. El receptor no vería ni el constructor ComputeEngine ni su método main ni cualquier otro
método de java.lang.Object.
En las llamadas a método remotoss, los objetos -parámetos, valores de retorno y excpeciones - que no son objetos
remotos son pasados por valor. Esto significa que se crea una copia del objeto en la máquina virtual del receptor.
Cualquier cambio en el estado del objeto en el receptor será reflejado sólo en la copia del receptor, no en el ejemplar
original.
Todos los programas que utilicen RMI deben instalar un controlador de seguridad o el RMI no descargará las clases
(las que no se encuentren el el path local) para los objetos que se reciban como parámetros. Estas restriciones
aseguran que las operaciones realizadas por el código descargado pasarán a través de unas pruebas de seguridad.
El ComputeEngine utiliza un ejemplo de controlador de seguridad suministrado como parte del RMI, el
RMISecurityManager. Este controlador de seguridad fuerza una política de seguridad similar al controlador de
seguridad típico de los applets (es decir, es muy conservador con los accesos que permite). Una aplicación RMI
podría definir y utilizar otra clase SecurityManager que diera un acceso más liberal a los recursos del sistema, o, en
el JDK 1.2, utilizar un fichero de vigilancia que ofrezca más permisos.
Aquí temos el código que crea e instala el controlador de seguridad.
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
Poner el Objeto Remoto a Disposición de los Clientes
Luego, el método main crea un ejemplar de ComputeEngine. Esto se hace con la sentencia.
El sistema proporciona un objeto remoto particular, el registro RMI, para encontrar referencias a objetos remotos. El
registro RMI es un sencillo servicio de nombrado para objetos remotos que permite a los clientes remotos obtener
una referencia a un objeto remoto por su nombre. El registro se utiliza típicamente para localizar el primer objeto
remoto que un cliente RMI necesita utilizar. Este primer objeto remoto, luego proporciona soporte para encontrar
otros objetos.
El interface java.rmi.Naming es utilizado como un API final para la entrega (o registrado) y búsqueda de objetos
remotos en el registro. Una vez registrado un objeto remoto en el registro RMI en el host local, los llamadores de
cualquier host pueden busar el objeto remoto por el nombre, obtener su referencia, y luego llamar a los métodos del
objeto. El registro podría ser compartido por todos los servidores ejecútandose en un host, o un proceso servidor
individual podría crear y utilizar su propio registro si así lo desea.
Naming.rebind(name, engine);
Al llamar al método rebind se hace una llamada remota al registro RMI del host local. Esta llamada puede provocar
que se genera une RemoteException, por eso tenemos que manejar la excepción. La clase ComputeEngine
maneja la excepción dentro de los bloques try/catch. Si la excepción no fuese manejada de esta manera,
tendríamos que añadir RemoteException a la clausula throws (ahora inexistente) del método main.
En esta página:
● Crear un Programa Cliente
Nuestro ejemplo está compuesto por dos clases separadas. la primera clase ComputePi, busca y
llama a un objeto Compute. La segunda clase Pi, implementa el interface Task y define el trabajo
que va a hacer el motor de cálcilo. El trabajo de la clase Pi es calcular el valor del número pi, con
algún número de posiciones decimales.
Como recordaremos, el interface no-remoto Task se define de esta forma.
package compute;
public interface Task extends java.io.Serializable {
Object execute();
}
El interface Task extiende java.io.Serializable por lo que cualquier objeto que lo implemente
puede ser serializado por el sistema RMI y enviado a una máquina virtual remota como parte de una
llamada a un método remoto. Podríamos haber elegido hacer que la implementación de nuestra
clase implementara los interfaces Task y Serializable, y hubiera tenido el mismo efecto. Sin
embargo, el único proposito del interface Task es permitir que las implementaciones de este
interface sean pasadas a objetos Compute, por eso, una clase que implemente el interface Task no
tiene sentido que también implemente el interface Serializable. Dado esto, hemos asociado
explícitamente los dos interfaces en el tipo system, asegurando que todos los objetos Task sean
serializables.
El código que llama a los métodos del objeto Compute debe obtener una referencia a ese objeto,
crear un objeto Task, y luego pedir que se ejecute la tarea. Más adelante veremos la definición de la
tarea Pi. Un objeto Pi se construye con un sólo argumento, la precisión deseada en el resultado. El
resultado de la ejecución de la tarea es un java.math.BigDecimal que representa el número pi
calculado con la precisión especificada.
La clase cliente ComputePi.
package client;
import java.rmi.*;
import java.math.*;
import compute.*;
Después de llamar al controlador de seguridad, el cliente construye un nombre utilizado para buscar
un objeto remoto Compute. El valor del primer argumento de la línea de comandos args[0], es el
nombre del host remoto, en el que se están ejecutando los objetos Compute. Usando el método
Naming.lookup, el cliente busca el objeto remoto por su nombre en el registro del host remoto.
Cuando se hace la búsqueda del nombre, el código crea una URL que específica el host donde se
está ejecutando el servidor. El nombre pasado en la llamada a Naming.lookup tiene la misma
síntaxis URL que el nombre pasado a la llamada Naming.rebind que explícamos en páginas
anteriores.
Luego, el cliente crea un objeto Pi pasando al constructor de Pi el segundo argumento de la línea de
comandos, args[1], que indica el número de decimales utilizados en el cálculo. Finalmente, el
cliente llama al método executeTask del objeto remoto Compute. El objeto pasado en la llamada a
executeTask devuelve un objeto del tipo java.math.BigDecimal, por eso el programa fuerza el
resultado a ese tipo y almacena en resultado en la variable result. Finalmente el programa imprime
el resultado.
Finalmente, echemos un vistazo a la clase Pi. Esta clase implementa el interface Task y cálcula el
valor del número pi con un número de decimales especificado. Desde el punto de vista de este
ejemplo, el algoritmo real no es importante (excepto, por supuesto, para la fiabilidad del cálculo).
Todo lo importante es que el cálculo consume numéricamene muchos recursos (y por eso es el tipo
que cosa que querríamos hacer en un servidor potente).
package client;
import compute.*;
import java.math.*;
/**
* Calcula pi.
*/
public Object execute() {
return computePi(digits);
}
/**
* Calcula el valor de Pi con el número de decimales especificados.
* El valor se calcula utilizando la fórmula de Machin.
*
* pi/4 = 4*arctan(1/5) - arctan(1/239)
*
* y una poderoas serie de expansiones de arctan(x)
* para una precisión suficiente.
*/
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);
}
/**
* Calcula el valor, en radianes, de la arcotangente de la
* inversa del entero suministrado para el número de decimales.
* El valor se calcula utilizando la poderosa serie de
* expansiones de arcotangente.
*
* 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);
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(ZERO) != 0);
return result;
}
}
La característica más interesante de este ejemplo es que el objeto Compute no necesita una
definición de la clase Pi hasta que se le pasa un objeto Pi como un argumento del método
executeTask. Hasta este punto, el código de la clase se ha cargado por el RMI dentro de la
máquina virtual del objeto Compute, se ha llamado al método execute, y se ha ejecutado el código
de la tarea. El Object resultante (que en el caso de la tarea Pi es realmente un objeto
java.math.BigDecimal) es enviado de vuelta al cliente, donde se utiliza para imprimir el resultado.
El hecho de que el objeto Task suministrado calcule el valor de Pi es irrelevante para el objeto
ComputeEngine. Por ejemplo, también podríamos implementar una tarea que generara un número
primo aleatorio utilizando un algoritmo probabilistico. (Esto también consume muchos recursos y por
tanto es un candidato para ser enviado al ComputeEngine). Este código también podría ser
descargado cuando el objeto Task fuera pasado al objeto Compute. Todo lo que el objeto
Compute sabe es que cada objeto que recibe implementa el método execute, no sabe (y tampoco
le interesa) qué hace la implementación.
TutorJava recomienda...
En esta página:
● Compilar el Ejemplo
❍ Construir un Fichero JAR con las Clases de Interfaces
❍ Construir las Clases del Servidor
❍ Construir las clases del Cliente
Compilar el Ejemplo
En un escenario del mundo real donde se desarrollara un servicio como el del motor de cálculo, un
desarrollador querría crear un fichero JAR que contenga los interfaces Compute y Task para que
los implementan las clases servidor y para que los utilicen los programas clientes.
Luego, un desarrollador (quizás el mismo que creo el fichero JAR con los interfaces) escribiría una
implementación del interface Compute y desarrollaría ese servicio en una máquina disponible para
los clientes.
Los desarrolladores de los programas clientes pueden utilizar los interfaces Compute y Task
(contenidos en el fichero JAR) e independientemente desarrollar una tarea y un programa cliente
que utilice un servicio Compute.
En esta página, aprenderemos cómo crear un fichero JAR, las clases del servidor, y las clases del
cliente. Veremos como la clase Pi será descargada al servidor durante la ejecución. También
veremos como el stub remoto ComputeEngine será descargado desde el servidor hasta el cliente
durante la ejecución.
El ejemplo separa los interfaces, la implementación de los objetos remotos y el código del cliente en
tres paquetes diferentes.
● compute (Los interfaces Compute y Task)
● engine (Implementación de la clase, el interface y el stub de ComputeEngine)
● client (la implementación del cliente ComputePi y de la tarea Pi)
Primero construiremos el fichero JAR para proporcionar los interfaces del servidor y del cliente a los
desarrolladores.
Windows.
cd c:\home\waldo\src
javac compute\Compute.java
javac compute\Task.java
jar cvf compute.jar compute\*.class
UNIX.
cd /home/waldo/src
javac compute/Compute.java
javac compute/Task.java
jar cvf compute.jar compute/*.class
added manifest
adding: compute/Compute.class (in=281) (out=196)
(deflated 30%)
adding: compute/Task.class (in=200) (out=164)
(deflated 18%)
Ahora podemos distribuir el fichero compute.jar a los desarrolladores de las aplicaciones del cliente
y del servidor para que puedan hacer uso de los interfaces.
En general, cuando cosntruimos las clases del servidor o del cliente con los compiladores javac y
rmic, necesitaremos especificar donde deberían residir los ficheros de clase resultantes para que
sean accesibles a la red. En este ejemplo, esta localización es, para Unix,
/home/user/public_html/classes porque algunos servidores web permiten el acceso a
public_html mediante una URL HTTP construida como http://host/~user/. Si nuestro servidor
web no soporta esta convención, podríamos utilizar un fichero URL en su lugar. El fichero de URL
toma la forma file:/home/user/public_html/classes/ en UNIX, o
file:/c:\home\user\public_html\classes/ en Windows. También se puede seleccionar otro tipo
de URL apropiado.
La accesibilidad en la red de los ficheros de clases permite al sistema RMI descargar código cuando
sea necesario. En vez de definir su propio protocolo para descargar código, RMI utiliza un protocolo
URL soportado por Java (por ejemplo, HTTP) para descargar el código. Observa que un servidor web
completo y poderoso no necesita realizar esta descarga de fichero class. De hecho, un sencillo
servidor HTTP proporciona toda la funcionalidad necesaria para hacer que las clases estén
disponibles para su descarga en RMI mediante HTTP, puedes encontrar uno en.
ftp://java.sun.com/pub/jdk1.1/rmi/class-server.zip
Construir las Clases del Servidor
El paquete engine sólo contiene la implementación de la clase del lado del servidor,
ComputeEngine, la implementación del objeto remoto del interface Compute. Como
ComputeEngine es una implementación de un interface remoto, necesitamos generar un stub para
el objeto remoto para que los clientes puedan contactar con él.
Digamos que, ana, la desarrolladora de la clase ComputeEngine, ha situado
ComputeEngine.java en el directorio c:\home\ana\src\engine, y ha colocado el fichero class
para que lo usen los clientes en un subdirectorio de su directorio public_html,
c:\home\ana\public_html\classes (en UNIX podría ser /home/ana/public_html/classes,
accesible mendiante algún servidor web como http://host/~ana/classes/).
Windows.
set CLASSPATH=c:\home\ana\src;c:\home\ana\public_html\classes\compute.jar
Unix:
Windows.
cd c:\home\ana\src
javac engine\ComputeEngine.java
rmic -d . engine.ComputeEngine
mkdir c:\home\ana\public_html\classes\engine
cp engine\ComputeEngine_*.class
c:\home\ana\public_html\classes\engine
Unix.
cd /home/ana/src
javac engine/ComputeEngine.java
rmic -d . engine.ComputeEngine
mkdir /home/ana/public_html/classes/engine
cp engine/ComputeEngine_*.class
/home/ana/public_html/classes/engine
La opción -d le dice al compilador rmic que situe los ficheros de clases generados,
ComputeEngine_Stub y ComputeEngine_Skel, en el directorio c:\home\ana\src\engine.
También necesitamos poner estos ficheros accesibles en la red, por eso debemos copiarlos en el
área public_html\classes.
Windows.
cd c:\home\ana\public_html\classes
jar xvf compute.jar
Unix.
cd /home/ana/public_html/classes
jar xvf compute.jar
created: META-INF/
extracted: META-INF/MANIFEST.MF
extracted: compute/Compute.class
extracted: compute/Task.class
Para construir el código del cliente, necesitamos el fichero compute.jar que contiene los interfaces
Compute y Task que utiliza el cliente. Digamos que el fichero compute.jar está situado en
c:\home\jones\public_html\classes. Las clases del cliente se pueden construir así.
Detalles Específicos de la Plataforma: Compilar el Cliente
Windows:
set CLASSPATH=c:\home\jones\src;c:\home\jones\public_html\classes\compute.jar
cd c:\home\jones\src
javac client\ComputePi.java
javac -d c:\home\jones\public_html\classes client\Pi.java
UNIX.
En esta página:
● Ejecutar el Ejemplo
❍ Una Nota sobre la Seguridad
❍ Arrancar el Servidor
❍ Arrancar el Cliente
Ejecutar el Ejemplo
Una Nota sobre la Seguridad
El modelo de seguridad del JDK 1.2 es más sofisticado que el modelo utilizado en el JDK 1.1.
Contiene ampliaciones para seguridad de grano fino y requiere código que permita los permisos
específicos para realizar ciertas operaciones.
En el JDK 1.1, todo el código que haya en el path de clases se considera firmado y puede realizar
cualquier operación, el código descargado está gobernado por las reglas del controlador de
seguridad instalado. Si ejecutamos este ejemplo en el JDK 1.2 necesitaremos especificar un fichero
de policía cuando ejecutemos el servidor y el cliente. Aquí tenemos un fichero de policía general que
permite al código descargado desde cualquier codebase, hacer dos cosas.
● conectar o acceptar conexiones en puertos no privilegiados (puertos por encima del 1024) de
cualquier host, y
● conectar con el puerto 80 (el puerto HTTP).
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.net.SocketPermission "*:80", "connect";
};
Si hacemos nuestro código disponible mediante URLs HTTP, deberíamos ejecutar el fichero de
policía anterior cuando ejecutemos este ejemplo. Sin embargo, si utilizarámos un fichero de URLs
en su lugar, podemos utilizar el fichero de policía siguiente. Observa que en entornos windows, la
barra invertida necesita ser representada con dos barras invertidas en el fichero de policía.
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.io.FilePermission
"c:\\home\\ana\\public_html\\classes\\-", "read";
permission java.io.FilePermission
"c:\\home\\jones\\public_html\\classes\\-", "read";
};
Este ejemplo asume que el fichero de policía se llama java.policy y contiene los permisos
apropiados. Si ejecutamos este ejemplo en el JDK 1.1, no necesitamos un fichero de policía ya que
el RMISecurityManager proporciona toda la protección que necesitamos.
Arrancar el Servidor
Antes de arrancar el motor de cálculo, necesitamos arrancar el registro de RMI con el comando
rmiregistry. Como explicamos en páginas anteriores el registro RMI es una facilidad de nombrado
que permite a los clientes obtener una referencia a un objeto remoto.
Observa que antes de arrancar el rmiregistry, debemos asegurarnos de que el shell o ventana en
la que ejecutaremos rmiregistry no tiene la variable de entorno CLASSPATH, o si la tiene ésta no
incluye el path a ninguna clase, incluyendo los stubs de nuestras clases de implementación de los
objetos remotos, que querramos descargar a los clientes de nuestros objetos remotos.
Si arrancamos el rmiregistry y éste puede encontrar nuestras clases stub en el CLASSPATH, no
recordará que las clases stub cargadas pueden ser cargadas desde el codebase de nuestro servidor
(que fue especificado por la propiedad java.rmi.server.codebase cuando se arrancó la aplicación
servidor). Como resultado, el rmiregistry no enviará a los clientes un codebase asociado con las
clases stub, y consecuentemente, nuestros clientes no podrán localizar y cargar las clases stub (u
otras clases del lado del servidor).
unset CLASSPATH
start rmiregistry
UNIX.
unsetenv CLASSPATH
rmiregistry &
Por defecto el registro se ejecuta sobre el puerto 1099. Para arrancar el registro sobre un puerto
diferente, se especifica el número de puerto en la línea de comandos. No olvidemos borrar el
CLASSPATH.
Detalles Específicos de la Plataforma: Arrancar el Registro en el Puerto 2001
Windows.
UNIX.
Una vez arrancado el registro, podemos arrancar el servidor. Primero, necesitamos asegurarnos de
que el fichero compute.jar y la implementación del objeto remoto (que es lo que vamos a
arrancar) están en nuestro path de clases.
Detalles Específicos de la Plataforma - Seleccionar la variable CLASSPATH
Windows.
set CLASSPATH=c:\home\ana\src;c:\home\ana\public_html\classes\compute.jar
Unix.
Windows.
java -Djava.rmi.server.codebase=file:/c:\home\ana\public_html\classes/
-Djava.rmi.server.hostname=zaphod.east.sun.com
-Djava.security.policy=java.policy
engine.ComputeEngine
UNIX.
java -Djava.rmi.server.codebase=http://zaphod/~ana/classes/
-Djava.rmi.server.hostname=zaphod.east.sun.com
-Djava.security.policy=java.policy
engine.ComputeEngine
El comando java anterior define varias propiedades.
● java.rmi.server.codebase, una propiedad que especifica una localización, una URL codebase, de las
clases originarias desde este servidor para que la información de las clases enviadas a otras máquinas
virtuales incluya la localización de la clase que el receptor pueda descargar. Si el codebase especifica
un directorio (como oposición a un fichero JAR), debemos incluir la barra inclinada en la URL.
● java.rmi.server.hostname, una propiedad que indica el nombre totalmente cualificado de nuestro
servidor. En algunos entornos de red, el nombre totalmente cualificado del host no se puede obtener
utilizando el API de Java. RMI hace el mejor esfuerzo para obtener ese nombre. Si uno de ellos no
puede ser determinado, fallará y utilizará la dirección IP. Para asegurarnos de que el RMI utilizará un
nombre de Host, podríamos seleccionar la propiedad java.rmi.server.hostname como medida de
seguridad.
● java.security.policy, una propiedad utilizada para especificar el fichero de policía que contiene los
permisos concedidos a los codebases específicados.
La clase stub de ComputeEngine se carga dinámicamente en la máquina virtual del cliente sólo
cuando la clase no está disponible localmente y la propiedad java.rmi.server.codebase ha sido
configurada apropiadamente, para la localización de la clase stub, cuando se arrancó el servidor.
Una vez cargada la clase stub no necesitamos recargarla más veces para referencias adicionales a
objetos ComputeEngine.
Arrancar el Cliente
Una vez que el registro y el motor se están ejecutando, podemos arrancar el cliente, especificando.
● la localización donde el cliente sirve sus clases (la clase Pi) utilizando la propiedad
java.rmi.server.codebase.
● como argumentos de la línea de comandos, el nombre del host (para que el cliente sepa donde
localizar el objeto remoto) y el número de decimales utilizado en el cálculo del número Pi.
● java.security.policy, una propiedad utilizada para especificar el fichero de policía que contiene los
permisos adecuados.
Primero seleccionamos el CLASSPATH para ver el cliente de jones y el fichero JAR que contiene los
interfaces. Luego se arranca el cliente de esta forma.
Detalles Específicos de la Plataforma: Arrancar el Cliente
Windows.
UNIX.
3.14159265358979323846
La siguiente figura muestra de dónde obtienen las clases el rmiregistry, el servidor
ComputeEngine y el cliente ComputePi durante la ejecución del programa.