Explicamos cómo usar RMI (Java Remote Method Invocation) para crear un .class Servidor, un .class Cliente y las implementaciones necesarias para que el cliente conecte con el servidor RMI. En este ejemplo, el servidor permanecerá a la espera, con un método llamado esPrimo() que devolverá true o false en función de si el número pasado por parámetro es primo. El cliente conectará con el servidor RMI, ejecutará el método remoto esPrimo(), enviando un número y el servidor le devolverá si es primo o no.. Usaremos Java y el IDE IntelliJ IDEA. Probaremos la aplicación cliente y servidor tanto en equipos Windows como Linux.
- ¿Qué es Java RMI?.
- Requisitos para realizar aplicación Java RMI para invocar método remoto y obtener resultado.
- Proyecto Java en IntelliJ IDEA para invocar un método remoto RMI en un servidor y obtener el resultado en el cliente.
- Compilar los .java para generar los .class con javac.
- Descarga gratuita de la aplicación completa Java RMI (.java, .class) desarrollada con IntelliJ IDEA.
- Ejecutar aplicación Servidor RMI en equipo Windows y Cliente RMI en equipo Linux.
- Ejecutar aplicación Servidor RMI en equipo Linux y Cliente RMI en equipo Windows.
- Versiones de los sistemas operativos y aplicaciones usadas para realizar la aplicación Java RMI.
¿Qué es Java RMI?
RMI (Java Remote Method Invocation) es un mecanismo ofrecido por Java para invocar un método de manera remota. Forma parte del entorno estándar de ejecución de Java y proporciona un mecanismo simple para la comunicación de servidores en aplicaciones distribuidas basadas exclusivamente en Java. Sus análogos en otras tecnologías son CORBA o SOAP.
RMI se caracteriza por la facilidad de su uso en la programación, por estar específicamente diseñado para Java. Proporciona paso de objetos por referencia (no permitido por SOAP), recolección de basura distribuida (Garbage Collector distribuido) y paso de tipos arbitrarios (funcionalidad no provista por CORBA).
A través de RMI, un programa Java puede exportar un objeto, con lo que dicho objeto estará accesible a través de la red y el programa permanece a la espera de peticiones en un puerto TCP (el estándar es el 1099). A partir de ese momento, un cliente puede conectarse e invocar los métodos proporcionados por el objeto.
Requisitos para realizar aplicación Java RMI para invocar método remoto y obtener resultado
El lenguaje de programación Java nos permite realizar programar usando únicamente un editor de texto. Generaremos los .java correspondientes con el código fuente y los podremos compilar usando el comando javac que está disponible en la instalación de JRE o JDK en el equipo. Así que no necesitaríamos nada más que un editor de texto y el compilador javac. Pero para mayor comodidad y para tener ayuda en línea y depuración de sintaxis, podremos usar un IDE. En nuestro caso usaremos IntelliJ IDEA, aunque es válido cualquier otro como Eclipse o NetBeans.
Si queremos probar la aplicación en real y desde fuera de nuestra red, para conectar el equipo (o equipos) cliente con el servidor desde fuera de la LAN, desde Internet, necesitaremos abrir el puerto que se decida usar (el de defecto para RMI es el 1099) en el router o cortafuegos de la red donde se encuentre el servidor RMI. Necesitaremos hacer NAT (mapeo de puerto) del puerto 1099 (o el que se establezca) a la IP local del servidor, para que le lleguen las peticiones de los clientes. En el siguiente enlace mostramos cómo hacer NAT en un puerto a una IP de la LAN en un router:
Si solo vamos a usar la aplicación en equipos de la misma LAN, únicamente necesitaremos tener abierto el puerto 1099 en el cortafuegos del equipo que hará de servidor RMI.
Proyecto Java en IntelliJ IDEA para invocar un método remoto RMI en un servidor y obtener el resultado en el cliente
Crearemos un nuevo proyecto Java de línea de comandos en IntelliJ IDEA, desde «File» – «New» – «Project». Elegiremos «Java» y pulsaremos «Next»:
Marcaremos «Create project from template» y seleccionaremos «Command Line App»:
Introduciremos le nombre para el proyecto, por ejemplo RMIPrimo y pulsamos «Finish»:
Interfaz remota RMI con la definición de los métodos PrimoRMI.java
Ahora iremos creando las interfaces y clases de nuestra aplicación. En primer lugar crearemos una clase que será la interfaz remota donde definiremos el método remoto esPrimo(). Para ello pulsaremos con el botón derecho del ratón sobre la carpea src de nuestro proyecto y elegiremos «New» – «Java Class»:
Introduciremos el nombre para la clase, por ejemplo PrimoRMI y seleccionaremos «Interface»:
Introduciremos el siguiente código Java para esta interfaz:
1 2 3 4 5 6 7 8 9 10 |
import java.rmi.Remote; //Creamos la interfaz remota para la aplicación public interface PrimoRMI extends Remote { //Definimos los métodos de la interfaz remota //Método que lee un número pasado por parámetro //y devuelve true si es primo, false si no lo es boolean esPrimo(int numero) throws java.rmi.RemoteException; } |
Como vemos, en esta interfaz únicamente declaramos los métodos remotos que se podrán invocar desde los clientes RMI. En nuestro caso, el método esPrimo(). En la siguiente interfaz agregaremos el código Java que ejecutará dicho método.
Para la interfaz anterior deberemos importar java.rmi.Remote, que es la interfaz que proporciona Java con soporte para RMI.
Clase donde se implementan los métodos de la interfaz remota PrimoRMIImpl.java
A continuación agregaremos otra nueva clase implement, con el mismo procedimiento que anteriormente. A esta clase la llamaremos PrimoRMIImpl, seleccionaremos «Class»:
Será donde implementemos los métodos de la interfaz remota PrimoRMI, que podrán ser invocados (usados o accedidos) por los clientes RMI. Para nuestro ejemplo, definiremos el método esPrimo(), con el código Java correspondiente para calcular si un número es primo o no:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Implementamos la interfaz remota public class PrimoRMIImpl implements PrimoRMI { // Comprueba si un número es primo o no @Override public boolean esPrimo(int numero) { // Comprueba si es múltiplo de 2 if (numero % 2 == 0) return false; // En caso contrario, comprueba los impares for (int i = 3; i * i <= numero; i += 2) { if(numero % i == 0) return false; } return true; } } |
Vemos que la clase anterior PrimoRMIImpl es una implementación de la clase PrimoRMI, donde se agrega el código Java que tendrá cada método remoto. Dichos métodos han de coincidir en nombre y estructura (parámetros, tipo de datos, si es función o procedimiento, etc.) exactamente con los definidos en la interfaz. En caso de no coincidir el IDE nos mostrará el error correspondiente: Class ‘PrimoRMIImpl’ must either be declared abstract or implement abstract method ‘esPrimo(int)’ in ‘PrimoRMI’.
Si en esta clase definimos un método que no está declarado en la clase interfaz remota, no podrá ser invocado por los clientes RMI.
Clase Servidor RMI Servidor.java
Vamos a definir ahora la clase Servidor, que será la que inicie el objeto RMI y permanezca a la escucha para recibir las peticiones de los clientes. En nuestro caso, como IntelliJ nos ha creado una clase «Main.java» la renombraremos a «Servidor.java». Si no la ha creado la podremos agregar manualmente como hemos hecho anteriormente. Para renombrar Main abriremos Main.java, pulsando doble click sobre «Main» en la carpeta «src» [1], seleccionaremos Main [2] y pulsaremos con el botón derecho del ratón sobre la selección, eligiendo «Refactor» [3] y «Rename…» [4]:
Introduciremos el nuevo nombre, por ejemplo «Servidor» y pulsaremos INTRO para aplicar el cambio:
Podremos comprobar que, automáticamente, también ha cambiado el nombre del fichero Main.java por Servidor.java:
En el caso del Servidor RMI, agregaremos el siguiente código Java (viene comentada cada línea que realiza alguna acción importante):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; public class Servidor extends PrimoRMIImpl { // Constructor public Servidor() { } public static void main(String[] args) { try { System.out.println("Preparando servidor RMI..."); // Instanciamos la clase implementada PrimoRMIImpl rmiPrimo = new PrimoRMIImpl(); // Exportamos el objeto de la clase implementada // Con port=0 se usará el puerto por defecto de RMI, el 1099 PrimoRMI stub = (PrimoRMI) UnicastRemoteObject.exportObject(rmiPrimo, 0); // Vinculamos el objeto remoto (stub) en el registro (rmiregistry) Registry registry = LocateRegistry.getRegistry(); // Enlazamos el stub y nombramos el objeto remoto RMI como "rmiPrimo" registry.bind("rmiPrimo", stub); System.out.println("Servidor RMI escuchando..."); } catch (Exception e) { System.out.println("Error en Servidor: " + e.getMessage()); } } } |
En realidad, hemos añadido al método main el código Java para mostrar lo que va realizando el servidor por consola, instanciar un stub con la interfaz remota PrimoRMI. Haremos una llamada al objeto remoto en rmiregistry. Nombraremos el stub por «rmiPrimo». Dicho nombre deberá conocerse para hacer la llamada al objeto remoto RMI desde el cliente. Y de esta forma el servidor permanecerá siempre a la escucha de la conexión de los clientes.
Clase Cliente RMI Cliente.java
Por último, vamos a definir la clase Cliente. Como hemos hecho hasta ahora, agregando una nueva clase con el nombre «Cliente» y añadiendo el siguiente código (también explicado por cada línea):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.Scanner; public class Cliente { //Constructor private Cliente(){} public static void main(String[] args) { int numero; String direccionServidor; if (args.length == 2) { direccionServidor = args[0]; numero = Integer.parseInt(args[1]); } else { Scanner lecturaTeclado = new Scanner(System.in); System.out.print("Introduzca la dirección IP o DNS del servidor RMI: "); direccionServidor = lecturaTeclado.nextLine(); System.out.print("Introduzca un número: "); numero = Integer.parseInt(lecturaTeclado.nextLine()); } //Si no se indica dirección de servidor, se asume "null" (localhost) if (direccionServidor.equals("")) { direccionServidor = null; } try { //Obteniendo registro de rmiregistry System.out.println("Obteniendo registro de rmiregistry..."); Registry registry = LocateRegistry.getRegistry(direccionServidor); //Buscando el objeto RMI remoto System.out.println("Buscando objeto RMI rmiPrimo y creando stub..."); PrimoRMI stub = (PrimoRMI) registry.lookup("rmiPrimo"); System.out.println("Objeto rmiPrimo remoto encontrado..."); System.out.println(); System.out.println("Ejecutando método remoto RMI esPrimo en servidor..."); boolean resultado = stub.esPrimo(numero); if (resultado) { System.out.println("El número " + numero + " es primo según el servidor RMI"); } else { System.out.println("El número " + numero + " NO es primo según el servidor RMI"); } System.out.println(); System.out.println("Fin programa cliente RMI"); } catch (Exception e) { System.out.println("Error en cliente RMI: " + e.getMessage()); } } } |
El cliente, como vemos en el código Java anterior, solicita por consola (o bien se le puede pasar como parámetro) la dirección IP (o nombre DNS) del servidor RMI y también solicitará un número, para que el servidor compruebe si es primo o no y devuelva el resultado al cliente.
En el caso de la clase Cliente, invocaremos al objeto RMI que hemos llamado en el servidor rmiPrimo, crearemos el stub y así podremos usar, desde el cliente, los métodos definidos en la interfaz remota, igual que si los usáramos en el servidor. En nuestro caso el método esPrimo(), que le pasaremos como parámetro el número introducido por el usuario y el servidor devolverá si es primo o no, mostrándole el resultado al cliente, como veremos a continuación.
Compilar los .java para generar los .class con javac
En esta aplicación no usaremos el IDE IntelliJ IDEA para generar el jar ni para compilar, lo haremos desde la línea de comandos, con el comando javac de Java. Para poder ejecutar javac, deberemos tener la ruta donde esté dicho comando en la variable de entorno PATH del sistema (sea Windows o Linux).
Si estamos trabajando en un equipo con sistema operativo Windows, abriremos el Panel de control y las Propiedades de sistema. En esta ventana pulsaremos en «Variables de entorno» [1]. En «Variables del sistema» seleccionaremos «Path» [2] y pulsaremos «Editar» [3]. Agregaremos, si no existe, la ruta de la carpeta «bin» de Java JDK instalado en nuestro equipo [4]. En nuestro caso:
C:\Program Files\Java\jdk1.8.0_201\bin
De esta forma podremos usar todos los comandos de …/bin de Java en cualquier ubicación del equipo:
Como es lógico, para realizar la compilación y para ejecutar el programa necesitaremos tener instalado en el equipo Java JRE o JDK, ambos incluyen javac para generar los .class a partir de los .java y java para ejecutar los .class.
En el caso de equipos Linux, comprobaremos si tenemos en la variable de entorno PATH la ruta de la carpeta bin de Java. En el caso de Linux CentOS y Java OpenJDK los ejecutables se encuentran en /usr/bin. Para ver el valor actual de la varibla de entorno PATH en un equipo Linux podemos ejecutar el comando:
printenv PATH
Para ver la ubicación de un comando podemos ejecutar:
which javac
Nos devolverá, si existe, la carpeta donde está ubicado el comando javac, en nuestro caso en: /usr/bin. Si queremos agregar esta carpeta a la variable PATH, ejecutaríamos:
export PATH=$PATH:/usr/bin
Una vez que dispongamos del comando javac, abriremos una ventana de la línea de comandos y accederemos a la carpeta donde tengamos los .java de la aplicación:
cd C:\Users\alonso\IdeaProjects\RMIPrimo\src
Generaremos los .class con el comando:
javac *.java
Y ya los tendremos disponibles para llevárnoslos a cualquier equipo y poder ejecutarlos para que sea el servidor o el cliente RMI:
Los comandos anteriores para generar los .class son válidos también para Linux.
Descarga gratuita de la aplicación completa Java RMI (.java, .class) desarrollada con IntelliJ IDEA
En el siguiente enlace tenéis disponible la descarga gratuita del código fuente completo de la aplicación Java RMI de ejemplo:
Ejecutar aplicación Servidor RMI en equipo Windows y Cliente RMI en equipo Linux
Para ejecutar una aplicación servidor RMI necesitaremos, previamente, ejecutar rmiregistry. Para ello, desde la línea de comandos, ejecutaremos el siguiente comando:
start rmiregistry
Dicho comando, que también se encuentra en la carpeta bin de Java, junto con java y javac, abrirá una nueva ventana, que debemos dejar abierta. Esta aplicación quedará en segundo plano, esperando conexiones por el puerto 1099 (o el que se le haya indicado en el arranque) para «enlazar» el cliente entrante con el servidor en escucha:
Antes de ejecutar el servidor, si queremos evitar problemas con las tildes (acentos), las eñes y otros caracteres, cambiaremos la página de códigos a la 1252, con el comando:
chcp 1252
Ahora estaremos en disposición de ejecutar el servidor, para ello ejecutaremos el siguiente comando:
java Servidor
Si todo es correcto, se iniciará el servidor y permanecerá a la escucha y espera de la conexión de clientes:
El servidor mostrará los mensajes: Preparando servidor RMI y Servidor RMI escuchando.
Si no hemos iniciado la herramienta rmiregistry, el servidor mostraría el error:
Error en Servidor: Connection refused to host: IP_Servidor; nested exception is:
java.net.ConnectException: Connection refused: connect
Ahora ejecutaremos el cliente, para realizar las pruebas, en otro equipo con sistema operativo Linux. Pasaremos los ficheros .class a una carpeta del equipo Linux y ejecutaremos el Cliente con el comando:
javac Cliente
Si no le pasamos por parámetro la IP y un número, nos los pedirá por consola. Introduciremos la IP del equipo servidor RMI y un número. Ejecutaremos el cliente con la página de códigos 1252, para que también muestre correctamente las tildes (acentos), eñes y demás caracteres especiales:
java -Dfile.encoding=cp1252 Cliente
En el Cliente no hay que ejecutar la herramienta rmiregistry. Ahora la aplicación se iniciará, nos solicitará la IP del servidor RMI. La introduciremos y pulsaremos INTRO. A continuación nos solicitará que introduzcamos un número. Lo introcudiremos y pulsaremos INTRO. El Cliente conectará con el servidor por el puerto 1099, con el «protocolo» RMI. Accederá al stub rmiPrimo e invocará el método remoto esPrimo(), calculando si es o no primo el número introducido y mostrando el resultado.
En nuestro ejemplo, el cliente nos devolverá:
Introduzca la dirección IP o DNS del servidor RMI: 192.168.1.2
Introduzca un número: 129
Obteniendo registro de rmiregistry…
Buscando objeto RMI rmiPrimo y creando stub…
Objeto rmiPrimo remoto encontrado…
Ejecutando método remoto RMI esPrimo en servidor…
El número 129 NO es primo según el servidor RMI
Fin programa cliente RMI
Ejecutar aplicación Servidor RMI en equipo Linux y Cliente RMI en equipo Windows
Antes de iniciar el servidor en Linux, deberemos abrir el puerto 1099 (o el que vayamos a usar para RMI). Para ello, en el caso de Linux CentOS, ejecuaremos los comandos:
firewall-cmd –zone=public –permanent –add-port=1099/tcp
firewall-cmd –reload
Desde la consola o terminal de Linux, ejecutaremos el Servidor RMI. Tendremos los .class de la aplicación en una carpeta y accederemos a ella:
cd /RMIPrimo
Como el equipo Linux será el servidor, tendremos que ejecutar la herramienta rmiregistry, con el comando:
rmiregistry &
Añadimos el «&» al final para ejecutar rmiregistry en segundo plano, así podremos ejecutar en la misma consola el Servidor, con el comando:
java Servidor
En el equipo Windows, de la misma forma que hemos hecho anteriormente, abriremos una ventana de MS-DOS y ejecutaremos el Cliente (no necesita ejecutar la herramienta rmiregistry):
El programa cliente se ejecutará de la misma forma que lo hizo en Linux y mostrará como resultado si el número pasado es primo o no.
En el caso de que no tengamos abierto el puerto correspondiente, en el equipo que hará de servbidor RMI, sea en el equipo Windows o Linux, el cliente, al intentar conectar, mostrará este mensaje de error:
Buscando objeto RMI rmiPrimo y creando stub…
Error en cliente RMI: Connection refused to host: IP_Servidor; nested exception is:
java.net.ConnectException: Connection timed out: connect
Versiones de los sistemas operativos y aplicaciones usadas para realizar la aplicación Java RMI
Para desarrollar la aplicación Java hemos usado IntelliJ IDEA 2020.2.
Para compilar los .java y ejecutar las aplicaciones cliente en Windows hemos usado Java JDK 1.8.0_201.
Para ejecutar la aplicación RMI en entornos con sistema operativo Windows (tanto cliente como servidor) hemos usado Windows 10.
Para ejecutar la aplicación RMI en entornos con sistema operativo Linux (tanto cliente como servidor) hemos usado Linux CentOS 7 Minimal.