Aprende Java Aprende Php Aprende C++ Aprende HTML 5 Aprende JavaScript Aprende JSON Aprende MySQL Aprende SQLServer Aprende Visual Basic 6 Aprende PostgreSQL Aprende SQLite Aprende Redis Aprende Kotlin Aprende XML Aprende Linux VSC Aprende Wordpress Aprende Laravel Aprende VueJS Aprende JQuery Aprende Bootstrap Aprende Netbeans Aprende Android
Sigueme en Facebook Sigueme en Twitter Sigueme en Instagram Sigueme en Youtube Sigueme en TikTok Sigueme en Whatsapp
Home / Proyectos / MultiHilos: Comunicación Cliente/Servidor en Java

MultiHilos: Comunicación Cliente/Servidor en Java

Por jc mouse jueves, septiembre 29, 2016

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 🙂

java thread

¿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:

netbeans thread

Paquete «client«:

  • MainClient.java Clase principal para el Cliente
  • ServerThread.java Hilo para interactuar con el servidor

Paquete «helper«

  • Helper.java Contiene metodos usados tanto por el Cliente como el Servidor
  • JSystem.java Clase para colorear consola

Paquete «server«

  • ClientThread.java Hilo para interactuar con el cliente
  • MainServer.java Clase principal para el servidor

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:

  • La aplicación principal (MainServer) estará a la espera de nuevas conexiones
  • Cuando una nueva conexión llega, acepta la conexión en un socket el cual pasa como parámetro a un nuevo hilo (ClientThread).
  • Mientras la petición se procesa en paralelo al hilo principal, el servidor continua a la espera de otra conexión.
  • Este ciclo se repite mientras el servidor este activo

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:

arquitectura sistemas

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.

multithreaded

Y un video explicando todo:

enjoy!!!

Tags

Artículos similares

JSCRUM .:. Gestor de Proyectos

¿Qué es SCRUM? SCRUM es un modelo de referencia que define un conjunto de prácticas y roles, y que puede tomarse como pu[...]

MariaDB: Tablas Virtuales (Vistas – View)

¿Qué es una vista? Una vista (View) o Tabla Virtual, es una forma lógica de ver los datos ubicados en varias tablas, es[...]

Procedimientos almacenados en java

En este tuto realizaremos la implementación y ejecución de procedimientos almacenados de MySQL en Java, si quieres darle[...]

Abrir enlace web desde JLabel con Java

El siguiente código te permite abrir enlaces web desde un JLabel, ademas aprovechando el soporte a etiquetas HTML del co[...]

Servicio Web SOAP en Java

En este post vamos a construir un Servicio Web bajo el protocolo SOAP (Simple Object Access Protocol) el cual básicament[...]

Obtener Metadatos de una base de datos con java

La clase de java DatabaseMetaData nos permite obtener información exhaustiva sobre una base de datos, para ser más exact[...]