En el tema 4 hemos visto un servidor "unicast", que constituye
la forma más sencilla de servidor RMI, basado en la clase UnicastRemoteObject
,
o exportados por su método estático exportObject
.
Una referencia remota desde un cliente a dicho servidor RMI es válida
mientras el servidor está en ejecución. Una vez que el servidor
termina, o deja de ser exportado, la referencia ya no es válida y no
puede usarse. Por lo tanto se producirá una excepción si el cliente
usa dicha referencia para invocar a un método remoto. Un cliente para
dicho servidor debe obtener la referencia remota (stub) cada vez que se ejecuta,
comenzando por una búsqueda del registro. Para evitar esta situación
se utilizan los denominados servidores activables.
Otro tipo de servidores son los denominados servidores IOOP. IOOP es el protocolo de transporte adoptado por el Object Management Group (OMG) para CORBA (Common Object Request Architecture), proporcionando interoperatividad con las implementaciones de objetos CORBA en varios lenguajes. RMI sobre IOOP permite a los programadores Java programar las interfaces RMI pero utilizar IOOP como capa de transporte subyacente. De esta forma, los programadores Java pueden participar de los servicios de red de CORBA con servicios y clientes implementados en cualquier lenguaje soportado por CORBA.
La activación automática de objetos remotos es un servicio de RMI que proporciona dos caracterísiticas básicas: la capacidad de instanciar (activar) objetos remotos bajo demanda de las peticiones de los clientes, y la capacidad de las referencias a objetos remotos de permanecer válidas aunque el servidor falle (crashes), haciendo las referencias persistentes. Estas caracterísiticas pueden ser bastante útiles en ciertas circunstancias, como por ejemplo:
La Figura 6.1 muestra el esquema de un sistema RMI que incluye el servicio de activación. Se puede observar que en un sistema RMI puede también estar presente un servidor web proporcionando un servicio codebase (localización global para ficheros .class y archivos .jar, accesibles desde los clientes y servidores RMI).
Figura 6.1. Sistema RMI con servicio de activación.
Si un objeto remoto se convierte en activable, puede ser registrado en el registro RMI sin ser instanciado realmente (a diferencia de los objetos remotos no activables). Para definir un servidor activable (puede crearse bajo demanda) los pasos a seguir son:
Activatable
del
paquete java.rmi.activation
Activatable
(si la clase
se deriva de Activatable
), o llamando a Activatable.exportObject
.Un servidor activable se registra mediante una llamada a Activatable.register
.
Cuando se ha registrado, se le asocia con un grupo de activación, un
codebase, y un (posiblemente nulo) argumento inicial.
Un grupo de activación es un grupo de cero o más servidores activables. Cada grupo de activación se ejecuta en una máquina virtual Java separada. Un grupo de activación se registra con el sistema de activación. Cuando se requiera, será creado por el sistema de activación en una de sus propias JVM. En esencia, los grupos de activación son una forma de definir colecciones de objetos remotos activables que prodrían compartir el mismo espacio físico de direcciones. Cada grupo de activación, además, es responsable de monitorizar, activar y reactivar los objetos que contiene.
El sistema de activación RMI es una instancia de la interfaz ActivationSystem
proporcionada por la implementación de Java. La implementación
de Sun JDK tiene la forma del programa rmid
. Una referencia al
sistema de activación se obtiene a través del método estático
ActivationGroup.getSystem
.
El codebase de un servidor activable es la URL en la que sus ficheros .class pueden encontrarse. Es el mismo concepto que el codebase normal de RMI.
El argumento de inicialización de un servidor activable es un
objeto MarshalledObject
, proporcionado por el constructor invocado
por el sistmea de activación durante la activación del objeto.
Este MarshalledObject
se construye inicialmente por el programa
que registra al servidor. Puede contener cualquier cosa que se quiera con tal
de que sea Serializable
; puede no contener nada, o puede ser null
.
Cuando un cliente recibe un stub desde un servidor activable, dicho
stub contiene una forma especial de objeto RemoteRef
, que
incialmente contiene un valor null
, y un ActivationID
para el objeto remoto. Cuando el stub se usa por primera vez en una invocación
remota tiene lugar un protocolo de interacción con el demonio del sistema
de activación rmid
en el host remoto. El resultado de esta
interacción es un RemoteRef
"vivo" que el stub
puede usar par invocar el método remoto. En definitiva, el sistema
de activación realiza los siguientes pasos (ver Figura 6.1):
ActivationID
ActivationGroup
asociado con élRemoteRef
asociada al servidor activable desde el
grupo, y devolver el control al stub clienteFigura 6.2. Diagrama de bloques del proceso de activación.
Además de extender la clase Activatable
(aunque también
puede no hacerlo), se requieren los siguientes pasos extra para definir un servidor
activable:
//Sintaxis del constructor ActivatableXXX(ActivationID id, MarshalledObject data) //ActivatableXXX se sustituye por el nombre de la clase
exportObject
Si el servidor activable se deriva de Activatable
, el constructor
anterior debe realizar una llamada a uno de los siguientes constructores de
su clase base Activatable
:
Activatable(ActivationID id, int port) //o bien, Activatable(ActivationID id, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)
dependiendo de si se quiere o no especificar socket factories cliente o servidor. La llamada tendrá lugar via una de las formas:
super(id,port); //o bien, super(id,port,csf,ssf);
Estos constructores son usados por el sistema de activación para activar un objeto que ha recibido una llamada a un método remoto, pero no está realmente activo. Son constructores de (re)activación.
La clase Activatable tiene otros dos constructores de incialización, utilizados cuando decidimos crear de forma proactiva uno de los métodos remotos y registrarlos con el servicio de activación de RMI.
Activatable(String src, MarshalledObject data, boolean restar, int port) //o bien, Activatable(String src, MarshalledObject data, boolean restar, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)
Si el servidor activable no se deriva de Activatable
, el constructor
debe realizar una llamada directamente (o indirectamente) a uno de los siguientes
métodos:
Activatable.exportObject(Remote remote,ActivationID id, int port) //o bien, Activatable.exportObject(Remote remote, ActivationID id, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)
Estos métodos son públicos y estáticos por lo que pueden ser accedidos por cualquier clase.
El proceso de registrar un servidor activable consiste en hacer que éste sea conocido por el sistema de activación como una entidad que puede ser creada y activada bajo demanda.
El proceso de realizar el registro es bastante complejo debido principalmente a la complicación añadida de la activación de los grupos e inicialización de los argumentos. Los pasos básicos a realizar para registrar un grupo de activación se muestran en el código siguiente:
String file =...; //URL del fichero de política de seguridad String location=...; //URL de codebase MarshalledObject data=...; //argumento de inicialización Properties props = new Properties(); props.put("java.security.policy",file); ActivationGroupDesc.CommandEnvironment ace = null; ActivationGroupDesc groupDesc = new ActivationGroupDesc(props,ace); ActivationGroupID gid = ActivationGroup.getSystem().registerGroup(groupDesc); ActivationDesc desc= new ActivationDesc (gid,MyActivatable.getName(),location,data); MyRemote rx= (MyRemote)Activatable.register(desc);
En este ejemplo:
MyRemote
es el nombre de un interfaz remoto MyActivable
es el nombre de una clase activable que implementa
la interfaz remotafile
, location
,
y data
se deja a la conveniencia de la aplicación data
puede ser null
Se construye una instancia de Properties
, en la que se debe especificar
los parámetros del security manager, incluyendo la localización
absoluta del fichero de política de seguridad para el grupo.
Se construye una instancia de ActivationGroupDesc.CommandEnvironment
,
normalmente con valor null, a menos que se quiera unsar un path u opciones
no estándar para el comando java ejecutado cuando se activa el grupo.
Se construye un ActivationGroupDesc
, y se registra este grupo
obteniendo su ID de grupo. El ActivationGroupID
obtenido se puede
usar posteriormente para registrar objetos dentro del grupo y para des-registrar
el grupo.
El método Activatable.register()
devuelve un stub
remoto del objeto activable. Puesto que dicho stub se utilizará
para un objeto que implemente la interfaz MyRemote
, podemos hacer
un cast de dicho stub a MyRemote
.
El proceso para registrar un servidor activable es el mismo aunque el
objeto se derive o no de Activable
.
Para registrar un servidor activable se necesita un descriptor de activación
(ActivationDesc
), la clase ActivationDesc
contiene
la información que el sistema de activación necesita para activar
un objeto; para crear un ActivationDesc
se necesita un identificador
de grupo (ActivationGroupID
) obtenido en el paso anterior, y la
clase del servidor activable y los datos iniciales si los hay.
En ambos casos, tanto para el registro de un grupo de activación como de un servidor activable se debe guardar el identificador de grupo o la referencia remota obtenida, típicamente en un fichero, via serialización.
La principal diferencia entre un objeto remoto activable y otro que no lo es
es que una referencia remota no necesita tener un objeto "vivo" asociado
a ella. Si un objeto activable no está en ejecución (por ejemplo
no ha sido construido todavía, o ha terminado, o ha sido recogido por
el recolector de basuras), una referencia remota de dicho objeto puede exportarse
al cliente. El cliente recib el el stub correspondiente, y puede realizar
invocaciones remotas a través de él. Cuando el primer método
es invocado, el servicio de activación en ejecución constatará
que el objeto no está activo y activará el objeto para el cliente.
Si el objeto no tiene una máquina virtual en ejecución, se inciará
una. El objeto entonces es activado usando información que ha sido registrada
por el sistema de activación. Dicha información incluye el nombre
de clase del objeto, una URL desde donde puede cargar el bytecode de
la clase si ésta no se encuentra en la variable CLASSPATH
local, y datos que se pasan al constructor de activación del objeto.
Una vez que el objeto ha sido activado tiene lugar la invocación del
método, y los resultados son marshaled y enviados de vuelta al
cliente.
Una vez que el objeto remotoe está en funcionamiento, las siguientes peticiones de ejecución se manejan de la forma usual. Si el objeto deja de funcionar por alguna razón, la siguiente petición provocará el inicio del servicio de activación de nuevo. Así, las referencias remotas a objetos activables pueden persistir a lo largo de de muchas "vidas" del servidor real del objeto.
IIOP (Internet Inter-ORB protocol) es el protocolo de transporte adoptado por OMG para CORBA. Proporciona ineroperatividad con las implementaciones de objetos CORBA en varios lenguajes.
La clase javax.rmi.PortableRemoteObject
se utiliza para definir
los denominados "objetos remotos portables" (servidores RMI que se
comunican mediante RMI/IIOP). Dicha clase es análoga a java.rmi.server.UnicastRemoteObject
,
proporcionando un constructor, un método exportObject
, y
un método unexportObject
. Las diferencias entre objetos
remotos portables y objetos remotos unicast o activables son:
rmic
con
el flag -iiop
La Figura 6.3 muestra el esquema de un sistema RMI sobre IIOP. En RMI/IIOP se utiliza el COS Naming service, en lugar del registro RMI. En RMI sobre IIOP no se puede utilizar el servicio de activación, ya que solamente está soportado con el protocolo JRMP.
Figura 6.3. RMI sobre IIOP.
SERVIDOR REMOTO IIOP
Para escribir un objeto remoto portable se tienen tres opciones:
PortableRemoteObject
RemoteObject
Un objeto remoto portable puede escribirse heredando de PortableRemoteObject
de la siguiente forma:
import javax.rmi.PortableRemoteObject; public class MyIIOPServer() extend PortableRemoteObject implements MyRemote { public MyIOOPServer() throws RemoteException {} //implementación de los métodos remotos }
Un objeto remoto portable puede escribirse heredando de PortableRemoteObject
de la siguiente forma:
import javax.rmi.PortableRemoteObject; public class MyIIOPServer() extend RemoteObject implements MyRemote { public MyIOOPServer() throws RemoteException { PortableRemoteObject.exportObject(this); } //implementación de los métodos remotos }
Un objeto remoto portable puede escribirse heredando de cualquier otra clase
excepto de UnicastRemoteObject
y Activatable
. Dicho
servidor debe exportarse él mismo o ser exportado por alguien. Normalmente
el servidor puede exportarse a sí mismo en la construcción llamando
a PortableRemoteObject.exportObject
.
import javax.rmi.PortableRemoteObject; public class MyIIOPServer() extend PortableRemoteObject implements MyRemote { public MyIOOPServer() throws RemoteException {} //implementación de los métodos remotos }
Para construir el servidor se ejecuta rmic
con la clase servidor
utilizando, como ya se ha comentado anteriormente el flag -iiop
.
Como resultado se generan las clases CORBA stub y tie (en lugar
de las clases JRMP stub y skeleton. CORBA utiliza clases tie
en la parte del servidor para procesar las llamadas entrantes y dirigirlas a
la implementación de la clase adecuada. Cada implementación de
clase requiere una clase tie.
Si es necesario generar IDL, se debe ejecutar rmic
con el flag
-idl
. Solamente será necesario IDL si se utilizan clientes
no Java.
Java proporciona la herramienta idlj
para permitir utilizar Java
desde las definiciones IDL de CORBA. La herramienta idlj opera en la dirección
IDL-Java, mientras que rmil -idl
opera el la dirección Java-IDL.
Es importante tener en cuenta que RMI/IIOP está diseñado como
la intersección de RMI y CORBA, no como la unión de las características
de ambas. Así por ejemplo, los objetos remotos portables no pueden ser
activables, y las definiciones de constantes en las interfaces remotas solo
pueden ser tipos primitivos o Strings
, y deben ser evaluados en
tiempo de compilación.
En teoría, una vez que hemos producido el IDL correspondiente para el servicio remoto, se puede implementar dicho servicio en otro lenguaje. En la práctica esto depende de si el sumnistrador de ORB soporta la caracterísitca Objects By Value de CORBA 2.3 para el lenbuaje requerido. Actualmente Objects By Value solamente está definido para Java y C++.
SERVIDOR REMOTO DUAL (JRMP/IIOP)
Es posible escribir un objeto remoto portable de forma que sorporte clientes IIOP y JRMP. Para ello se necesita lo siguiente:
UnicastRemoteObject
ni de PortableRemoteObject
.UnicastRemoteObject.exportObject
o PortableRemoteObject.exportObject
, dependiendo del protocolo
que se quiera soportar. En este caso, como se quiere soportar ambos protocolos,
se debe llamar a ambos métodos.InitialContexts
, uno para permitir el enlace con el registro
RMI, y otro con el servicio de nombres COS, y enlazar el servidor en ambos.-D
.Como ejemplo de servidor que soporta ambos servicios, ilustramos el siguiente:
import java.rmi.*; import java.rmi.server.*; import java.util.Properties; import javax.naming.InitialContext; importa javax.rmi; public class MyDualServer() implements MyRemote { public MyDualServer() throws RemoteException { UnicastRemoteObject.exportObject(this); //exporta e JRMP PortableRemoteObject.exportObject(this); //exporta a IIOP } //implementación de los métodos remotos public static void main(String[] args) throws Exception { System.setSecurityManager(new RMISecurityManager()); MyDualServer server = new MyDualServer(); //enlace al registro RMI Properties jrmpProps = new Properties(); jmpProps.put("java.naming.factory.initial", "com.sun.jndi.rmi.registry.RegistryContextFactory"); initialContextjrmpCtx = new InitialContext(jrmpProps); hrmpCtx.rebind("MyRemote",server); //enlace al nombrado COS Properties iiopProps = new Properties(); iiopProps.put("java.naming.factory.initial", "com.sun.jndi.rmi.cosnaming.CNCtxFactory"); initialContextiiopProps = new InitialContext(iiopProps); iiopProps.rebind("MyRemote",server); } }
CLIENTE IIOP
Con respecto al cliente, tal y como ya se ha visto, un cliente RMI es muy parecido a un cliente de un objeto local. Las únicas diferencias reales son la fuente del objeto y el uso de una interfaz intermedia para acceder al objeto así obtenido.
De igual forma, un cliente RMI/IIOP es muy parecido a un cliente RMI/JRMP. De nuevo las únicas diferencias reales son la fuente del objeto y el uso de una interfaz intermedia para acceder al objeto así obtenido.
Cuando se obtiene un objeto remoto, debe hacerse un cast al tipo de la interfaz remota esperada. Como hemos visto, con RMI/JRMP, ésto se realiza con un simple cast de Java:
MyRemote myRemote = (MyRemote) object;
En RMI/IIOP, se requiere una operación de red CORBA para comprobar e implementar el cast.
MyRemote myRemote = (MyRemote) PortableRemoteObject.narrow (object,MyRemote.class);
A continuacón se ilustra un ejemplo de cliente RMI/IIOP para el servidor dual anterior.
import java.rmi.*; import java.util.Properties; import javax.naming.InitialContext; public class MyRemoteClient() { public static void main(String[] args) throws Exception { System.setSecurityManager(new RMISecurityManager()); //búsqueda de nombrado COS via JNDI Properties props = new Properties(); props.put("java.naming.factory.initial", "com.sun.jndi.cosnaming.CNCtxFactory"); InitialContext ctx = new InitialContext(props); Object obj = ctx.lookup("MyRemote"); //obtención del objeto: realización del cast CORBA MyRemote mr = (MyRemote)PortableRemoteObject.narrow (obj,MyRemote.class); //ahora se pueden invocar a los métodos sobe mr } }
Se puede escribir un cliente no Java para un servidor RMI/IIOP. Cómo hacerlo depende del ORB utilizado.