4.3: Computación distribuida: Java RMI A. Goñi, J. Ibáñez, J. Iturrioz, J.A. Vadillo OCW 2013
Indice RMI: Introducción Construcción de aplicaciones RMI Dfii Definir Interfaz remota Implementar interfaz remota: Clase remota Registrar objeto clase remota Localizar y ejecutar objeto remoto Arquitectura RMI Ejemplo Conexión entre nivel de presentación, lógica del negocio y nivel de datos Evolución del sistema 105
Introducción a RMI RMI (Remote Method Invocation) Es un API, Conjunto de interfaces, clases y métodos que permiten desarrollar en Java aplicaciones distribuidas de manera sencilla Equivalente a RPC (Remote Procedure Call) Existen otros estándares como CORBA Permite desarrollar aplicaciones distribuidas usando distintos Lenguajes de Programación 106
Introducción a RMI Clase Cliente1... Clase ClienteN... EN NODOS CLIENTE Clase GestorBilletes - listabilletes: Vector - maxbilletes: int + getgestor(): GestorBilletes + getbillete(nom: String): int // Dev. nº billete asignado o -1 si no hay -- Clase con 1 sola instancia (Singleton) -- El método static getgestor() la devuelve EN NODO SERVIDOR En una arquitectura distribuida no se puede hacer lo siguiente: GestorBilletes g = GestorBilletes.getGestor(); return g.getbillete("kepa t Sola"); Ya que GestorBilletes es un objeto de la máquina virtual remota 107
Construcción aplicaciones RMI Para construir una aplicación Cliente/Servidor donde un cliente acceda a un servicio remoto (clase remota) usando RMI hay que: 1.- Definir la interfaz remota 2.- Implementar la interfaz remota (clase remota) 3.- Registrar un objeto de la clase remota 4.- Localizar y ejecutar el objeto remoto 108
1. Definir la interfaz remota Cliente1... Cliente2... Interfaz GestorBilletes + getbillete(nom: String): int // Dev. nº billete asignado o -1 si no hay -- INTERFAZ REMOTA RMI permite invocar a un objeto remoto Para ello hay que definir una interfaz remota Así, un cliente puede hacer lo siguiente: GestorBilletes g; // Código para obtener la dirección del // objeto remoto y dejarlo en g return g.getbillete( Kepa Sola ); 109
1. Definir la interfaz remota // GestorBilletes.java import java.rmi.*; public interface GestorBilletes extends Remote { public int getbillete(string g nom) throws RemoteException; } Interfaz java.rmi.remote -- INTERFAZ REMOTA Interfaz GestorBilletes + getbillete(nom: String): int -- INTERFAZ REMOTA La interfaz extiende java.rmi.remote Todos los métodos deben lanzar java.rmi.remoteexception 110
2. Implementar la Interfaz Remota La clase Remota Clase java.rmi.server.unicastremoteobject Interfaz java.rmi.remote -- CLASE REMOTA extiende -- INTERFAZ REMOTA extiende Clase ServidorGestorBilletes + getbillete(nom: String): int -- CLASE REMOTA implementa Interfaz GestorBilletes + getbillete(nom: String): int -- INTERFAZ REMOTA El servidor implementa la interfaz remota Extiende java.rmi.server.unicastremoteobject 111
2. Implementar la Interfaz Remota La clase Remota // ServidorGestorBilletes.java a import java.rmi.*; import java.rmi.server.unicastremoteobject; import java.util.*; public class ServidorGestorBilletes extends UnicastRemoteObject implements GestorBilletes{ private Vector listabilletes = new Vector(); private static int maxbills = 50; public ServidorGestorBilletes() throws RemoteException{} public int getbillete(string nom) throws RemoteException{ //lógica de negocio num=9999; // Devuelve siempre el billete 9999... return num; } 112
3. Registrar un objeto de la clase remota Se crea un registro en el servidor (en ( el main () la misma clase que el objeto u otra distinta) java.rmi.registry.locateregistry.createregistry(numpuerto); registry createregistry(numpuerto); main() de Se crea un objeto de la clase remota ServidorGestorBilletes objetoservidor = new ServidorGestorBilletes(); Se crea un nombre para ese servicio String servicio = "rmi://localhost:1099//gestorbilletes"; // si 1099 es numpuerto Se registra ese servicio con ese nombre Naming.rebind(servicio,objetoServidor) 113
3. Registrar un objeto de la clase remota Ejemplo class ServidorRemoto public static void main(string[] args) { //Falta el java.policy try { java.rmi.registry.locateregistry.createregistry(1099); registry createregistry(1099); } catch (Exception e) {System.out.println( Rmiregistry ya lanzado +e.tostring());} } try { ServidorGestorBilletes objetoservidor = new ServidorGestorBilletes(); 3 String servicio = "//localhost/gestorbilletes"; // "//localhost:numpuerto/nombreservicio" // Registrar el servicio remoto 4 Naming.rebind(servicio,objetoServidor); } catch (Exception e) {System.out.println("Error al lanzar el servidor");} 2 114 1
3. Registrar un objeto de la clase remota El registrador: r RMIRegistry java.rmi.registry.locateregistry.createregistry(p) Crea el proceso rmiregistry en el puerto p. El rmiregistry lanzado no acaba aunque acabe el servidor RMI Lanza una excepción si el puerto está ocupado try { java.rmi.registry.locateregistry.createregistry(1099); } catch (Exception e) {System.out.println( println( Rmiregistry ya lanzado +e.tostring());} Código que lanza el rmiregistry y controla la excepción que se puede levantar al reejecutar varias veces el servidor RMI Parar el rmiregistry:unicastremoteobject.unexportobject(registry, true); 115
4. Localizar y ejecutar el objeto remoto Clase java.rmi.server.unicastremoteobject Interfaz java.rmi.remote -- CLASE REMOTA -- INTERFAZ REMOTA extiende Clase ServidorGestorBilletes + getbillete(nom: String): int -- CLASE REMOTA implementa extiende Interfaz GestorBilletes + getbillete(nom: String): int -- INTERFAZ REMOTA El cliente busca el objeto remoto, a partir del nombre lógico Pide al servidor que ejecute los métodos que desee usa Clase Cliente ges : GestorBilletes -- CLASE CLIENTE 116
4. Localizar y ejecutar el objeto remoto import java.rmi.*; public class Cliente { public static void main(string[] args) { // Falta gestion java.policy 1 GestorBilletes objremoto; String nomserv = "rmi://localhost/gestorbilletes"; // "rmi://direccionip:numpuerto/nombreservicio" try { 2 objremoto = (GestorBilletes)Naming.lookup(nomServ); int b = objremoto.getbillete(args[0]); 3 if (b==-1) System.out.println("No hay billetes"); else System.out.println("Obtenido : "+b); } catch (Exception e) { System.out.println("Error... t tl ");}} }} 117
Arquitectura RMI objremoto.getbillete("koldo") objremoto @ObjLocalX RETURN: 5 Máquina virtual java CLIENTE Máquina virtual java SERVIDOR objservidor.getbillete("koldo") t ld objservidor @ObjLocalY Se desea conseguir que lo que se le pida al objeto @ObjLocalX sea ejecutado por el objeto @ObjLocalY en otra máquina virtual Java 118
Arquitectura RMI Cliente Maquina virtual java Cliente RED Maquina virtual java Servidor Objeto ServidorGestorBilletes @ObjLocalX Stub ServidorGestorBilletes (impl. GestorBilletes) @ObjLocalY Skeleton ServidorGestorBilletes (impl. GestorBilletes) Stub ServidorGestorBilletes: Representante del objeto ServidorGestorBilletes en el cliente Skeleton ServidorGestorBilletes: Representante del objeto ServidorGestorBilletes en el servidor NOTA: Los objetos Stub, Skeleton y el objeto remoto comparten la misma interfaz POR LO TANTO: hay yq que asegurarse de que las clases STUB y la INTERFAZ REMOTA están accesibles en el cliente (copiándolas en el mismo, por ejemplo) 119
Arquitectura RMI: Objetos Stub y Skeleton Programa Cliente Programa Servidor call return call return Stub Skeleton Nivel de Referencia Remota Nivel de Transporte Nivel Nve de Referencia Remota Nivel de Transporte Conexión TCP/IP Los objetos Stub y Skeleton eto se encargan de realizar a la conexión y del paso de parámetros y resultados 120
Arquitectura RMI: Objetos Stub y Skeleton En principio, i i los stubsy tb los skeleton klt se pasan los objetos enviados como parámetros y los resultados REALIZANDO UNA COPIA DE SUS VALORES (y de los objetos incluidos en ellos, recursivamente). No se pasan referencias a un objeto remoto. Para ello, se usan los mecanismos de serialización de Java POR LO TANTO: las clases de los objetos que se pasen por parámetro en métodos remotos deben implementar la interfaz Serializable 121
Arquitectura RMI: Generación de stubs y skeletons El STUB y SKELETON están asociados a la interfaz remota y a la clase remota GestorBilletes.java GestorBilletes.class Stub ServidorGestorBilletes.java ServidorGestorBilletes.class Skeleton Cuando se compilan se añaden el Stub y el Skeleton a las clases correspondientes(jdk6.0) Nota: se generaban explícitamente como clases independientes usando la herramienta RMIC EL STUB: GestorBilletes.class tiene que quedar disponible para la máquina cliente.
Arquitectura RMI Relación entre Stub y Skeleton rmiregistry rebind (Servidor) java.rmi.registry.locateregistry.createregistry(p) GestorBilletes objetoservidor =new ServidorGestorBilletes(); String nombreservicio = //localhost/gestorbilletes ; // //localhost:numeropuerto/nombreservicio ; // Registrar servicio i remoto Naming.rebind(nombreServicio,objetoServidor); gestorbilletes @ServidorGestorBilletes GestorBilletes.class (STUB)............ Instancia Lógica de Negocio ServidorGestorBilletes Skeleton...... Clase Clase de acceso a la Logica Remota Stub
Arquitectura RMI Relación entre Stub y Skeleton rmiregistry lookup (Cliente) GestorBilletes t objetoremoto t = (GestorBilletes)Naming.lookup( rmi://ip_servidor/gestorbilletes ); en puerto 1099, por defecto El método lookup, devuelve del registro una instancia de acceso a la lógica remota(stub). Esta instancia será la responsable de comunicarse (a través del Skeleton) con el objeto servidor. puerto 1099 Maquina virtual Java ubicada en IP_Servidor RMIREGISTRY gestorbilletes @ServidorGestorBilletes GestorBilletes.................. Naming.rebind( //localhost/gestorbilletes,objetoservidor); / en puerto 1099, por defecto 124
Arquitectura RMI Gestor de seguridad d Un programa Java debe especificar un gestor de seguridad que determine su política de seguridad. Algunas operaciones requieren que exista dicho gestor. En concreto, las de RMI. RMI sólo cargará una clase Serializable desde otra máquina si hay un gestor de seguridad que lo permita Se puede establecer un gestor de seguridad por defecto para RMI de la siguiente manera: System.setSecurityManager( new RMISecurityManager()); 125
Arquitectura RMI Gestor de seguridad El gestor de seguridad por defecto de RMI utiliza una política muy restrictiva. Sólo se pueden ejecutar STUBs del CLASSPATH local Se puede cambiar, indicando otro fichero de política de seguridad: System.setProperty(" ("java.security.policy", c:\\mipath\\java.policy"); Contenido del fichero java.policy java.policy: grant { permission java.security.allpermission; a.secu e ss }; 126
Arquitectura RMI Gestor de seguridad d También se puede indicar para cada ejecución cuál es la política de seguridad (esto es, usar la opción -Djava.security.policy = fichero_políticaseguridad) O bien, el fichero java.policy, hay dejarlo en un directorio concreto, conocido por la máquina virtual. De esa manera, todas las aplicaciones lanzadas con esa máquina virtual usarán dicha política de seguridad Si la máquina virtual que se ejecuta es esta: DIRECTORIO_JDK_O_JRE\bin\java.exe El fichero java.policy hay que dejarlo en DIRECTORIO_JDK_O_JRE\lib\security 127
import java.rmi.*; import java.sql.*; Ejemplo: Servidor Remoto accede a BD import java.rmi.server.unicastremoteobject; i t t import java.util.*; public class ServidorGestorBilletesBD extends UnicastRemoteObject implements GestorBilletes { private static Connection conexion; private static Statement sentencia; public ServidorGestorBilletesBD() throws RemoteException{ try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); sun.jdbc.odbc.jdbcodbcdriver conexion=drivermanager.getconnection("jdbc:odbc:bill"); sentencia=conexion.createstatement(); conexion.setautocommit(false);// Habrá que hacer COMMITs } catch(exception e) { System.out.println( Error: "+e.tostring());} } public int getbillete(string nom) throws RemoteException {.. 128
Ejemplo: Servidor Remoto accede a BD // Método main de ServidorGestorBilletesBD.java public static void main(string[] args) { System.setProperty("java.security.policy,"F:\\iso\\java.policy"); System.setSecurityManager(new RMISecurityManager()); try { java.rmi.registry.locateregistry.createregistry(1099); } catch (Exception e) {System.out.println( Rmiregistry ya lanzado +e.tostring());} try { ServidorGestorBilletesBD objetoservidor = new ServidorGestorBilletesBD(); String maquina = "//localhost/"; // o bien el nombre IP: //sisd00.si.ehu.es/ String servicio = "gestorbilletes"; String servicioremoto i = maquina+servicio; i i // Registrar el servicio remoto Naming.rebind(servicioRemoto,objetoServidor); }catch (Exception e) {System.out.println("Error: "+e.tostring());} }} 129
Conexión entre nivel de presentación, lógica del negocio y datos Hst Hasta ahora hr hemos hms considerado nsidrd que en el lni nivel ld de presentación, el objeto con la lógica del negocio se encuentra en un atributo (de tipo interface Java) Utilizando RMI se puede seguir con esa misma idea, pero en este caso la interfaz es remota En vez de asignarle al objeto de presentación, el objeto con la lógica del negocio se puede hacer que sea el objeto de presentación quien lo busque (usando lookup). Cambiar la lógica del negocio consiste en sustituir un objeto por otro en el servidor. 130
RMIREGISTRY y ServidorGestorBilletesBD en la misma máquina (IPServidor), pero cada uno en DISTINTAS MÁQUINAS VIRTUALES JAVA RmiRegistry está escuchando en un puerto (1099) objremoto:servidorgestorbillet esbd mainservidorgestorbilletesbd new Mirar métodos de LocateRegistry.createRegistry Naming exportobject(puertoanonimo) (1) Rmiregistry i rebind("localhost:1099/gestorbilletes",objremoto) getref() : PedirBillete Naming lookup("rmi://ipservidor:1099/gestorbilletes") stub getbillete("pepe") numbilleteparapepe SOCKET("IPservidor",1099,"gestorBilletes") (5) (6) PresentacionRemoto en otra máquina A. Goñi. Dpto. LSI, UPV/EHU SOCKET(stub) stub: ServidorGestorBilletesBD _Stub referenciaaobjremoto (2) (3) SOCKET("localhost",1099, gestorbilletes,stub) new(referenciaaobjremoto) stub: ServidorGestorBilletesBD _Stub (4) () Se pasa el objeto STUB serializado, El cliente cargará dinámicamente la clase ServidorGestorBilletesBD_Stub SOCKET("IPServidor", PuertoAnonimo,"gestorBilletes","getBillete","Pepe") SOCKET(numBilleteParaPepe) (7) (1) Se debe lanzar el RMIREGISTRY PARA QUE FUNCIONE RMI (2) Se debe haber generado el STUB (con RMIC, para versiones anteriores a JDK 6) (3) La clase STUB debe estar accesible en el CLASSPATH del SERVIDOR (4) Se debe permitir el USO DE SOCKETS (POLÍTICA DE SEGURIDAD) (5) La clase STUB debe estar accesible en el CLASSPATH del CLIENTE, o bien, se debe lanzar indicando de dónde descargarlo (CODEBASE) 131 (6) Se debe permitir la DESCARGA DINÁMICA DE CLASES (POLÍTICA DE SEGURIDAD) (7) Los parámetros y resultados de los métodos remotos deben ser de clases SERIALIZABLES
ARQUITECTURA FÍSICA EN 2 NIVELES: Interface LogicaNegocio CLIENTE GORDO / SERVIDOR FLACO hacerx( ) hacery( ) Clase Presentacion usa logne: LogicaNegocio setlogicanegocio (l: LogicaNegocio) // Permite cambiar lógica // negocio en tº ejecuc. clase LogicaNegocioConcreta // i tº j hacerx( ) // Implementaciones hacery( ) // llaman al nivel de // datos (usan JDBC) -- En esta clase se llama -- a la lógica del negocio: Por ej.: logne.hacerx( ) CREAR LA INTERFAZ GRÁFICA Y ASIGNAR LÓGICA DEL NEGOCIO: Presentacion p = new Presentacion(); p.setlogicanegocio(new LogicaNegocioConcreta()); p.setvisible(true); 132
ARQUITECTURA FÍSICA EN 3 NIVELES usando RMI Interface LogicaNegocio Clase Presentacion logne: LogicaNegocio usa hacerx( ) hacery( ) interfaz Remota setlogicanegocio (String nomserv) // Por ejemplo aquí se // puede asignar a logne clase LogicaNegocioConcreta // d i l N hacerx( ) // Implementaciones hacery( ) // llaman al nivel de // datos (usan JDBC) -- En esta clase se llama -- a la lógica del negocio: Por ej.: logne.hacerx( ) -- Para asignar la lógica -- del negocio se usa Naming.lookup y NomServ -- La lógica del negocio se crea -- y se exporta usando: Naming.rebind y dando un nombre de servicio: NomServ 133