lunes, 12 de septiembre de 2011

RMI - Mi primer ejemplo

Seguir los pasos de este tutorial:



1. Codigo fuente:

package rmi.server.suma;


import java.rmi.Remote;


/**
 * Es una interface java con todos los métodos que queramos poder invocar de forma remota, es decir, 
 * los métodos que queremos llamar desde el cliente, pero que se ejecutarán en el servidor.
 * 
 * Tiene que heredar de la interface Remote de java, si no el objeto no será remoto
 * 
 * Todos los parámetros y valores devueltos de estos métodos deben ser tipos primitivos 
 * de java o bien clases que implementen la interface Serializable de java
 * 
 * http://www.chuidiang.com/java/rmi/rmi.php
 * 
 * 
 * @author dciocca
 */
public interface InterfaceRemota extends Remote {


    public int suma (int a, int b) throws java.rmi.RemoteException; 
}





package rmi.server.suma;


import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;


/**
 * Esta clase remota debe implementar la interface remota que hemos definido (y por tanto implementará 
 * también la interface Remote de java).
 * 
 * Debe definir métodos como hashCode(), toString(), equals(), etc de forma adecuada a un objeto remoto
 * Es decir, debera tener en cuenta una serie de cosas más o menos complejas y de las que afortunadamente 
 * no tenemos que preocuparnos ya que haremos que esta clase herede de UnicastRemoteObject.
 * 
 * @author dciocca
 *
 *
 */
public class ObjetoRemoto extends UnicastRemoteObject implements InterfaceRemota { 

private static final long serialVersionUID = 1L;

    protected ObjetoRemoto() throws RemoteException {
super();
}


public int suma(int a, int b)  
    { 
        System.out.println ("sumando " + a + " + " + b + "..."); 
        return a+b; 
    }
}




package rmi.server.suma;


import java.rmi.Naming;


/**
 * Programa java que instancie y registre el objeto remoto.
 * 
 * Para su correcto funcionamiento debe estar ejecutandose el proceso 
 * rmiregistry
 * En caso que no este corriendo el demonio rmiregistry lanzara la siguiente 
 * excepcion:
 * java.rmi.ConnectException: Connection refused to host: localhost
 *  
 * @author dciocca
 *
 */
public class Servidor {

    public Servidor() {
        try {
       
// Este programa instanciara y registrara el objeto remoto. 
        // Es importante mantener la barra al final.
System.setProperty(
"java.rmi.server.codebase",
"file:/C:/Desarrollo/wc_rmi/Rmi_Test/bin/");

            // Se publica el objeto remoto
            InterfaceRemota objetoRemoto = new ObjetoRemoto();
            // El método rebind() registra el objeto en rmiregistry. 
            // Si ya estuviera registrado, lo sustituye por el que acabmos de pasarle
            Naming.rebind ("//localhost/ObjetoRemoto", objetoRemoto);
            
            System.out.println("Objeto registrado en el rmiregistry!!");
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new Servidor();
    }


}



package rmi.server.suma;

import java.rmi.Naming;

/**
 * Programa que utiliza el objeto de forma remota previamente registrado en el
 * rmiregistry
 * 
 * IMPORTANTE:
 * 
 * Para que el código del cliente compile necesita ver en su classpath a InterfaceRemota.class. 
 * Para que además se ejecute sin problemas los metodos del stub, necesita además ver a ObjetoRemoto_Stubs.class, 
 * por lo que estas clases deben estar accesibles desde el servidor o bien tener copias locales de ellas.
 *  
 * @author dciocca
 *
 */
public class Cliente {
/** Crea nueva instancia de Cliente */
    public Cliente() {
        try {
        // Lugar en el que está el objeto remoto.
        // Debe reemplazarse "localhost" por el nombre o ip donde
        // esté corriendo "rmiregistry".
        // Naming.lookup() obtiene el objeto remoto
            InterfaceRemota objetoRemoto = 
                (InterfaceRemota)Naming.lookup ("//localhost/ObjetoRemoto");
            
            // Se realiza la suma remota.
            System.out.print ("2 + 3 = ");
            System.out.println (objetoRemoto.suma(2,3));
            
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new Cliente();
    }

}


2. Generación del stub (en la pc servidor)


Para que desde un programa que se encuentra en una PC o JVM se pueda llamar a un método de una clase que está en otra PC o JVM, está claro que de alguna manera se debe enviar un mensaje por red de una pc a otra, indicando que se quiere llamar a determinado método de determinada clase y además pasar los parámetros de dicha llamada. Una vez ejecutado el método, el ordenador que lo ha ejecutado debe enviar un mensaje con el resultado. La clase de stubs es una clase con los mismos métodos que nuestro ObjetoRemoto, pero en cada uno de esos métodos está codificado todo el tema del envio del mensaje por red y recepción de la respuesta.
Es por eso que si se modifica la clase, también se debera modificar el stub para que el cliente tenga siempre el ultimo objeto con el que interactuar.

C:\Desarrollo\wc_rmi\Rmi_Test\src> set CLASSPATH=c:\Desarrollo\wc_rmi\Rmi_Test\bin

C:\Desarrollo\wc_rmi\Rmi_Test\src> rmic rmi.server.suma.ObjetoRemoto

Este comando nos generara la siguiente clase:

ObjetoRemoto_Stub.class


NOTA: Esto generara el .class bajo el directorio donde lanzamos RMIC respetando el package de la clase.


3. Ejecutamos el rmiregistry  (en la pc servidor)

C:\Desarrollo\wc_rmi\Rmi_Test\src> set CLASSPATH=
C:\Desarrollo\wc_rmi\Rmi_Test\src> rmiregistry

NOTA: Listo, queda escuchando el proceso. Ahora podemos instanciar el objeto remoto al rmiregistry



4. Ejecutamos el servidor.java para instanciar el objeto remoto al remiregistry  (en la pc servidor)




De esta manera queda registrado el objeto en el rmiregistry de la pc que actúa como servidor.


5. Copiamos o agregamos al classpath (tanto la InterfaceRemota.class como el stub generado previamente (en la pc que actúa como cliente)


Es necesario agregar tanto la interface InterfaceRemota.class (del servidor) y el stub (del servidor) en el mismo directorio donde vamos a ejecutar la clase Cliente.java (en el cliente). Es decir, estas 3 clases ya compiladas las copiamos a otra carpeta por ejemplo: c:\Desarrollo\Prueba\rmi\server\suma





6. Ejecutamos el cliente.java (en la pc que actua como cliente)




ANEXO 1: Todas las capturas juntas



Aquí vemos como en la ventana del servidor sale un "Sumando 2 + 3 = ..." y en la del cliente "2 + 3 = 5".


ANEXO 2: Paso por parametro de un objeto serializable ( no primitivo )


En estos casos, el objeto debera implementar la interface serializable y luego tendremos que tener una copia del .class del objeto utilizado como parametro en la pc o JVM donde este corriendo el cliente.

Para que la clase sea realmente Serializable, necesita además, que todos sus atributos sean también Serializable

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.

Existe otro pasaje de parámetros conocido como parámetros remotos pero que no se entrara en detalle.


ANEXO 3: Carga dinamica de clases

Como vimos en este ejemplo, cada clase Serializable que pasemos de un lado a otro (cliente/servidor) necesita tener dos copias del fichero .class, una en el cliente y otra en el servidor, de forma que ambos puedan usarlo. 

Habilitando un mecanismo de RMI llamado carga dinámica de clases, podemos evitar hacer estas copias. Por medio de este mecanismo, el cliente y el servidor dicen donde están sus clases Serializable. Este sitio debe ser accesible desde la red. De esta forma, cuando el servidor, por ejemplo, necesite una clase del cliente porque la recibe como parámetro, RMI se encargará de descargar esa clase automáticamente del sitio que ha indicado el cliente.






No hay comentarios:

Publicar un comentario