2. Uso del servicio de nombres RMI

El servicio de nombres RMI (RMI registry) es un simplemente un servidor que permite a una aplicación buscar objetos que están siendo exportados para su uso mediante llamadas a métodos remotos.

Una vez que el objeto ha sido localizado, ya se puede utilizar utilizando la misma sintaxis que una llamada a un método local. Para encontrar los objetos, RMI utiliza un servicio que mantiene una tabla de direcciones de objetos remotos que están siendo exportados por sus aplicaciones (ver Figura 2.1). A todos los objetos se les asigna nombres únicos que se utilizan para identificarlos. Algunos métodos pueden llamarse desde la interfaz rmi.registry.Registry, o desde la clase rmi.Naming, que permite añadir, eliminar y acceder a objetos remotos en la tabla de registro de objetos. El servidor del servicio de nombres registra los objetos mediante llamadas a bind() o rebind() sobre una instancia de un registro del objeto que está siendo exportado. Ya que todos los objetos tienen nombres únicos, una llamada a bind() a un regisro que contiene un nombre (String) que ya está registrado provocará una excepción. De forma alternativa, rebind() reemplaza un objeto antiguo con un nombre dado, con un nuevo objeto.

Figura 2.1. Tabla mantenida por RMI registry.

Hay dos formas de iniciar el servicio de nombres: una es usando la aplicación que proporciona java (rmiregistry), y otra es escribir nuestro propio servicio de nombres usando las clases e interfaces de java.rmi.*

Normalmente la aplicación de servicio de nombres se inicia como una aplicación en background. Por defecto, se ejecutará sobre el puerto 1099, pero se puede seleccionar otro puerto cuando se lanza la aplicación. Desde linux, se puede utilizar el siguiente comando:

rmiregistry &      //puerto 1099 por defecto, o bien

rmigegistry 2099 &     //usa el puerto 2099

Esta utilidad crea un objeto Registry que "escucha" en el puerto especificado (o en el puerto por defecto, 1099), ejecutando un bucle a la espera de procesos locales que registren objetos en él, o de clientes que conecten y busquen objetos RMI en dicho registro.

2.1. Localización de las clases Stub y Skeleton

Cuando se inicia el servicio de nombres, usa la variable de entorno CLASSPATH para encontrar las clases que necesita para dar servicio a los objetos remotos. Dicha variable debería contener una entrada con el directorio que contiene las clases stub y skeleton. Si no es así, es necesario especificar dicho directorio en una propiedad del sistema cuando se lanza el servicio de nombres. Para ello se utiliza el flag -D seguido de la propiedad java.rmi.server.Codebase y de una URL (Uniform Resource Locator) con el directorio que contiene a las clases stub y skeleton. Por ejemplo, la siguiente línea de comandos especifica que dicho directorio se encuentra en la dirección http://foo.com/MyStubs/

java -Djava.rmi.server.CodeBase=http://foo.com/MyStubs/MyServer

En el ejemplo anterior, se indica al servicio de nombres que debe buscar en el URL especificado para cargar las clases necesarias cuando se solicite alguna petición a un objeto remoto. Obviamente, el servicio de nombres solamente puede pasar referencias a objetos que pueda localizar. Se asume que cualquier objeto remoto exportado por MyServer y registrado en el servicio de nombres puede ser referenciado por las clases stub y skeleton accesibles por la URL especificada.

El valor devuelto por una búsqueda en el registro no es el servidor RMI en sí mismo, sino el stub remoto asociado.

Los servidores registry (objetos remotos que "mapean" nombres a objetos remotos) pueden referenciarse mediante una URL. Dicha URL se utilizará para localizar a un servidor registry ejecutándose en un puerto específico, en una determinada máquina, y con un nombre para un objeto remoto específico. La especificación del protocolo para este servicio es rmi, y el puerto estándar es el 1099.

La clase java.rmi.Naming proporciona la forma más sencilla de acceder a un registry remoto. Los argumentos name para los métodos de Naming toman la forma URL que acabamos de comentar, cuya sintaxis es:

[rmi:][//][host][:puerto][/nombre]

En donde:

2.2. Enlace, borrado y búsqueda de objetos del registro

El servicio de nombres RMI proporciona tres operaciones fundamentales: enlace (bind), borrado (unbind), y búsqueda (lookup). La operación bind añade una entrada (par nombre del servicio-dirección) al registro. La operación unbind elimina una entrada a partir del nombre del registro. Finalmente la operación lookup permite el uso del nombre de un servicio para encontrar la dirección de dicho servicio. Dichas operaciones se encuentran en el paquete java.rmi.Naming.

Cuando una aplicación enlaza (bind) un objeto al RMI registry se necesitan tres cosas: una referencia a una instancia del objeto a enlazar, la dirección del servidor del registro (registry server) incluyendo el número de puerto, y el nombre del objeto bajo el cual será registrado el objeto a enlazar. Como ya se ha comentado en el apartado 2.1, existen dos métodos para enlazar objetos al registro: bind(), y rebind(). Se pueden lanzar varias excepciones, tales como MalformedURLException, RemoteException, UnknownHostException, o AlreadyBoundException. Esto hace necesario el utilizar una sentencia try/catch con el método bind, tal y como se muestra en el código siguiente:

try {
  MyRemoteObject ro= new MyRemoteObject();
  java.rmi.Naming.bind("rmi://www.foo.com/MyObject", "ro");
} catch (Exception e) {
   //tratamiento de excepciones
}

Para eliminar un objeto del registro se utiliza el método unbind(). Antes de eliminar un objeto, puede ser conveniente ver si está ya registrado, para eso se proporciona el método list(), que devuelve un array de Strings que contiene los nombres de los objetos actualmente registrados. El siguiente código utiliza los métodos list() y unbind() para eliminar un determinado objeto del registro RMI, se puede observar que también pueden provocarse varias excepciones que es conveniente capturar:

String[] RemoteObjects;
PrintStream out = System.out;
 
try {
  remoteObjects= Naming.list("rmi://www.foo.com/");
} catch (RemoteException e) {
   out.println("No he podido localizar el registro");
  }
  catch (MalformedURLException e) {
   out.println("No he podido analizar el string URL");
  }
  catch (UnknownHostException e) {
   out.println("No puedo localizar el host");
  }

  for (int c; c< remoteObjects.lenght; c++}
    if (remoteObjects[c].equals("MyObject"))
      try {
        Naming.unbind(u);
      }
      catch (Exception e) {
        //tratamiento de excepciones
      };

Para buscar un objeto en el registro se utiliza el método lookup(). Dicho método tiene como parámetro la URL del registro, y el nombre del objeto a buscar. El siguiente trozo de código intenta buscar en el registro el servidor de MyRemoteObject (que es un objeto registrado en la máquina www.foo.com), cuyo nombre es MyObject.

MyRemoteObject o;
 
try {
  o = (MyRemoteObject)Naming.lookup("rmi://www.foo.com/MyObject");
} catch (NotBoundException e) {
   out.println("No he podido localizar el objeto en el registro");
  }
  catch (RemoteException e) {
   out.println("No he podido localizar el registro");
  }
  catch (MalformedURLException e) {
   out.println("No he podido analizar el string URL");
  }
  catch (UnknownHostException e) {
   out.println("No puedo localizar el host");
  }

2.3. Paquete java.rmi.registry

Se puede controlar el registro a más bajo nivel utilizando el interfaz java.rmi.registry.Registry. El paquete java.rmi.registry contiene también la clase LocateRegistry que puede usarse para crear o encontrar un registro.

El método LocateRegistry.getRegistry() devuelve una referencia de una instancia de un registro ejecutándose en un sistema remoto.

El método LocateRegistry.createRegistry() permite crear un registro propio, que puede ejecutarse dentro de nuestra aplicación en lugar de utilizar la aplicación que propociona JDK.

Un registro puede ser iniciado solamente en la máquina local en la que se ejecuta la aplicación que lo inicia. El registro solamente puede utilizar los puertos por encima de 1024 si no se tienen privilegios de superusuario. Se requiere al menos un registro por host en los que se ejecutan los servidores RMI. La razón de ello es que el registro solamente permite llamadas a bind, rebind, y unbind originadas en el mismo host por razones de seguridad.

A continuación mostramos un ejemplo en el que se crea un registro propio, se enlaza a una instancia de un objeto remoto, y se muestra un listado de nombres registrados:

Registry reg;
String list;
PrintStream out = System.out;



try {
  reg = java.rmi.registry.LocateRegistry.createRegistry(5000);
} catch (RemoteException e) {
   out.println("No he podido localizar el objeto en el registro");
  }
  catch (RemoteException e) {
   out.println("No he podido crear el registro, pruebe con otro puerto");
  }

try {
  MyRemoteObjet ro = new MyRemoteObject();
  reg.rebind("MyObject", ro);
} catch (RemoteException rx) {
   out.println("No he podido crear el objeto remoto");
  }
  catch (AccessException ax) {
   out.println("No he podido crear el registro, pruebe con otro puerto");
  }
  catch (MalformedURLException e) {
   out.println("Operación rebind() no permitida");
  }

try {
  list = reg.list();
} catch (RemoteException rx) {
   out.println("La operación list() ha fallado");
  }
  catch (AccessException ax) {
   out.println("Acceso denegado");
  }

for (int c= 0; c< list.length; c++)
   out.println(list[c]);