RMI (Remote Method Invocation)
Contents
- Permite invocar un método sobre un objeto en otra MVJ (máquina virtual Java)
- Podemos pasar como parámetros tanto tipos primitivos como objetos
- Podemos recibir como resultado tipos primitivos o un objeto (o una excepción)
- Los objetos locales (serializables) se pasan por valor, y los remotos por referencia
- Es parte integrante del JDK
- Utiliza un protocolo nativo propio JRMP1 (Java Remote Method Protocol) que se sitúa sobre el protocolo de red (ej.- TCP/IP, por ejemplo)
- A diferencia de RPC, RMI forma parte integrante del lenguaje
- No hay que hacer ninguna referencia explícita a ningún nivel subyacente del sistema sobre el que se va a ejecutar la aplicación
- Como la máquina virtual de Java es la misma para cualquier sistema, la forma en la que se programe usando este mecanismo será la misma siempre.
- No puede utizarse para invocar objetos codificados en otros lenguajes
- Frente a CORBA
- No soporta otros lenguajes
- Menos robusto y escalable
- Principales aportaciones de RMI
- Un recolector de residuos distribuido
- Mantiene los sistemas de seguridad de Java
- Utiliza la posibilidad de crear hilos de ejecución (threads) concurrentes potenciando la idea concurrente en las aplicaciones distribuidas
- Explota la serialización de los objetos Java para transportar objetos a través de la red sin necesidad de ningún esquema extra.
- Interfaz común a cliente y servidor, extensión de 'Remote'
- Implementación de interfaz
- En la MVJ cliente:
- Stub, generado automáticamente desde la implementación
- Aplicación cliente (puede crear y usar objetos a partir del stub)
- En la MVJ servidor:
- Esqueleto, generado automáticamente desde la implementación
- Aplicación servidor (crea y registra objetos a partir de la implementación)
- Debe ser 'public' y extender 'java.rmi.remote'
- Declara los métodos que deseamos invocar de forma remota
- Cada uno debe declarar 'java.rmi.RemoteException' en su clausula 'throws'
- Puede declarar excepciones especificas de la aplicación
- Los objetos remotos deben pasarse como interfaces remotos
- Los objetos locales deben ser serializables
import java.rmi.*;
public interface Sumador extends Remote {
long suma(String s) throws NumeroIncorrecto, RemoteException;
}
public class NumeroIncorrecto extends Exception {
private String mensaje;
NumeroIncorrecto (String s) {mensaje=s;}
public String getMessage() {return mensaje;}
}
- Declaramos una clase que implemente al menos un interfaz remoto
- El constructor debe ser público e incluir 'throws java.rmi.RemoteException'
- Debe implementar los métodos definidos en el interfaz remoto
- Puede declarar otros métodos, pero sólo son accesibles localmente
import java.rmi.server.*;
import java.io.*;
import java.util.StringTokenizer;
public class ImplemSumador
extends UnicastRemoteObject
implements Sumador {
public ImplemSumador() throws RemoteException {}
public long suma(String s)
throws NumeroIncorrecto, RemoteException {
long n=0;
StringTokenizer st= new StringTokenizer(s);
while (st.hasMoreTokens()) {
String token=st.nextToken();
try {n+=Long.parseLong(token);}
catch (NumberFormatException e) {
throw new NumeroIncorrecto(
'No puedo interpretar '+token+
' como un entero');
}
}
return n;
}
}
- Para que un cliente pueda acceder a un objeto remoto, el servidor debe registrarlo
- Previamente hay que activar la utilidad 'rmiregistry'
- El servidor invoca el método Naming.rebind(id,obj) para registrar una implementación
- El cliente invoca el método Naming.lookup(id) para obtener un stub
- El servidor es una aplicación java que:
- Crea e instala un gestor de seguridad
- Crea una o más instancias del objeto remoto
- Enlaza al menos uno de los objetos remotos con un nombre en el registro RMI
- El nombre puede tener distintos formatos
- rmi://localhost:1234/SUMADOR
import java.rmi.*;
public class ServidorSumador {
public static void main (String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
ImplemSumador sum= new ImplemSumador();
Naming.rebind('SUMADOR',sum);
System.out.println('Preparado');
} catch (Exception e) {e.printStackTrace();}
}
}
- El cliente debe:
- Obtener una referencia invocando Naming.lookup()
- Invocar métodos sobre la referencia
- Naming.lookup():
- Utiliza el mismo tipo de identificador (URL) que Naming.rebind()
- Contacta con el registro indicado por la URL y solicita el nombre
- Recibe un obejto stub para el objeto remoto y carga código para el mismo
- Devuelve al invocante una referencia al stub
import java.rmi.*;import java.rmi.registry.*;import java.net.*;import java.io.*;
public class ClienteSumador {
public static void main (String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
Sumador sum= (Sumador) Naming.lookup('SUMADOR');
LineNumberReader entrada= new LineNumberReader(
new BufferedReader(new InputStreamReader(System.in)));
for (;;) {
String linea= entrada.readLine();
if (linea==null || linea.length()==0) break;
String salida;
try {salida=Long.toString(sum.suma(linea));}
catch (NumeroIncorrecto e) {salida=e.getMessage();}
System.out.println(salida);
}
} catch (Exception e) {e.printStackTrace();}
}
}
- Una vez compilada la implementación del objeto
javac ImplemSumador.java
- Generamos el stub y esqueleto con rmic
- Este herramienta siempre usa el classpath, por lo que aunque estemos en el directorio en el que están las clases necesarias, tendremos que pasarle como parámetro el nombre completo del package.
rmic ImplemSumador
- El resultado son las clases ImplemSumador_Skel e ImplemSumador_Stub
- El registro debe estar activo para poder aplicar llamadas bind() o rebind()
- Para iniciar el registro
- Puede indicarse el port de escucha (por defecto 1099)
start rmiregistry
- El registro debe ser capaz de localizar los ficheros .class para los stubs
- Este proceso se encuentra localizado en una máquina con una dirección IP concreta y escucha en un puerto concreto
- El comando bind() tiene como argumentos la dirección IP de la máquina sobre la que se ejecuta el registro y el port de escucha
- Localhost no funciona con RMI. Por tanto se debe dar el nombre de la máquina completo.
- RMI no funciona a no ser que se tenga una conexión TCP/IP establecida, aunque se esté trabajando en la máquina local exclusivamente.
- El nombre del servicio es arbitrario
- En nuestro caso coincide con el nombre de la clase, pero no tiene porque ser así
- Lo único importante es que sea único dentro del registro al que el cliente pregunta
- Si en un mismo registro tuviésemos dos servicios con el mismo nombre se lanzaría la excepción AlreadyBoundException.
- Para prevenir esta posibilidad podemos usar rebind() en lugar de bind() ya que ésta añade o reemplaza el nuevo servicio en función de que ya exista.
- Aunque se termine el main(), el objeto creado y registrado permanece vivo por el registro esperando por alguna petición de algún cliente a no ser que se llame a Naming.unbind().
- Cuando se está desarrollando es necesario parar/arrancar el registro cada vez que se genera una nueva versión del servidor.
- El registro se puede reiniciar desde la aplicación haciendo uso de LocateRegistry.createRegistry(puertoEscucha)
- Arrancamos el servidor (hay que indicar un fichero de politica de seguridad)
java -Djava.security.policy=politica ClienteSumador
- Podemos utilizar inicialmente una politica de seguridad permisiva
- Editamos el fichero 'politica'
grant {
permission java.security.Allpermision; // todo permitido
};
- El servidor debe crear una o más instancias del objeto remoto
- Debe registrar dichas instancias mediante bind() o rebind()
- Se ejecuta como cualquier aplicación Java 'tradicional'
- Interface remota simple que representa un servicio de consulta de la hora:
import java.rmi.*;
interface HoraExactaI extends Remote {
long obtenerHora() throws RemoteException;
}
- Implementación de la clase servidor
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.net.*;
public class HoraExacta
extends UnicastRemoteObject
implements HoraExactaI {
// Implementación de la interface:
public long obtenerHora() throws RemoteException {
return System.currentTimeMillis();
}
// Implementación del constructor para lanzar RemoteException:
public HoraExacta() throws RemoteException {
super();
}
// Registro del servicio:
public static void main(String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
HoraExacta he = new HoraExacta();
Naming.bind("//canyella:2005/HoraExacta", he);
System.out.println("Preparado...");
} catch(Exception e) {
e.printStackTrace();
}
}
}
- Generación de stub y esqueleto
rmic master.rmi.HoraPerfecta
- Implementación del cliente
import java.rmi.*;
import java.rmi.registry.*;
public class MuestraHoraExacta {
public static void main(String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
HoraExactaI t =
(HoraExactaI)Naming.lookup("//canyella:2005/HoraExacta");
for(int i = 0; i < 10; i++)
System.out.println("Hora Exacta = " +t.obtenerHora());
} catch(Exception e) {
e.printStackTrace();
}
}
}