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.
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:
registry
conoce
al objeto, y que se quiere registrar, eliminar su registro, o buscar, también
puede omitirseEl 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"); }
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]);