PRÁCTICA 3 El servicio de echo en Java-RMI E l objetivo de esta práctica es crear y ejecutar una aplicación cliente servidor echo basada en java-rmi. La práctica tiene dos partes: La primera parte implementa un servidor sencillo basado en un objeto java-rmi con un único método echo. La segunda parte utiliza las facilidades de movilidad de código en Java-RMI. Se trata de implementar también el servicio de echo, pero esta vez el servidor es una máquina de cómputo genérica, denominada ComputeEngine, que puede ejecutar cualquier algoritmo cuyo código se le proporcione a través de la red. En este caso, el algoritmo será el algoritmo del servicio de echo. La metodología de desarrollo de aplicaciones RMI propuesta en esta práctica está basada en el plugin de RMI para Eclipse de http//www.genady.net. Esta metodología se encuentra bien detallada en las Flash demos que se encuentran en la web y que pueden accederse también a través del menú desplegable del plugin RMI. DYA 29
Estructura de la aplicación 3.1.- Estructura de la aplicación La aplicación Echo se estructura en tres paquetes: la interfaz, el cliente y el servidor, los cuales se describen a continuación. Ver figura 1 (pág. 30). FIGURA 1. Estructura de una RPC 3.1.1.- El paquete interfaz (rmi) Consta del siguiente fichero: EchoInt.java: describe el servicio echo. Este fichero debe ser prácticamente igual a la interfaz especificada en la práctica anterior, excepto que el interfaz es en esta práctica es subclase de java.rmi.remote. public interface EchoIntRMI extends java.rmi.remote 3.1.2.- El paquete servidor (server) Consta, básicamente, de dos ficheros: EchoObject.java: implementa la interfaz EchoInt y proporciona el servicio de echo en local (a clientes locales, no a clientes remotos). La implementación es idéntica a la práctica anterior y consiste en devolver la cadena que se envía, junto con el URL y la hora de la máquina servidora al cabo de 3 segundos. Este retraso simula que el servicio tiene un tiempo de cómputo largo y apreciable. 30 El servicio de echo en Java-RMI
Realización del servicio de echo elemental en RMI EchoObjectRMI.java: es el verdadero objeto RMI (extends UnicastRemoteObject). También implementa la interfaz EchoInt e implementa el servicio de echo en remoto (para clientes remotos). Su implementación se basa en crear una instancia del objeto EchoObject y delegar en el la implementación del método echo. La funcionalidad adicional que aporta esta clase es la de registrar el servicio en el servidor de nombres y proporcionarle la capacidad de ser invocado remotamente mediante el código genético que aporta la clase UnicastRemoteObject. El objeto servidor lo componen el código objeto correspondiente a estos ficheros junto a los skeletons generados automáticamente por RMI. 3.1.3.- El paquete cliente (client) Lo constituye el siguiente fichero: EchoRMI.java: es el cliente RMI. Se encarga de obtener una referencia RMI al objeto servidor a partir del servicio de nombres. Una vez obtenida esta referencia, realiza el bucle: - Leer de teclado - Invocar el objeto (a través de la referencia al objeto) - Imprimir el resultado por pantalla. 3.2.- Realización del servicio de echo elemental en RMI Para la realización de la parte básica de la práctica cree un proyecto prj-rmi y siga la metodología descrita en la práctica 1. 3.2.1.- Creación del proyecto 1. Descargue los ficheros de ayuda al directorio de descargas ($WS/descargas) si los hubiera. 2. Cree un proyecto prj-rmi en el workspace según se indica en la práctica 1 cree también los paquetes de que consta la aplicación: rmi, client, server. 3.2.2.- Generación de la interfaz RMI El desarrollo de la interfaz RMI, contenida en el paquete rmi, consta de los siguientes pasos: 3. Cree una interfaz EchoInt. La opción más sencilla es utilizar File->New->Other->Java->RMI - > Remote Interface. También se puede hacer con File->New->New Interface especificando: - Name: EchoInt DYA 31
Realización del servicio de echo elemental en RMI - Extended interfaces: java.rmi.remote 4. Complete la definición de la interfaz con la especificación del método echo. 3.2.3.- Generación del servidor RMI pasos: El desarrollo del servidor RMI, contenido en el paquete server, consta de los siguientes 5. Cree una clase EchoObjectRMI. con File->New Class especificando: - Name: EchoObjectRMI - Superclass: java.rmi.unicastremoteobject - Extended interfaces: rmi.echoint - public static void main - Constructors from superclass 6. Copie el fichero EchoObject.java del directorio de descargas al directorio server del proyecto en el workspace y actualice el Package explorer para visualizarlo. 7. Complete la implementación del servidor con la implementación de los métodos echo y main. - El método echo delega en el correspondiente método de la clase EchoObject. - El método main básicamente debe realizar una instancia del EchoObjectRMI e inscribirla en el Servicio de Nombres de RMI 3.2.4.- Generación de stubs para ello: La ejecución de aplicaciones RMI requiere la generación automática de stubs y skeletons, 8. Habilite la generación de stubs RMI para el servidor del proyecto. Para ello en el menú contextual del proyecto seleccione: - RMI->Enable Stubs Generation Esto generará los stubs cada vez que sea necesario (si el proyecto tiene seleccionada la opción Project->Build automatically) pero el package explorer normalmente no los muestra. 9. Para mostrar los stubs en el Package explorer deberá habilitar la siguiente opción en el menú contextual del proyecto: - Properties -> -keep En caso de no disponer del plugin RMI, la generación de stubs y skeletons puede realizarse desde una consola MS-DOS estableciendo la variable de entorno CLASSPATH y ejecutando el compilador de RMI, especificando como parámetros el servidor de echo: > rmic server/echoobjectrmi.java 3.2.5.- Generación del cliente RMI El desarrollo del cliente RMI, contenido en el paquete client, consta de los siguientes pasos: 32 El servicio de echo en Java-RMI
Realización del servicio de echo elemental en RMI 10. Copie el fichero EchoRMI.java del directorio de descargas al directorio client del proyecto en el workspace y actualice el Package explorer para visualizarlo. 11. Realice los ejercicios propuestos. - Sólo tiene que realizar la invocación al servidor de echo. - Observe la necesidad de un gestor de seguridad en el cliente. 3.2.6.- Compilación y ejecución de aplicaciones RMI en Eclipse El desarrollo de aplicaciones RMI en Eclipse se ve facilitado por el plugin de http:// www.genady.net. Para ejecutar una aplicación RMI, básicamente debe seguir los siguientes pasos: 1. Arranque el servicio de nombres RMI rmiregistry utilizando la opción Start Local Registry (port 1099) del menú del plugin de RMI: En caso de no disponer del plugin RMI, este servicio también puede arrancarse desde una consola MS-DOS estableciendo la variable de entorno CLASSPATH y ejecutando: > start rmiregistry En Unix: > rmiregistry& 2. Ejecute el servidor EchoObjectRMI creando un perfil de ejecución con el menú Run as -> RMI Application y fijando las siguientes propiedades de la máquina virtual (menú RMI VM Properties): - java.rmi.server.codebase: permite especificar un URL para el código rmi. De esta forma, la máquina virtual puede conocer la ubicación de las clases y sus correspondientes stubs o skeletons. Fíjela en el directorio bin de la aplicación (file:${workspace_loc:/prj-rmi/bin). Esta opción se especifica con Compute from classpath. - La ejecución de aplicaciones desde consola (caso de no disponer del plugin RMI) debe especificar correctamente las propiedades de la máquina virtual: > java server/echoobjectrmi -Djava.rmi.server.codebase=... -Djava.security.policy=... 3. Compruebe que el servicio echo ha sido registrado correctamente en el rmiregistry utilizando el RMI Registry Inspector. 4. Ejecute el cliente EchoRMI creando un perfil de ejecución con el menú Run as -> RMI Application y especificando: - Argumentos de ejecución (menú (x) Arguments): host del servidor. - Propiedades de la máquina virtual (menú RMI VM Properties). Puesto que el cliente utiliza un gestor de seguridad es necesario especificar una política: - java.security.policy: permite especificar el URL para un fichero con la política de seguridad necesaria para ejecutar aplicaciones RMI. Existen aquí dos opciones: o bien crear un fichero DYA 33
Despliegue y ejecución de aplicacione RMI desde la consola. automáticamente desde las opciones disponibles (Create...) o bien especificar un fichero con el siguiente contenido. grant { permission java.net.socketpermission "*:1024-65535", "connect,accept,resolve"; ; 5. Realice también pruebas de invocación de clientes a servidores remotos utilizando el servidor de echo de otros compañeros de prácticas. 3.3.- Despliegue y ejecución de aplicacione RMI desde la consola. La ejecución de la aplicación RMI fuera del entorno Eclipse consta de dos pasos fundamentales el despliegue de la aplicación como ficheros jar y la ejecución, propiamante dicha, del servidor y del cliente. 3.3.1.- Despliegue de la aplicación Deben generarse los siguientes ficheros jar: 1. rmi_remote.jar: contiene los interfaces y los stubs de RMI. Debe estar accesible por el cliente en tiempo de ejecución. En el caso de la práctica Echo contiene: rmi/echoint.java server/echoobjectrmi_stub.java 2. rmi_server.jar: contiene la implementación del servidor. En el caso de la práctica Echo contiene: 1. rmi/echoint.java server/* 2. rmi_client.jar: contiene la implementación del cliente. En el caso de la práctica Echo contiene: rmi/echoint.java client/* 3.3.2.- Ejecución del servidor RMI La ejecución del servidor consta de los siguientes pasos: 1. Ejecutar el servicio de nombres rmiregistry en la máquina que se ejecute el servidor. 2. Crear un fichero de política de segurida denominado, por ejemplo, security.policy con el siguiente contenido: grant { permission java.security.allpermission; 34 El servicio de echo en Java-RMI
Realización de la aplicación echo utilizando movilidad de código 3. Dejar el fichero rmi_remote.jar con la implementación del servidor en una localización accesible tanto por parte del servidor como del cliente (hhtp:, ftp:, ó file: ). Por ejemplo: file:/users/dya/echo/rmi_remote.jar 4. La máquina java que ejecute al servidor debe tener establecidos las siguientes propiedades: - -Djava.security.policy= el fichero del paso 2 - -Djava.rmi.server.codebase=Localización del fichero rmi_remote.jar - -classpath rmi_server.jar En el caso de la práctica de Echo - java -Djava.security.policy=security.policy - -Djava.rmi.server.codebase=file:/users/dya/echo/rmi_remote.jar - -classpath rmi_server.jar server.echoobjectrmi 3.3.3.- Ejecución del cliente RMI Para ejecutar el cliente RMI, el fichero rmi_remote.jar con los stubs y el fichero de política de seguridad deben estar accesibles en la máquina del cliente. La ejecución se realiza mediante la siguiente orden: java -Djava.security.policy=security.policy -Djava.rmi.server.codebase=file:/users/dya/echo/rmi_remote.jar -classpath rmi_client.jar client.echormi localhost 3.4.- Realización de la aplicación echo utilizando movilidad de código Visite el Tutorial de Java (disponible en la web de la asignatura) y seleccione el capítulo de RMI. En este capítulo se desarrolla una aplicación donde existe un servidor de computo genérico ComputeEngine, que ejecuta un código (subclase de Task) que el cliente le puede especificar como parámetro por valor en una invocación RMI (movilidad de código). La Task que se desarrolla en el tutorial es las Task Pi que contiene un algoritmo para calcular el número pi. Ver figura 2 (pág. 36). Este ejemplo se encuentra disponible como tutorial en el plugin RMI. Esta segunda parte de la práctica consiste en compilar y ejecutar el ejemplo del tutorial y, posteriormente, modificarlo para sustituir el algoritmo de calcular el numero pi por el algoritmo de realizar el servicio echo. De esta manera el servidor ComputeEngine ejecutará una Task con el servicio de echo. Se requiere también modificar la interfaz del Compute del ComputeEngine para adaptarla a la siguiente especificación: DYA 35
Realización de la aplicación echo utilizando movilidad de código FIGURA 2. La aplicación ComputeEngine import java.rmi.remote; import java.rmi.remoteexception; public interface Compute extends Remote { //loadtask: Cargar una nueva task en el ComputeEngine. No ejecutarla void loadtask(task t) throws RemoteException; //executetask: Ejecutar una task previamente cargada con loadtask //la task admite como argumentos de entrada los proporcionados en arg y //el resultado de la Task es devuelto como resultado de executetask Object executetask(object arg) throws RemoteException; Esta segunda parte consta de los siguientes pasos: 1. Crear los proyectos de esta aplicación con: File->New->Other->Java->RMI -> Tutorials y seleccionando Sun s RMI Tutorial. 2. Ejecutar la aplicación anterior. Para ello, siga los pasos del apartado 3.2.6.- 3. Modificar la aplicación anterior para ajustarse a la nueva especificación de la interfaz Compute. Para ello: - Modifique las interfaces Compute y Task. - Modifique el servidor de ComputeEngine para implementar la nueva interfaz Compute. - Realice una Task que implemente el algoritmo de echo, aprovechando el objeto EchoObject.java. - Realice una nueva versión del cliente de echo de la primera parte de la práctica para que invoque la nueva máquina de cómputo genérica. 4. Ejecute la nueva versión del servidor de cómputo genérico. 36 El servicio de echo en Java-RMI
Ficheros de apoyo 3.5.- Ficheros de apoyo 3.5.1.- Fichero server/echoobjectrmi.java 3.5.2.- package server; import java.rmi.remoteexception; import java.rmi.registry.locateregistry; import java.rmi.registry.registry; import java.rmi.server.unicastremoteobject; import rmi.echoint; public class EchoObjectRMI extends UnicastRemoteObject implements EchoInt { private static final long serialversionuid = 1L; protected EchoObjectRMI() throws RemoteException { super(); // TODO Auto-generated constructor stub private static EchoObject eo = new EchoObject(); public String echo(string input) throws RemoteException { // TODO Auto-generated method stub return eo.echo(input); public static void main(string[] args) { // TODO Auto-generated method stub try { Registry registry = LocateRegistry.getRegistry(); registry.rebind("echo", new EchoObjectRMI()); catch (RemoteException e) { System.err.println("Something wrong happended on the remote end"); e.printstacktrace(); System.exit(-1); // can't just return, rmi threads may not exit System.out.println("The echo server is ready"); 3.5.3.- Fichero server/echoobject.java El mismo que para la práctica de sockets. DYA 37
Ficheros de apoyo 3.5.4.- Fichero client/echoobject.java package client; import java.io.*; import java.rmi.naming; import java.rmi.rmisecuritymanager; import rmi.echoint; public class EchoRMI { public static void main(string[] args) { // TODO Auto-generated method stub if (args.length<1){ System.out.println("Uso echo <host>");system.exit(1); if(system.getsecuritymanager()== null) System.setSecurityManager(new RMISecurityManager()); BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); PrintWriter stdout = new PrintWriter(System.out); String input,output; try{ EchoInt obj = (EchoInt) Naming.lookup("//" + args[0] + "/echo"); stdout.print("> "); stdout.flush(); while ( (input = stdin.readline())!=null){ //EJERCICIO: invocar el objeto RMI stdout.println(output); stdout.print("> "); stdout.flush(); catch(exception e){ System.out.println("Error en el cliente de echo RMI : " + e.getmessage()); 38 El servicio de echo en Java-RMI