Ejemplos java y C/linux

Tutoriales

Enlaces

Licencia

Creative Commons License
Esta obra está bajo una licencia de Creative Commons.
Para reconocer la autoría debes poner el enlace http://www.chuidiang.org

PASO DE PARÁMETROS Serializable Y Remote EN RMI

Una vez que hemos visto un ejemplo simple de rmi, vamos a ampliar un poco la cosa.

A un método de un objeto remoto, además de pasar tipos primitivos int, char, etc como vimos en el ejemplo simple, también podemos pasarle objetos que implementen la interface Serializable o la interface Remote. Veremos que el primer caso es el equivalente al paso de parámetro por valor en rmi y el segundo al paso de parámetro por referencia.

Aunque aquí y en el ejemplo sólo hablemos de los parámetros de los métodos remotos, todo es válido para el valor devuelto con return y ¿por qué no? con la excepcion RemoteException de dichos métodos.

PARÁMETROS Serializable

Un objeto Serializable es el que implementa la interface Serializable. Como dicha interface no tiene ningún método, basta con poner que nuestra clase la implementa y ya está.

Para que la clase sea realmente Serializable, necesita además, que todos sus atributos sean también Serializable. Esto quiere decir que sus atributos pueden ser tipos primitivos (int, char, float, etc), clases de java normalitas  (consultar en la API de Java si implementan la interface, por ejemplo Integer, String) o bien clases nuestras que también la implementen.

Cuando pasamos un objeto Serializable como parámetro de un método remoto, java se encarga de convertir ese objeto a bytes (serializar el objeto), enviarlo por red y hacerlo llegar al otro lado (el servidor). Allí, java se encarga de convertir nuevamente esos bytes a un objeto (deserializar el objeto). Si no usamos carga dinámica de clases, tanto el cliente como el servidor deben tener esa clase en su CLASSPATH para poder usarla.

En este momento, existen dos instancias distintas de la clase, una copia de la otra. Si el cliente o el servidor tocan su copia, la otra no se ve afectada. Pasar un objeto Serializable equivale al paso por valor.

En el ejemplo simple de rmi, vamos a modificar el InterfaceRemoto para que en vez de dos parámetros int admita un objeto Serializable que los contenga en su interior. Por aquello de la "elegancia" en el código, haremos que el parámetro recibido sea una interface, la InterfaceSumandos.

/* La InterfaceRemota admite ahora otra Interface como parámetro */
public interface InterfaceRemota extends Remote {
    public int suma (InterfaceSumandos sumandos) throws RemoteException;
    ...
}

/* La interface que hace de parámetro del método remoto */
public interface InterfaceSumandos extends Serializable
{
    public int getSumando1();
    public int getSumando2();
}

La implementación de esta clase puede ser así

/* Una implementación para el parámetro del método remoto */
public class Sumando implements InterfaceSumandos
{
    private int a=0;
    private int b=0;
    public Sumando (int a, int b)    {
        this.a=a;
        this.b=b;
    }
    public int getSumando1()    {
        return a;
    }
    public int getSumando2()    {
        return b;
    }
}

Se compila normalmente y necesitamos copiar los class tanto en el servidor como en el cliente.

PARÁMETROS Remote

Un objeto Remote es el que implementa la interface Remote. Aunque aparentemente con esta inteface no tenemos que hacer nada (igual que con la interface Serializable), en realidad si debemos hacer nuestra clase de una cierta forma. Para evitarnos hacer este código especial, lo mejor es hacer heredar nuestra clase de UnicastRemoteObject. Con ello ya implementa la interface Remote y tenemos hecho todo el código necesario hecho.

Cuando un objeto es Remote, además de compilarlo de la forma habitual, necesitamos compilarlo con el compilador rmic, que viene con java y está en JAVA_HOME/bin. Una vez obtenido nuestro ObjetoRemote.class, pasamos el compilador rmic de esta forma

$ rmic paquete.ObjetoRemote

No ponemos el fichero ObjetoRemote.class, sino el nombre de la clase. Esto generará un fichero ObjetoRemote_Stub.class.

Cuando pasamos un objeto Remote como parámetro de un método rmi, java se encarga de enviar el ObjetoRemote_Stub al otro lado. El servidor reconstruye este ObjetoRemote_Stub. Por ello, ObjetoRemote_Stub.class debe estar en el CLASSPATH de ambos.

Cuando el servidor llama a cualquier método de la clase ObjetoRemote_Stub, que son los mismos que tiene ObjetoRemote, la clase ObjetoRemote_Stub se encarga de transmitir la llamada a través de la red al ObjetoRemote original que está en el cliente. Por ello, si el servidor cambia algo llamando a ObjetoRemote_Stub, se cambia en el objeto original del cliente.

Si el servidor trata de obtener un valor de ObjetoRemote_Stub a través de algún método, esta clase se encarga de pedir el valor a través de red al original.

Por ello, pasar objetos Remote es equievalente al paso de parámetros por referencia. Sólo existe un objeto real y hay otro ficticio que delega todas las llamadas, a través de red, en el original

Seguimos modificando el ejemplo simple de rmi, añadiendo un nuevo método a InterfaceRemota para que admita, por ejemplo, un Contador remoto. Nuevamente, como nos pasamos de "elegantes", otra interface para ello

/* Añadimos un nuevo método a InterfaceRemota */
public interface InterfaceRemota extends Remote {
    public int suma (InterfaceSumandos sumandos) throws RemoteException;
    public void tomaContador (InterfaceContador contador) throws RemoteException;
}

/* Y aquí la nueva interface que hará de parámetro del nuevo método */
public interface InterfaceContador extends Remote
{
    public void incrementa() throws RemoteException;
}

Y la implementación de esto puede ser

/* Una implementación para este nuevo parámetro */
public class Contador extends UnicastRemoteObject implements InterfaceContador
{
    private int contador;
    public Contador () throws RemoteException    {
        contador=0;
    }
    public void incrementa() throws RemoteException    {
        System.out.println (contador + " + 1 = " + ++contador);
    }
}

Al heredar de UnicastRemoteObject nos vemos obligados a hacer un constructor que lance una RemoteException. Escribimos como se incrementa el contador cada vez que llamamos al método para ver en la pantalla de quién (servidor o cliente) se realiza el incremento.

Este Contador debemos compilarlo de la forma normal y luego, como comentamos antes, con rmic para obtener el Contador_Stub.class. Este último es el que tienen que tener cliente y servidor.

SEGUIMOS MODIFICANDO EL EJEMPLO SIMPLE

Ahora sólo nos queda modificar el ObjetoRemoto del ejemplo para que implemente los dos métodos de InterfaceRemota.

El método de suma lo modificamos lo justo para que sirva funcionando como antes.

El método de tomaContador() haremos que incremente el contador recibido 10 veces, una cada segundo. La nueva implementación de este ObjetoRemoto puede ser así

/* Y ahora el ObjetoRemoto al completo */
public class ObjetoRemoto extends UnicastRemoteObject
    implements InterfaceRemota
{
    public ObjetoRemoto () throws RemoteException    {
        super();
    }

    public int suma(InterfaceSumandos sumandos)     {
       System.out.println("Sumando " + sumandos.getSumando1() + " + " +
             sumandos.getSumando2() + " = ...");
        return sumandos.getSumando1()+sumandos.getSumando2();
    }
  
    public void tomaContador (InterfaceContador contador)    {
       final InterfaceContador elContador=contador;
       Thread hilo = new Thread (new Runnable()
      {
         public void run()
         {
            int i=0;
            while (i<10)
            {
               try
               {
                  i++;
                  elContador.incrementa();
                  Thread.sleep(1000);
               }
               catch (Exception e)
               {
                   e.printStackTrace();
               }
            }
         }
      });
       hilo.start();
    }
}

Bueno, no te asustes con el método tomaContador(). Simplemente lanza un hilo que incrementa el contador y se duerme un segundo y así 10 veces. Luego termina.

Este ObjetoRemoto se compila normal y con rmic. El correspondiente ObjetoRemoto_Stub tiene que estar en cliente y servidor.

LIBERAR EL OBJETO REMOTO QUE EL CLIENTE PASA AL SERVIDOR

Simplemente un detalle. Cuando el cliente pasa Contador al servidor, servidor de alguna forma, a través de red, tiene una referencia al Contador de cliente. Aunque cliente termine su ejecución, mientras esta referencia esté, el código de cliente no termina.

Para que el cliente pueda avisar a rmi de que libere ese objeto, tiene el método unexportObject() de la clase UnicastRemoteObject. Por ello, el código del cliente espera unos 10 segundos para que el servidor tenga tiempo de incrementar el contador y luego libera dicho objeto. Si lo liberamos antes de tiempo, el servidor comenzará a dar excepciones cada vez que intente incrementar el contador.

En la clase Cliente, hay por tanto, este trozo de código

Contador unContador=new Contador();  // Se crea el contador
objetoRemoto.tomaContador(unContador);   // Se le pasa al servidor
Thread.sleep(10000);  // 10 segundos
UnicastRemoteObject.unexportObject(unContador,false);  // Se libera el contador.

El flag final indica si se debe liberar inmediatamente (true) o esperar que no esté ocupado haciendo algo (false).

LOS FUENTES DEL EJEMPLO

En RMI_SERIALIZABLE.zip tienes todos los fuentes y clases compiladas del ejemplo. Si lo desempaquetas, saldrán dos directorios, src_cliente y src_servidor. Para ejecutar, puedes ver las instrucciones al final del ejemplo simple de rmi.

Supuestamente debería ver la suma, igual que en el ejemplo anterior y deberías ver como el servidor va incrementando el contador y como el cliente se entera de ese incremento, escribiéndolo en pantalla.

Ahora podemos pasar a ver la carga dinámica de clases en rmi.

Estadísticas y comentarios

Numero de visitas desde el 4 Feb 2007: