4. Otros tipos de servidores

En el tema 1 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.

4.1. Servicio de activación de objetos

La activación automática de objetos remotos es un servicio de RMI que proporciona dos características 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ísticas pueden ser bastante útiles en ciertas circunstancias, como por ejemplo:

La Figura 6 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 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:

Se debe definir un constructor con una cierta forma en la clase servidor:

//Sintaxis del constructor
ActivatableXXX(ActivationID id, MarshalledObject data)
 
//ActivatableXXX se sustituye por el nombre de la clase

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 inicializació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 restart, int port) //o bien,
 
Activatable(String src, MarshalledObject data,
            boolean restart, 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.

Un servidor activable se registra mediante una llamada a Activatable.register. Para realizar el registro, 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 podrí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, el cual se pasa al constructor invocado por el sistema 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 para invocar el método remoto. En definitiva, el sistema de activación realiza los siguientes pasos (ver Figura 7):

Figura 7. Diagrama de bloques del proceso de activación.

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:

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 usar 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 proceso para registrar un servidor activable es el mismo aunque el objeto se derive o no de Activatable.

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.

Finalmente, 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.

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 recibe 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 iniciará 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 marshalled y enviados de vuelta al cliente.

Una vez que el objeto remoto 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.

4.2. RMI/IIOP

IIOP (Internet Inter-ORB protocol) es el protocolo de transporte adoptado por OMG para CORBA. Proporciona interoperatividad 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:

La Figura 8 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 8. RMI sobre IIOP.

SERVIDOR REMOTO IIOP

Para escribir un objeto remoto portable se tienen tres opciones:

Un objeto remoto portable puede escribirse heredando de PortableRemoteObject de la siguiente forma:

import javax.rmi.PortableRemoteObject;
 
public class MyIIOPServer() extends PortableRemoteObject
                            implements MyRemote 
{  public MyIOOPServer() throws RemoteException {}
     //implementación de los métodos remotos
}

Un objeto remoto portable puede escribirse heredando de RemoteObject de la siguiente forma:

import javax.rmi.PortableRemoteObject;
 
public class MyIIOPServer() extends 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() extends Object
                            implements MyRemote 
{  public MyIOOPServer() throws RemoteException {}
     PortableRemoteObject.exportObject(this);
     //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 rmic -idl opera en 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 suministrador de ORB soporta la característica Objects By Value de CORBA 2.3 para el lenguaje 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:

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;
import javax.rmi;
 
public class MyDualServer() implements MyRemote 
{  public MyDualServer() throws RemoteException {
     UnicastRemoteObject.exportObject(this); //exporta a 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();
      jrmpProps.put("java.naming.factory.initial",
            "com.sun.jndi.rmi.registry.RegistryContextFactory");
      InitialContext jrmpCtx = new InitialContext(jrmpProps);
      jrmpCtx.rebind("MyRemote",server);

      //enlace al nombrado COS
      Properties iiopProps = new Properties();
      iiopProps.put("java.naming.factory.initial",
            "com.sun.jndi.rmi.cosnaming.CNCtxFactory");
      InitialContext iiopCtx = new InitialContext(iiopProps);
      iiopCtx.rebind("MyRemote",server);
   }
}

CLIENTE RMI/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, esto 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 continuació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.