Explicamos de forma sencilla cómo hacer dos aplicaciones Java, una cliente y otra servidor, de forma que el servidor queda a la escucha para que el cliente se conecte y le envíe un mensaje de texto. El servidor usará thread (hilos) para permitir múltiples conexiones de clientes. Usaremos como IDE de desarrollo IntelliJ IDEA, aunque el mismo código que mostramos será válido para el resto de entornos como NetBeans y Eclipse. Realizaremos pruebas reales, generando los JAR de Cliente y Servidor, para ejecutarlos en Windows y en Linux, en la red LAN y desde Internet.
- Requisitos para desarrollar aplicación cliente que envíe un mensaje de texto a un servidor mediante Java.
- Aplicación Servidor que usa ServerSocket y Socket en Java y queda a la espera de recibir la conexión de los clientes y sus mensajes, con IDE IntelliJ IDEA.
- Aplicación Cliente que usa Socket y conecta con el Servidor y envía un texto al Servidor.
- Descarga del código fuente y aplicaciones JAR completas Cliente y Servidor para enviar mensajes mediante Socket con IDE IntelliJ IDEA.
- Generar JAR con ejecutables de Cliente y Servidor para probar en equipos reales.
- Pruebas reales de Cliente y Servidor en equipos Windows y Linux de la misma red LAN.
- Enviar mensaje desde Cliente que está fuera de la red LAN, desde Internet, a Servidor.
Requisitos para desarrollar aplicación cliente que envíe un mensaje de texto a un servidor mediante Java
El único requisito es disponer de un IDE de desarrollo para realizar las dos aplicaciones, cliente y servidor. En nuestro caso usaremos IntelliJ IDEA 2020.2 Community, pero sirve cualquier otro como Eclipse o NetBeans. Incluso se podría desarrollar sin entorno IDE, con un bloc de notas y el compilar de java javac.
Para probar la aplicación generaremos el JAR correspondiente, que nos llevaremos a otro equipo de la red y desde este enviaremos el mensaje a otro equipo. Uno tendrá el servidor iniciado y el otro iniciará el cliente. Por lo tanto, si queremos probarlo en un entorno real necesitaremos dos equipos conectados en red. Incluso este método también funcionará a través de Internet, siempre y cuando el equipo servidor tenga una IP pública conocida y la redirección (NAT, mapeo) del puerto que se use al equipo. A continuación explicamos de forma rápida como hacer NAT de un puerto a una IP en un router estándar.
NAT/Mapeo/Redirección de puerto en router estándar de casa Livebox
Si queremos permitir que los clientes de fuera de nuestra red LAN puedan enviar mensajes al servidor, necesitaremos abrir el puerto que vayamos a usar en el router de nuestra red. Habitualmente tenemos router estándar de baja gama, explicamos cómo hacerlo en uno de estos routers, un Livebox.
En primer lugar deberemos conocer la IP del equipo que tendrá el programa servidor ejecutándose y esperando la conexión de los clientes. En nuestro caso ese equipo tiene la IP 192.168.1.10:
Deberemos conocer también la IP del router, que como vemos en la imagen anterior suele ser la puerta de enlace asignada a los equipos, la 192.168.1.1. Abriremos un navegador y accederemos a esa IP. Nos solicitará usuario y contraseña que también hemos de conocer. Una vez dentro pulsaremos en «Configuración avanzada» [1] y en «NAT/PAT» [2]:
En la parte inferior, en «Personalizar reglas», pulsaremos en el desplegable y elegiremos «Nuevo…»:
Introduciremos los datos de la regla NAT:
- Aplicación/servicio: un nombre para la regla, por ejemplo «prueba_java».
- Puerto interno: el puerto que usaremos para las comunicaciones entre servidor y cliente, por ejemplo 2020.
- Puerto externo: el mismo que el anterior, 2020.
- Protocolo: TCP.
- IPv4 del dispositivo: en nuestro caso la IP del PC donde se ejecutará el programa Java servidor, que será la 192.168.1.10.
Una vez introducidos los datos pulsaremos «añadir» y la regla quedará habilitada y lista para usarse:
Para la conexión externa deberemos conocer, por último, la IP pública que nos ha proporcionado nuestro ISP (proveedor de Internet). Puede que sea dinámica y que vaya variando cada cierto tiempo (normalmente varios días o incluso semanas). En el propio router podemos consultarla, en Información y diagnóstico, pulsando en «Parámetros del sistema», aparecerá como «1.9 dirección IPv4»:
Aplicación Servidor que usa ServerSocket y Socket en Java y queda a la espera de recibir la conexión de los clientes y sus mensajes, con IDE IntelliJ IDEA
Abriremos IntelliJ IDEA (o el IDE de desarrollo Java que queramos) y crearemos un nuevo proyecto. En el caso de IntelliJ, es importante mencionar que según la versión del SDK de Java que usemos para compilar la aplicación, necesitaremos luego esa versión o superior de Java instalada en los equipos en los que queramos ejecutar la aplicación (sean Windows o Linux). En nuestro caso usaremos Java version 11.0.2:
Marcaremos «Create project from template» y elegiremos «Command Line App». Será una aplicación de línea de comandos:
Estableceremos los datos básicos para nuestro proyecto, el nombre del proyecto en «Project name», puesto que estamos creando la aplicación que hará de servidor, lo llamaremos «Servidor», la ubicación del proyecto y el paquete base, en nuestro caso com.ProyectoA:
El asistente creará la clase Main por defecto. Para el caso del servidor crearemos una segunda clase, para ello pulsaremos con el botón derecho del ratón sobre la carpeta del paquete «com.ProyectoA» y en el menú emergente pulsaremos en «New» – «Java Class»:
Introduciremos el nombre para la clase, por ejemplo «Servidor» y pulsaremos INTRO:
El código Java para la clase Servidor.java será el siguiente. La explicación de lo que hace dicha clase viene incluida en el código:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
package com.ProyectoA; import java.io.DataInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; /** * Clase Servidor que iniciará la escucha por un * puerto y recibirá los mensajes de los clientes. * Se extenderá a Thread para permitir programación concurrente * De forma que el servidor permita la conexión de múltiples clientes */ public class Servidor extends Thread { private int puerto; // Número de puerto de escucha para la conexión de clientes private DataInputStream canalEntradaDatos; //Canal de entrada de datos de clientes /** * Constructor * @param puerto: Número de puerto de escucha para la conexión de clientes por Socket */ public Servidor(int puerto) { this.puerto = puerto; } //Sobreescribimos método run para que el servidor se inicie y permita //conexiones de múltiples clientes (un hilo por cada cliente) @Override public void run() { try { //Creamos instancia de ServerSocket para //atender peticiones de clientes por el puerto indicado ServerSocket socketServidor = new ServerSocket(this.puerto); System.out.println(FechaActual() + " Servidor esperando " + "peticiones de clientes por puerto " + puerto + "..."); //Dejaremos el servidor siempre escuchando //Hasta que se reciba el mensaje: [[fin]] String mensajeCliente = ""; while(!mensajeCliente.equalsIgnoreCase("[[fin]]")) { // Esperamos a que se conecte un cliente y aceptamos la conexión Socket socketCliente = socketServidor.accept(); System.out.println(FechaActual() + " Cliente conectado: " + socketCliente.getInetAddress()); //Estableceremos canal de comunicación con cliente canalEntradaDatos = new DataInputStream(socketCliente.getInputStream()); //Leemos mensaje del cliente mensajeCliente = canalEntradaDatos.readUTF(); //Lo mostramos por consola System.out.println(FechaActual() + " [Mensaje recibido]"); System.out.println(FechaActual() + " * Cliente: " + socketCliente.getInetAddress()); System.out.println(FechaActual() + " * Mensaje: " + mensajeCliente); } } catch (IOException e) { System.out.println(FechaActual() + " Error al abrir puerto " + puerto + " para escucha de servidor: " + e.getMessage()); System.exit(2); //Cerramos aplicación con código de salida 2 } } /** * Función que devuelve la fecha y hora actuales con formato año-mes-dia hora:minuto:segundo:milisegundo */ public String FechaActual() { Date fechaHoraActual = new Date(); String fechaFormateada = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(fechaHoraActual); return fechaFormateada; } } |
En la clase principal Main de la aplicación Servidor, Main.java, colocaremos el siguiente código (que también viene explicado paso a paso):
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 |
package com.ProyectoA; import java.util.Scanner; public class Main { public static void main(String[] args) { int puerto = 0; // Número de puerto de escucha para la conexión de clientes //Pedimos el puerto de conexión //Si se ha pasado como parámetro if (args.length == 1) { try { puerto = Integer.parseInt(args[0]); } catch (Exception e) { System.out.println("Debe indicar un número de" + " puerto válido que no esté en uso."); System.exit(1); //Cerramos aplicación con código de salida 1 } } else { //En caso contrario, se lo pedimos al usuario por consola Scanner lecturaTeclado = new Scanner(System.in); System.out.print("Introduce el puerto de escucha " + "(no debe estar en uso por otra aplicación): "); try { puerto = Integer.parseInt(lecturaTeclado.nextLine()); } catch (Exception e) { System.out.println("Debe indicar un número " + "de puerto válido que no esté en uso."); System.exit(1); //Cerramos aplicación con código de salida 1 } } if (puerto > 0) { //Instanciamos la clase Servidor y le pasamos como parámetro el puerto Servidor prepararServidor = new Servidor(puerto); //Abriremos hilo (Thread) para que //el servidor quede esperando conexiones de múltiples clientes indefinidamente prepararServidor.start(); } else { System.out.println("Debe indicar un número " + "de puerto válido que no esté en uso."); System.exit(1); //Cerramos aplicación con código de salida 1 } } } |
Desde la clase Main instanciaremos la clase Servidor para iniciarlo y dejarlo escuchando y esperando la conexión de los clientes.
Una vez que tenemos el código Java, probaremos el Servidor pulsando en «Run ‘Main'»:
El programa Servidor ser ejecutará, solicitará al usuario que introduzca el puerto (también puede pasársele por parámetro). Una vez introducido el puerto, el programa Servidor lo usará y quedará escuchando por dicho puerto las conexiones de los clientes:
El Servidor queda indefinidamente a la escucha de los clientes por el puerto indicado, hasta que algún cliente le pase el mensaje «[[fin]]», en cuyo caso se cerrará.
A continuación describimos el desarrollo de la aplicación Java Cliente.
Aplicación Cliente que usa Socket y conecta con el Servidor y envía un texto al Servidor
Procederemos de la misma forma que hemos hecho para el Servidor. Crearemos un nuevo proyecto Java con IntelliJ IDEA, que llamaremos «Cliente»:
El Cliente, a diferencia del Servidor, solo tendrá una clase, la clase Main, que se creará por defecto al crear el proyecto. En dicha clase colocaremos el siguiente código Java:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
package com.ProyectoA; import java.io.*; import java.net.Socket; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Scanner; public class Main { public static void main(String[] args) { int puerto = 0; //Número de puerto para conexión con servidor String servidor = ""; //IP o nombre DNS del servidor al que nos conectaremos String mensaje = ""; //Mensaje a enviar al servidor //Pedimos los datos de conexión y el mensaje //Si se han pasado como parámetro if (args.length == 3) { //El servidor será el primer parámetro servidor = args[1]; //El puerto será el segundo parámetro try { puerto = Integer.parseInt(args[1]); } catch (Exception e) { System.out.println("Debe indicar un número de" + " puerto válido que no esté en uso."); System.exit(1); //Cerramos aplicación con código de salida 1 } //El mensaje será el tercer parámetro mensaje = args[2]; } else { //En caso contrario, se los pedimos al usuario por consola Scanner lecturaTeclado = new Scanner(System.in); System.out.print("Introduce la dirección del servidor: "); servidor = lecturaTeclado.nextLine(); System.out.print("Introduce el puerto de conexión con el servidor: "); try { puerto = Integer.parseInt(lecturaTeclado.nextLine()); } catch (Exception e) { System.out.println("Debe indicar un número " + "de puerto válido que no esté en uso."); System.exit(1); //Cerramos aplicación con código de salida 1 } System.out.print("Introduce el mensaje a enviar: "); mensaje = lecturaTeclado.nextLine(); } if (puerto > 0) { try { System.out.println(FechaActual() + " Conectando con servidor " + servidor + " por puerto: " + puerto + "..."); //Instanciamos clase Socket con servidor y puerto especificados Socket clientSocket = new Socket(servidor, puerto); System.out.println(FechaActual() + " Conectado a servidor: " + clientSocket.getInetAddress()); //Establecemos el canal de comunicación DataOutputStream outputStream = new DataOutputStream(clientSocket.getOutputStream()); //Enviamos el mensaje de texto al servidor System.out.println(FechaActual() + " Enviando mensaje a servidor..."); outputStream.writeUTF(mensaje); System.out.println(FechaActual() + " Mensaje enviado a servidor: " + mensaje); //Cerramos la conexión con el servidor clientSocket.close(); } catch (UnknownHostException ex) { System.out.println("Servidor no encontrado: " + ex.getMessage()); System.exit(1); //Salimos del programa con código de salida 1 } catch (IOException ex) { System.out.println("Error al conectar al servidor: " + ex.getMessage()); System.exit(2); //Salimos del programa con código de salida 2 } } else { System.out.println("Debe indicar un puerto, una" + " IP (o DNS) y un mensaje para el envío."); System.exit(1); //Cerramos aplicación con código de salida 1 } } /** * Función que devuelve la fecha y hora actuales con formato año-mes-dia hora:minuto:segundo:milisegundo */ public static String FechaActual() { Date fechaHoraActual = new Date(); String fechaFormateada = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(fechaHoraActual); return fechaFormateada; } } |
El código del Cliente también viene explicado paso a paso en el propio código. Básicamente conectará con el servidor por la IP y puertos indicados y enviará un mensaje de texto. Admite parámetros para pasarle los tres datos (IP, puerto y mensaje) o también por consola.
El Cliente en funcionamiento, tras compilarlo, nos pedirá la dirección del Servidor, de momento, como estamos en desarrollo, tanto Cliente como Servidor se ejecutan en el mismo equipo, por lo tanto la dirección del servidor será: localhost. Nos pedirá el puerto, introduciremos el mismo en que tengamos el servidor escuchando, en nuestro caso el 2020. Y nos pedirá, por último, el mensaje de texto a enviar, lo introduciremos y pulsaremos INTRO. El mensaje se enviará al Servidor:
Y en el Servidor, que también estaba iniciado (de lo contrario el cliente fallaría), recibirá el mensaje enviado por el Cliente:
Podemos probar a detener el servidor para comprobar qué hará el Cliente. Si el servidor está detenido e intentamos enviar un mensaje desde un Cliente, mostrará un error:
Como vemos, mostrará un error de conexión, Connection refused, y finalizará la aplicación Cliente con código de salida 2.
Descarga del código fuente y aplicaciones JAR completas Cliente y Servidor para enviar mensajes mediante Socket con IDE IntelliJ IDEA
A continuación os dejamos el enlace para la descarga del código fuente Java completo de estas aplicaciones (realizado con IntelliJ IDEA 2020.2):
Generar JAR con ejecutables de Cliente y Servidor para probar en equipos reales
Hasta ahora hemos probado el Cliente y el Servidor en el mismo equipo y compilándolos con IntelliJ IDEA. Ahora vamos a generar los JAR de ambos programas para ejecutarlos en real. Para generar los JAR seguiremos los pasos que explicamos en este apartado:
Generaremos el JAR de la aplicación Servidor, como indicamos en el enlace anterior, añadiendo un Artifacts:
Y generando el JAR Servidor.jar:
Y generamos el JAR para la aplicación Cliente, de la misma forma:
Y generando el JAR Cliente.jar:
En la carpeta del proyecto …\IdeaProjects\Socket_Cliente_Servidor\Cliente\out\artifacts\Cliente_jar tendremos el Cliente.jar:
Y en la carpeta del proyecto …\IdeaProjects\Socket_Cliente_Servidor\Servidor\out\artifacts\Servidor_jar tendremos el Servidor.jar:
Pruebas reales de Cliente y Servidor en equipos Windows y Linux de la misma red LAN
Realizaremos una prueba real de los programas Cliente y Servidor. Por ejemplo, ejecuaremos el programa Servidor, a la escucha por el puerto 2020, en un equipo con Windows 10. Y ejecutaremos el programa Cliente, en otro equipo con Linux y otro equipo con Windows.
Iniciaremos el programa Servidor (Servidor.jar) en el equipo con Windows 10, para ello abriremos una ventana de MS-DOS (de línea de comandos o cmd) y accederemos a la carpeta donde tengamos el programa Servidor.jar, por ejemplo a «Servidor_Java_ProyectoA», con el comando:
cd C:\Servidor_Java_Proyecto_A
A continuación ejecutaremos el siguiente comando Java para iniciar el .jar:
java -jar Servidor.jar
Si tenemos la versión correcta de Java y está agregada a la variable de entorno PATH se iniciará la aplicación Servidor y nos solicitará un puerto donde escuchar, introduciremos, por ejemplo, 2020 y pulsaremos INTRO. Si todo es correcto el Servidor permanecerá en escucha, esperando la conexión de los clientes:
Si se produce algún error en el comando java -jar, se pueden seguir los pasos de estos manuales (Windows y Linus) para configurar correctamente Java para la ejecución de aplicaciones .jar:
- Ejecutar fichero JAR de aplicación Java en Windows.
- Ejecutar fichero JAR de aplicación Java en Linux.
El Servidor tiene la IP 192.168.1.10, se puede consultar desde la línea de comandos con el comando ipconfig:
Necesitaremos este dato para poder conectar los clientes.
Una vez que el Servidor queda a la escucha, ahora iniciaremos un cliente, por ejemplo un equipo Linux, de la misma red LAN que el servidor. Deberemos copiar el fichero Cliente.jar al equipo Linux, por ejemplo pasándolo con Filezilla desde el equipo Windows (o de cualquier otra forma):
Una vez pasado el fichero Cliente.jar, desde el equipo Linux, desde una ventana de terminal, accederemos a la carpeta del jar (igual que en el caso de Windows). En nuestro caso hemos colocado el fichero Cliente.jar en la carpeta /tmp, por lo tanto:
cd /tmp
A continuación ejecutaremos el fichero Cliente.jar igual que hemos hecho con el fichero Servidor.jar en Windows:
java -jar Cliente.jar
Si tenemos la máquina virtual Java y tenemos las versiones correctas (como explicamos en este artículo) se iniciará el cliente y nos pedirá los siguientes datos:
- Dirección IP del servidor: como hemos obtenido anteriormente, en nuestro caso, la 192.168.1.10.
- Puerto: en todo el artículo estamos usando siempre el 2020.
- Mensaje: introduciremos el mensaje de texto que se enviará al servidor. Por ejemplo «Prueba envío mensaje desde Linux».
Tras pulsar INTRO en el mensaje el Cliente conectará con el Servidor y enviará el mensaje:
Comprobaremos en el Servidor que el mensaje ha llegado correctamente:
Vemos que sí, que desde el cliente con IP 192.168.1.5 (el cliente Linux) ha llegado el mensaje correctamente.
Ahora probaremos también la aplicación con un cliente Windows. Procederemos de la misma forma, copiando el fichero Cliente.jar, abriendo una ventana de MS-DOS (cmd) y ejecutándolo igual que en Linux, con el comando:
java -jar Cliente.jar
Si todo es correcto se iniciará el Cliente y nos solicitará los mismos datos que en el caso del Cliente Linux. Enviaremos el mensaje al Servidor:
Y comprobaremos que ha llegado al Servidor:
Vemos que ha llegado, desde la IP 192.168.1.12, que es la IP del equipo Windows Cliente. También vemos que la tilde de «envío» no ha llegado correctamente. En realidad sí que ha llegado, solo que en las consolas de MS-DOS (símbolo de sistema o cmd) de Windows hay que indicar, con el siguiente comando, el tipo de codificación. En el Cliente escribiremos el siguiente comando antes de conectar con el Servidor:
chcp 1252
Nos devolverá el mensaje «Página de códigos activa: 1252».
Ahora realizaremos la conexión y enviaremos un mensaje, veremos que las tildes (acentos) y las eñes y demás caracteres se envían:
Y también llegan al servidor correctamente:
Y como vemos en la última imagen, el servidor siempre permanece escuchando y atendiendo a los clientes, salvo que se le envíe el mensaje «[[fin]]» en cuyo caso finalizará. Antes de cerrarlo, haremos una última prueba real, enviando un mensaje desde un PC de Internet, de fuera de nuestra red LAN, como explicamos a continuación.
Enviar mensaje desde Cliente que está fuera de la red LAN, desde Internet, a Servidor
En los ejemplos reales anteriores todos los clientes estaban en la misma red que el Servidor. Ahora vamos a conectar con el Servidor desde un cliente de fuera de la red. Para ello debemos seguir estos pasos que indicamos al principio del artículo, para redireccionar el puerto 2020 (en nuestro caso) a la IP del Servidor (192.168.1.10 en nuestro caso).
Deberemos conocer la IP pública que nos proporciona el ISP (proveedor de Intenet) de donde tenemos el Servidor, como también explicamos aquí.
Pasaremos el fichero Cliente.jar al equipo de Internet, por el medio que sea. Ejecutaremos el Cliente.jar de la misma forma que hemos explicado anteriormente (sea Windows o Linux). En este caso, en la dirección IP del servidor introcudremos la IP pública obtenida anteriormente, en nuestro caso 5.19.6.174:
Comprobaremos que ha llegado el mensaje del Cliente de Internet al Servidor:
Vemos que el Cliente, con IP pública 7.4.1.10 ha enviado el mensaje correctamente al Servidor.
Ahora podremos detener el Servidor enviado el mensaje [[fin]] desde un Cliente: