Introducción
Ya vimos en un ejercicio anterior cómo conectar un socket cliente y uno servidor para mandar mensajes.
Enviar mensajes no tiene gran utilidad, ¿no sería mejor poder llamar a algunas funciones en el servidor? esto es justamente lo que hace RMI en Java. Declaramos el servidor, el cliente, y la interfaz que servirá como “pegamento” entre estos dos y que se encargará del paso de parámetros.
Después, desde el cliente llamamos a las funciones declaradas anteriormente.
De esta manera, todos los métodos se ejecutarán en el servidor. En este caso haremos una calculadora, para no hacer el post muy largo, pero podemos hacer miles de cosas; se me ocurre, por ejemplo, conectar a una base de datos.
En fin, vamos allá.
Ejemplo de RMI
Interfaz remota
Comencemos con la interfaz remota, como lo dije hace un momento esta servirá como pegamento. Veamos una cosa interesante, como se puede ver dice “public interface” en lugar de “public class“.
Esto es porque, aunque suene redundante, es una interfaz y recordemos que éstas sólo sirven para ser sobrescritas más tarde. ¿Y en dónde las sobrescribiremos? en el código del servidor.
import java.rmi.Remote;
import java.rmi.RemoteException;
/*
Declarar firma de métodos que serán sobrescritos
*/
public interface Interfaz extends Remote {
float sumar(float numero1, float numero2) throws RemoteException;
float restar(float numero1, float numero2) throws RemoteException;
float multiplicar(float numero1, float numero2) throws RemoteException;
float dividir(float numero1, float numero2) throws RemoteException;
}
En este caso, como haremos una calculadora sólo declaramos la firma de 4 métodos: sumar, restar, multiplicar, y dividir.
Todos reciben dos parámetros flotantes, y todos devuelven un dato del mismo tipo.
Además, indicamos que el método puede lanzar una excepción de tipo RemoteException.
Servidor
Ahora veamos el servidor. Este se encarga de exportar el objeto, sobrescribir funciones de la interfaz y escuchar peticiones del cliente.
import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class Servidor {
private static final int PUERTO = 1100; //Si cambias aquí el puerto, recuerda cambiarlo en el cliente
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
Remote remote = UnicastRemoteObject.exportObject(new Interfaz() {
/*
Sobrescribir opcionalmente los métodos que escribimos en la interfaz
*/
@Override
public float sumar(float numero1, float numero2) throws RemoteException {
return numero1 + numero2;
};
@Override
public float restar(float numero1, float numero2) throws RemoteException {
return numero1 - numero2;
};
@Override
public float multiplicar(float numero1, float numero2) throws RemoteException {
return numero1 * numero2;
};
@Override
public float dividir(float numero1, float numero2) throws RemoteException {
return numero1 / numero2;
};
}, 0);
Registry registry = LocateRegistry.createRegistry(PUERTO);
System.out.println("Servidor escuchando en el puerto " + String.valueOf(PUERTO));
registry.bind("Calculadora", remote); // Registrar calculadora
}
}
Como observamos, ya hemos sobrescrito los métodos con el cuerpo real de la función. La anotación @Override no es necesaria, pero es una buena práctica indicar que estamos sobrescribiendo una función.
Finalmente escuchamos en localhost (cosa que no podemos cambiar) en el puerto definido en la constante PUERTO.
Cliente
Para terminar con el código veamos ahora el cliente:
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Scanner;
public class Cliente {
private static final String IP = "192.168.1.15"; // Puedes cambiar a localhost
private static final int PUERTO = 1100; //Si cambias aquí el puerto, recuerda cambiarlo en el servidor
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry(IP, PUERTO);
Interfaz interfaz = (Interfaz) registry.lookup("Calculadora"); //Buscar en el registro...
Scanner sc = new Scanner(System.in);
int eleccion;
float numero1, numero2, resultado = 0;
String menu = "\n\n------------------\n\n[-1] => Salir\n[0] => Sumar\n[1] => Restar\n[2] => Multiplicar\n[3] => Dividir\nElige: ";
do {
System.out.println(menu);
try {
eleccion = Integer.parseInt(sc.nextLine());
} catch (NumberFormatException e) {
eleccion = -1;
}
if(eleccion != -1){
System.out.println("Ingresa el número 1: ");
try{
numero1 = Float.parseFloat(sc.nextLine());
}catch(NumberFormatException e){
numero1 = 0;
}
System.out.println("Ingresa el número 2: ");
try{
numero2 = Float.parseFloat(sc.nextLine());
}catch(NumberFormatException e){
numero2 = 0;
}
switch (eleccion) {
case 0:
resultado = interfaz.sumar(numero1, numero2);
break;
case 1:
resultado = interfaz.restar(numero1, numero2);
break;
case 2:
resultado = interfaz.multiplicar(numero1, numero2);
break;
case 3:
resultado = interfaz.dividir(numero1, numero2);
break;
}
System.out.println("Resultado => " + String.valueOf(resultado));
System.out.println("Presiona ENTER para continuar");
sc.nextLine();
}
} while (eleccion != -1);
}
}
En el cliente imprimimos un menú, y dejamos que el usuario elija. Los try/catch son por si existe algún número mal formado, o por si dejan la cadena vacía, etcétera.
Como vemos, podemos llamar a los métodos remotos como si se trataran de métodos locales, simplemente usando a la interfaz.
Ejecución
El código no sirve para nada si no lo compilamos y probamos. Recuerda que puedes compilarlo desde un IDE, o desde la terminal como los macho alfa.
En fin, compilemos los 3 archivos…
Ahora ejecutaré el servidor en una terminal:
Y el cliente en otra:
Nota: no sé por qué los acentos no se muestran correctamente, pero igual y no es una cosa que afecte al programa, sólo es molesto a la vista. Si lo deseas, simplemente cambia la ú por u. O ejecuta el programa desde un IDE.
Conclusión
La ventaja de esto es que todo el proceso se hace en el servidor, y si tenemos uno muy robusto pues qué mejor que dejar que él haga todas las operaciones que se llevarían más tiempo si las hiciéramos en una computadora normal.
Recuerda que en este ejemplo, la ip es la de mi pc. Si vas a conectar a otra computadora en una red, simplemente escribe su ip.
Lo mismo aplica para el puerto, si quieres escuchar en otro número recuerda cambiarlo tanto en el cliente como en el servidor.
Creo que poco a poco Java me va cayendo mejor.
¿Y si quisiéramos tener el servidor y el cliente en proyectos separados que cambios serian necesarios?
Excelente trabajo, me gustaría verlo con base de datos