En anteriores post construimos aplicaciones Cliente/Servidor los cuales intercambiaban mensajes de una forma secuencial y solo entre un servidor y un cliente. En esta ocasión ampliaremos este modelo para que tanto el Servidor como el Cliente puedan interactuar entre ellos usando multihilos (multithreading), es decir el servidor sera capaz de aceptar varias peticiones de distintos clientes y procesarlas en paralelo, así mismo el cliente sera capaz de enviar varias peticiones a la vez al servidor, usando también multihilos. El proceso a resolver sera algo trivial al sistema (la sucesión de fibonacci) , así también el hilo dormirá unos segundos aleatorios (sleep) para simular un proceso más complejo. El Cliente estará automatizado, es decir, solo se deberá ejecutarlo y el mismo ira generando las peticiones. Todo este proceso podrá ser seguido desde la consola de Netbeans el cual para fines didácticos, estará coloreado 🙂
¿Que necesitamos?
Tiempo: 30 minutos
Nivel: Intermedio
Paso 1. El Proyecto
Creamos un nuevo proyecto en Netbeans que se llamara «JMultiServer» a continuación, se crea la estructura y clases como se ven en la siguiente imagen:
Paquete «client«:
Paquete «helper«
Paquete «server«
Paso 2. Paquete Helper
Este paquete contiene métodos y clases usados tanto por el Cliente como por el Servidor, comprende la clase JSystem, clase para colorear el texto en consola y la clase Helper con 3 métodos cuyo código es el siguiente:
package helper; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; /** * @see https://www.jc-mouse.net/ * @author mouse */ public final class Helper { public Helper(){} public static abstract class fn { /** * retorna fecha y hora preformateada * @return String */ public static String fecha_actual(){ SimpleDateFormat dt = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss.SSS"); return dt.format(new Date()); } /** * Duerme el hilo unos segundos (random) */ public static void sleep() { Random r = new Random(); try { int t = r.nextInt(6000) + 500; Thread.sleep(t); } catch (InterruptedException ex) { System.err.println(ex.getMessage()); } } /** * Retorna un numero aleatorio entero entre un rango dado * @param min * @param max * @return int numero aleatorio */ public static int rndInt(int min, int max){ Random r = new Random(); int n = r.nextInt(max-min) + min; return n; } } }
Paso 3. Paquete Client
Este paquete contiene las clases que conforman la aplicación cliente, su función consiste en enviar peticiones al servidor, que en este caso son números enteros y esperar su respuesta, la cual es la Sucesión de Fibonacci. Este cliente sera mutihilo, es decir a diferencia de clientes vistos en post anteriores los cuales enviaban la petición y no se podía hacer nada hasta recibir la respuesta del servidor, este Cliente enviara peticiones en hilos independientes (ServerThread) al hilo principal (MainClient), el cual generara cada cierto tiempo un nuevo hilo (petición), igual método se usara en el servidor. El proceso esta automatizado por tanto el usuario no debe hacer nada más que ejecutar la clase principal.
Clase ServerThread
package client; import helper.Helper; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.net.Socket; /** * @see https://www.jc-mouse.net/ * @author mouse */ public class ServerThread implements Runnable { /** * puerto */ private final static int PORT = 5000; /** * host */ private final static String SERVER = "localhost"; @Override public void run() { Socket clientSocket;//Socket para la comunicacion cliente servidor try { //abre socket clientSocket = new Socket(SERVER, PORT); //para imprimir datos del servidor PrintStream toServer = new PrintStream(clientSocket.getOutputStream()); //Para leer lo que envie el servidor InputStream stream = clientSocket.getInputStream(); //genera numero aleatorio y manda al servidor toServer.println(Helper.fn.rndInt(2, 20)); //lee respuesta del servidor byte[] bytes = new byte[256]; stream.read(bytes, 0, 256); //convierte a string String received = new String(bytes, "UTF-8"); //imprime en pantalla System.out.println("Server> recibido del servior fibonacci" + received.trim()); clientSocket.close(); } catch (IOException ex) { System.err.println(ex.getMessage()); System.exit(0);//fin de app } } }
Clase MainClient
package client; import helper.Helper; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @see https://www.jc-mouse.net/ * @author mouse */ public class MainClient { /** * @param args the command line arguments */ public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); while (true) { //duerme el hilo del cliente unos segundos antes de generar una nueva peticion Helper.fn.sleep(); //nuevo hilo executor.submit(new ServerThread()); } } }
Paso 3. Paquete Server
El paquete Server contiene las clases que conforman la aplicación servidor, el cual, al igual que el cliente, es multihilo, básicamente funciona de la siguiente manera:
De esta forma el servidor puede aceptar muchas conexiones de varios clientes a la vez y procesarlos independientemente.
Clase ClientThread
package server; import helper.JSystem; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; import helper.Helper; /** * @see https://www.jc-mouse.net/ * @author mouse */ public class ClientThread implements Runnable { /** * Socket para la comunicacion cliente servidor */ private Socket clientSocket; /** * identificador para el hilo */ private int id; /** * Color de fondo para texto en consola */ private JSystem.ColorBg colorThread; /** * Constructor de clade * @param clientSocket * @param id identificador de Thread */ public ClientThread(Socket clientSocket, int id) { this.clientSocket = clientSocket; this.id = id; colorThread = bgColor(); } @Override public void run() { try { //para imprimir datos de salida PrintStream output = new PrintStream(clientSocket.getOutputStream()); JSystem.out.printColor(colorThread, JSystem.Color.white, "Server>"); JSystem.out.printColorln(JSystem.Color.green, " Nueva petición aceptada [id: " + id + "] - " + Helper.fn.fecha_actual()); //Para leer lo que envie el cliente BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); //se lee peticion del cliente. En este caso es un numero entero int request = Integer.valueOf(input.readLine()); JSystem.out.printColor(colorThread, JSystem.Color.white, "Server>"); JSystem.out.printColorln(JSystem.Color.black, " Thread [id: " + id + "] procesando..."); //se genera el fibonacci String f = fibo(request); //duerme el hilo unos segundos para emular procesos que pueden durar varios segundos o minutos,etc Helper.fn.sleep(); //imprime en consola de netbeans el resultado JSystem.out.printColor(colorThread, JSystem.Color.white, "Server>"); JSystem.out.printColorln(JSystem.Color.black, " Cliente [id: " + id + "] fibonacci de " + request + " = " + f); //se imprime en cliente output.flush();//vacia contenido output.println(f); //cierra conexion clientSocket.close(); //notifica muerte de hilo JSystem.out.printColor(colorThread, JSystem.Color.white, "Server>"); JSystem.out.printColorln(JSystem.Color.red, " Thread [id: " + id + "] termina - " + Helper.fn.fecha_actual()); } catch (IOException | NumberFormatException e) { System.err.println(e.getMessage()); } } /** * Genera la sucesión de fibonacci * @param num * @return String resultado */ private String fibo(int num) { int fibo1 = 1; int fibo2 = 1; int aux = 1; String cadena = "1"; for (int i = 2; i <= num; i++) { fibo2 += aux; aux = fibo1; fibo1 = fibo2; cadena += " " + aux; } return cadena; } /** * genera un color aleatorio para el hilo * @return JSystem.ColorBg */ private JSystem.ColorBg bgColor() { int rnd; List<JSystem.ColorBg> VALUES = Collections.unmodifiableList(Arrays.asList(JSystem.ColorBg.values())); int SIZE = VALUES.size(); do { Random RANDOM = new Random(); rnd = RANDOM.nextInt(SIZE); } while (VALUES.get(rnd)==JSystem.ColorBg.black || VALUES.get(rnd)==JSystem.ColorBg.white); return VALUES.get(rnd); } }//end ClientThread
Clase MainServer
package server; import helper.JSystem; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * @see https://www.jc-mouse.net/ * @author mouse */ public class MainServer { /** * Puerto */ private final int PORT = 5000; /** * Identificador para Thread */ private int idThread = 0; /** * Para controlar la ejecución de los hilos */ private ThreadPoolExecutor pool; /** * Ejecuta el servidor * @see https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html */ public void run() { //para crear Thread según sea necesario, //pero volverá a utilizar hilos ya construidos cuando esten disponibles ExecutorService executor = Executors.newCachedThreadPool(); pool = (ThreadPoolExecutor) executor; try { //ejecuta hilo que imprime las estadisticas de creacion de hilos executor.submit(new ClientsThread()); //Socket de servidor para esperar peticiones de la red ServerSocket serverSocket = new ServerSocket(PORT); System.out.println("Server> Servidor iniciado"); while (true) { try { System.out.println(); //en espera de conexion, si existe la acepta Socket clientSocket = serverSocket.accept(); //crea un nuevo hilo para la petición executor.submit(new ClientThread(clientSocket, ++idThread)); } catch (IOException ex) {//error System.err.println(ex.getMessage()); } } } catch (IOException ex) { System.err.println(ex.getMessage()); } } /** * Hilo que imprime la cantidad de hilos activos y ejecutados en la aplicación * restamos 1 para no tomar en cuenta este mismo hilo */ public class ClientsThread implements Runnable { @Override public void run() { if(pool!=null) while (true) { try { Thread.sleep(6000);//6 segundos } catch (InterruptedException ex) { System.err.println(ex.getMessage()); } JSystem.out.printColorln(JSystem.Color.blue,"Server> Thread en ejecución: (" + (pool.getActiveCount()-1) + ") Thread generados: ("+(pool.getTaskCount()-1)+")"); } } } /** * @param args the command line arguments */ public static void main(String[] args) { MainServer server = new MainServer(); server.run(); } }
Ejecución
Primero ejecutamos el servidor (MainServer), a continuación el Cliente (MainClient), el servidor quedara a la espera de peticiones, el cliente enviara peticiones cada «N» tiempo, a continuación podemos ver un ejemplo:
Podemos observar como las peticiones van llegando y se van marcando con un color diferente cada uno de ellos para dar un mejor seguimiento a su ciclo de vida, advertimos también que los procesos no son secuenciales, sino que cada Thread sigue su propio ciclo de vida independiente del resto, así mismo el servidor nos avisa cuanto hilos están en ejecución en un momento dado y cuantos fueron generados.
Para simular varios clientes a la ves, podemos ejecutar varias instancias de MainClient como se ve en la siguiente imagen.
Y un video explicando todo:
enjoy!!!
¿Qué es SCRUM? SCRUM es un modelo de referencia que define un conjunto de prácticas y roles, y que puede tomarse como pu[...]
¿Qué es una vista? Una vista (View) o Tabla Virtual, es una forma lógica de ver los datos ubicados en varias tablas, es[...]
En este tuto realizaremos la implementación y ejecución de procedimientos almacenados de MySQL en Java, si quieres darle[...]
El siguiente código te permite abrir enlaces web desde un JLabel, ademas aprovechando el soporte a etiquetas HTML del co[...]
En este post vamos a construir un Servicio Web bajo el protocolo SOAP (Simple Object Access Protocol) el cual básicament[...]
La clase de java DatabaseMetaData nos permite obtener información exhaustiva sobre una base de datos, para ser más exact[...]