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!!!
Necesitamos: Android Studio 2 tipos de fuente TTF Agregar Archivo de fuente al proyecto Paso 1: Crear carpeta assets Cli[...]
Un archivo README.md es un archivo de texto escrito en el lenguaje de marcado Markdown, este se encuentra en la raíz de[...]
En el desarrollo web la elección de un buen editor de texto enriquecido (WYSIWYG) es una decisión crucial que afecta dir[...]
El SystemTray/Bandeja del Sistema o como también se le llama «Área de Notificación» es la sección de la barra de tareas[...]
Las recientes técnicas de traducción profunda de imagen a imagen permiten la generación rápida de imágenes faciales a pa[...]
En este tutorial conoceremos una forma de conectar una aplicación en Laravel con tres bases de datos diferentes, 2 en My[...]