7. Objetos Distribuidos

 

7.1. Objetos distribuidos en Java

Para comunicar aplicaciones distribuidas en Java, se emplea la tecnología RMI (Remote Method Invocation), que permite a un objeto ejecutando en una máquina virtual Java invocar a métodos de un objeto en otra máquina virtual Java, y obtener objetos existentes en dicha máquina remota.

Toda aplicación RMI normalmente se descompone en 2 partes: un cliente y un servidor.


RMI trata de forma distinta a los objetos remotos, con respecto a cómo trata a los locales: en lugar de hacer una copia local del objeto remoto, lo que hace es obtener un Stub y un Skel. El Stub es la representación local del objeto remoto (es una referencia local en el cliente al objeto remoto). Por otra parte, el Skel es lo correspondiente en la parte del servidor, es la parte del objeto remoto en el servidor que comunica con el Stub que hay en el cliente, y entre ambos circulan los datos por la red.

7.2. Creaci�n de un servidor RMI

Un servidor RMI consiste en definir un objeto remoto que va a ser utilizado por los clientes. Para crear un objeto remoto, se define un interfaz, y el objeto remoto será una clase que implementa dicha interfaz. Veremos ahora los pasos a seguir para crear un servidor de ejemplo.

1. Definir el interfaz remoto

Cuando se crea un interfaz remoto:

Veamos un ejemplo:
public interface MiInterfazRemoto extends java.rmi.Remote
{
  public void miMetodo1() throws java.rmi.RemoteException;
  public int miMetodo2() throws java.rmi.RemoteException;
}
2. Implementar el interfaz remoto
public class MiClaseRemota  
extends java.rmi.server.UnicastRemoteObject
implements MiInterfazRemoto
{
   public MiClaseRemota() throws java.rmi.RemoteException
   {
    ... // Codigo del constructor
   }
     
   public void miMetodo1() throws java.rmi.RemoteException
   {
    ... // Aqu� pondremos el c�digo que queramos
   }
     
   public int miMetodo2() throws java.rmi.RemoteException
   {
    ... // Aqu� pondremos el c�digo que queramos
   }
     
   public void otroMetodo()
   {
    ... // Si definieramos este m�todo no podr�a llamarse 
	// remotamente al no ser del interfaz remoto
   }
     
   public static void main(String[] args)
   {
     if (System.getSecurityManager() == null)
      System.setSecurityManager(new RMISecurityManager());
     try
     {
       MiInterfazRemoto mir = new MiClaseRemota();
       Naming.rebind("//" + 
	   java.net.InetAddress.getLocalHost().getHostAddress() + 
	   ":" + args[0] + "/PruebaRMI", mir);
     } catch (Exception e) { }
   }
}
La clase implementa la interfaz que hemos definido previamente. Además, hereda de UnicastRemoteObject, que es una clase de Java que podemos utilizar (no tenemos por qué) como superclase para implementar objetos remotos, puesto que redefine muchos métodos de Object para poder utilizarlos remotamente.

Luego, dentro de la clase, definimos un constructor (que lanza la excepción RemoteException porque también la lanza la superclase UnicastRemoteObject), y los métodos de la/las interfaz/interfaces que implemente, además de los métodos adicionales que queramos definir (que no podr�n llamarse remotamente, ser�n locales a la m�quina servidor).

Finalmente, en el método main se define el código para:

Naming.rebind(nombre, objeto_remoto);

de forma que ante una petición del objeto se buscará por su nombre. Dicho nombre contiene la direcci�n IP (y opcionalmente el puerto) donde se colocar� el servidor RMI, y el nombre que identifica al objeto remoto, quedando:

"//direccion_IP:puerto/nombre"

En este caso:

"//" + 
java.net.InetAddress.getLocalHost().getHostAddress() + 
":" + 
args[0] + 
"/PruebaRMI"

La clase InetAddress se utiliza para obtener la direcci�n IP de la m�quina local. Luego se concatena con el puerto por el que se escuchar�n las peticiones de los clientes (que se pasa como par�metro del main(), y por defecto es el 1099). El final de la cadena (PruebaRMI) es el nombre que le damos al objeto RMI. Cuando un cliente quiera un objeto de este tipo, buscar� un objeto con el nombre indicado, como veremos m�s adelante.

Notar que el método main() se coloca dentro de la misma clase servidor por comodidad. Podría definirse otra clase aparte que fuese la que registrara el objeto remoto.
 

3. Compilar y ejecutar el servidor

Ya tenemos definido el servidor. Para compilar sus clases seguimos los pasos:

Para ejecutar el servidor, seguimos los pasos:
Donde se da permiso para conectar y aceptar conexiones con cualquier puerto no reservado, y conectar por el puerto 80.
start rmiregistry <puerto>
(Windows)
rmiregistry <puerto> &		
(Linux)
java -Djava.rmi.server.hostname=127.0.0.1
     -Djava.rmi.server.codebase=file:///home/pepe/
     -Djava.security.policy=java.policy
     MiClaseRemota 1200
Donde con �Djava.rmi.server.hostname se indica el nombre del servidor (para asegurarnos de que RMI lo obtiene), con �Djava.rmi.server.codebase se indica la URL donde están las clases que se envían a otras máquinas (es opcional, podría no ser necesario; haría falta si el cliente no encuentra las clases del servidor a las que debe acceder), y finalmente �Djava.security.policy indica el fichero de política de seguridad que se tiene (el fichero creado antes). Al servidor se le pasaba como par�metro, recordemos, el puerto por el que escuchar peticiones.

 

7.3. Creaci�n de un cliente RMI

Vamos a definir el cliente que accederá a el/los objeto/s remoto/s que creemos.

1. Definir la clase para obtener los objetos remotos que se quieran

La siguiente clase obtiene un objeto de tipo MiInterfazRemoto, implementado en el servidor:

public class MiClienteRMI
{
  public  static void main(String[] args)
  {
     if (System.getSecurityManager() == null)
        System.setSecurityManager(new RMISecurityManager());
     try
     {
        MiInterfazRemoto mir =
        (MiInterfazRemoto)Naming.lookup("//" + args[0] + ":" + 
					args[1] +"/PruebaRMI");
        mir.miMetodo1();
        int i = mir.miMetodo2();
     } catch (Exception e) { e.printStackTrace(); }
  }
}
Donde se siguen los pasos:
"//direccion_IP:puerto/nombre"


2. Compilar y ejecutar el cliente

Ya tenemos definido el cliente. Para compilarlo seguimos los pasos:

Para ejecutar el cliente, seguimos los pasos:
Donde los parámetros sirven para lo mismo que en el servidor. Al cliente se le pasan como par�metros la direcci�n IP del servidor y el puerto por el que estar� escuchando.

 

7.4. Acceso a objetos remotos. Serializaci�n

Los argumentos y los tipos de retorno que utilicemos en objetos remotos pueden ser de casi cualquier tipo Java, siempre que dicho tipo sea:

Hay algunos objetos Java que no son serializables (por ejemplo, imágenes), y por lo tanto no pueden ser pasados a ni devueltos por objetos remotos, es decir, no pueden circular por la conexi�n RMI. Pero muchas clases de utilidad sí definen dicho interfaz, y también podemos definirnos nuestras propias clases que lo implementen.

Por ejemplo, si queremos obtener un objeto de tipo MiParametro de un método de un objeto remoto:

public interface MiInterfazRemoto2 extends java.rmi.Remote
{
  public MiParametro miMetodo() throws java.rmi.RemoteException;
  ...
}
Lo que hacemos es definir ese objeto como serializable:
public class MiParametro implements java.io.Serializable
{
  ...
}
Tenemos que asegurarnos también de que los campos que contiene son objetos serializables a su vez. No podemos, por ejemplo, encapsular un campo Image en una clase serializable, puesto que el campo Image no lo es.

 

7.5. Servidores activables

Hasta Java 1.2, el objeto remoto a compartir se creaba al principio, y tenía que estar siempre a disposición de los clientes. A partir de la versión 1.2 se pueden crear objetos remotos cuando un cliente lo solicite, con el consiguiente ahorro en el consumo de recursos. Esto se consigue partiendo de la clase java.rmi.activation.Activatable . y del demonio rmid, que se encarga de gestionar el objeto remoto. Veremos ahora los pasos para construir este tipo de servidores.

1. Definir el interfaz remoto

Este paso es idéntico al explicado para el servidor normal, visto más arriba.

2. Implementar el interfaz remoto

La clase que implementa el interfaz remoto tiene algunos cambios con respecto a la definida para el servidor normal. Los pasos a seguir son:

Un ejemplo de servidor de este tipo sería:
import java.rmi.*;
import java.rmi.activation.*;
                                   
public class MiClaseRemota2 
extends java.rmi.activation.Activatable
implements MiInterfazRemoto
{
  public MiClaseRemota2(ActivationID a, MarshalledObject m) 
  throws java.rmi.RemoteException
  {
    super(a, 0);
  }
                                   
  public void  miMetodo1() throws java.rmi.RemoteException
  {
    ... // Aqu� pondremos el c�digo que queramos
  }
                                   
  public int  miMetodo2() throws java.rmi.RemoteException
  {
    ... // Aqu� pondremos el c�digo que queramos
  }
                                   
  public static void main(String[] args) throws Exception
  {
     System.setSecurityManager(new RMISecurityManager());
                                   
     Properties p = new Properties();
     p.put("java.security.policy", "/rmi/servidor2/java.policy");

     ActivationGroupDesc.CommandEnvironment ace = null;
     ActivationGroupDesc ejemplo = 
	new ActivationGroupDesc(p, ace);

     ActivationGroupID agi = 
	ActivationGroup.getSystem().registerGroup(ejemplo);
     MarshalledObject m = null;

     ActivationDesc desc = new ActivationDesc 
			   (agi, "MiClaseRemota2", 
			   "file://C:/rmi/servidor2/", m);
   
     MiInterfazRemoto mir = 
	(MiInterfazRemoto)Activatable.register(desc);
     Naming.rebind("//" + 
	java.net.InetAddress.getLocalHost().getHostAddress() + 
	":" + args[0] + "/PruebaRMI");
     System.exit(0);
  }
}
El servidor es muy parecido al anterior, salvo porque en el constructor se pasan dos parámetros, y porque el método main() tiene una forma distinta de registrarlo:

Con eso, se obtiene un objeto remoto (MiInterfazRemoto), llamando al m�todo register() de Activatable, pas�ndole como par�metro el ActivationDesc creado con todos los pasos anteriores. Despu�s ya se tiene el m�todo rebind() visto en el servidor primero.
 

3. Compilar y ejecutar el servidor

Para compilar el servidor se siguen los mismos pasos que para el servidor normal. Para ejecutarlo también, salvo que se debe lanzar también el demonio rmid (antes o despu�s de lanzar el rmiregistry):

start rmid -J-Djava.security.policy=java.policy	
(Windows)
rmid -J-Djava.security.policy=java.policy &	
(Linux)
Se debe indicar el fichero de política de seguridad para indicar al demonio que se permite acceder a dicho fichero como parámetro.
 

 

NOTA: tanto los par�metros -Djava... que se utilizan para ejecutar servidores y clientes como la gesti�n de seguridad mediante ficheros java.policy y objetos SecurityManager son elementos opcionales. Se mencionan aqu� para tener la forma general de una aplicaci�n RMI, aunque dependiendo del tipo de aplicaci�n se pueden omitir unos elementos u otros.

 

Aqu� ten�is un fichero ZIP con el ejemplo completo de la sesión:

compilar
ejecutar 1200

(Windows)
. ./compilar.sh
. ./ejecutar.sh 1200

(Linux)
ejecutar 192.168.12.1 1200		
(Windows)
. ./ejecutar.sh 192.168.12.1 1200	
(Linux)

Pod�is probar a conectar un cliente con un servidor RMI colocado en una m�quina distinta, y probar as� la verdadera utilidad de RMI.