<div class="page"> <div class="cover text-center"> <img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo"> # Servidor multiclient <div class="text-end fit-content ms-auto my-3 mt-auto pt-3"> <p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p> <p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p> <p><strong>Curs:</strong> 2024/2025</p> </div> <div> <p class="fw-bold mb-0">Llicència: BY-NC-SA</p> <p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p> <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank"> <img class="mx-auto" src="/itb/images/license.png" alt="Licence"/> </a> </div><!--license--> </div><!--cover--> </div><!--page--> {:toc} ## Objectius En aquest exemple es mostrarà com es poden crear un servidor capaç de gestionar diferents clients de manera simultània. El codi està disponible també a GitHub: https://github.com/fpmislata-dam2s-psp/PuigcerverJoan-PSP-solucions/tree/main/src/ud3/examples ## Client: MultiClient ### Codi font ```java package ud3.examples.multiclient.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ConnectException; import java.net.Socket; import java.util.Scanner; public class MultiClient { public static void main(String[] args) { try { String host = "localhost"; int port = 1234; System.out.println("Creant el Socket client."); Socket socket = new Socket(host, port); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // Es pot utilitzar l'opció autoflush PrintWriter out = new PrintWriter(socket.getOutputStream(), true); Scanner scanner = new Scanner(System.in); // Identifiquem el client en el servidor. // El servidor el primer que fa és esperar el nom System.out.print("Introdueix el teu nom: "); String nom = scanner.nextLine(); out.println(nom); // El client pot enviar missatges fins que escriga END String line; System.out.print("Text: "); while(!(line = scanner.nextLine()).equals("END")){ out.println(line); System.out.print("Text: "); } // Tanquem la connexió al acabar socket.close(); } catch (ConnectException e) { System.err.println("Connection refused!"); } catch (IOException e) { throw new RuntimeException(e); } } } ``` ### Explicació Aquest programa crea un client que es connecta al servidor. Després, demana un nom per identificar-se i permeteix escriure missatges al servidor fins que s'escriga la paraula END. Per poder executar a la vegada varies instàncies de `MultiClient` en __IntelliJ__, cal que modifiqueu la configuració d'execució. Heu d'anar a: __Run > Edit Configurations... > Sel·lecionar MultiClient > Build and run > Modify options > Marcar "Allow multiple instances"__ ## Servidor: StartMulticlientServer ### Codi font ```java package ud3.examples.multiclient.server; import java.io.IOException; import java.util.Scanner; public class StartMulticlientServer { public static void main(String[] args) { try { Scanner scanner = new Scanner(System.in); MulticlientServer server = new MulticlientServer(1234); server.start(); scanner.nextLine(); server.close(); } catch (IOException e) { throw new RuntimeException(e); } } } ``` ### Explicació Aquest programa crea una instància del servidor `MulticlientServer` i l'inicia. ## Servidor: MulticlientServer ### Codi font ```java package ud3.examples.multiclient.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class MulticlientServer extends Thread { private final ServerSocket server; private final List<ServerHandler> clients; private boolean running; /** * Crea un servidor ServerSocket a partir del port. * * @param port Port on escoltarà el servidor * @throws IOException Excepcions del constructor ServerSocket */ public MulticlientServer(int port) throws IOException { server = new ServerSocket(port); clients = new ArrayList<>(); running = true; } /** * Para l'execució del servidor i totes les gestions dels clients */ public void close(){ running = false; for (ServerHandler client : clients) { try { client.close(); } catch (IOException ignored) { } client.interrupt(); } this.interrupt(); } /** * Fil d'execució del servidor. * <p> * El servidor escolta el port i espera noves connexions. * Quan una nou client es connecta, es crea un objecte ServerHandler, * que gestionarà la comunicació amb aquest client en un fil distint. * <p> * D'aquesta manera, el servidor pot continuar escoltant i esperant * noves connexions mentres cada fil gestiona la comunicació * amb cada client. */ @Override public void run() { while (running){ try { Socket client = server.accept(); System.out.println("Nou client acceptat."); ServerHandler handler = new ServerHandler(client); clients.add(handler); handler.start(); } catch (IOException e) { System.err.println("Error while accepting new connection"); System.err.println(e.getMessage()); } } } } ``` ### Explicació Aquesta classe implementa un servidor capaç de gestionar multiples clients. Aquesta classe també està pensada per ser executada en un fil independent. El servidor s'inicia i espera connexions. Quan una connexió es realitzada per part d'un client, el servidor crea i inicia una instància de la classe `ServerHandler`, que s'encarrega de gestionar les repostes amb el client. ## Servidor: ServerHandler ### Codi font ```java package ud3.examples.multiclient.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; /** * Classe que gestiona la comunicació del servidor * amb un únic client en un fil d'execució independent. */ public class ServerHandler extends Thread { private final Socket socket; private final BufferedReader in; private final PrintWriter out; private String nom; public Server(Socket socket) throws IOException { this.socket = socket; in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); nom = null; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public void close() throws IOException { socket.close(); } /** * Fil d'execució independent per cada client. * <p> * Abans que res, el client s'identifica amb un nom. * Després, el servidor mostra els missatges que cada client ha enviat. */ @Override public void run() { try { setNom(in.readLine()); System.out.printf("%s s'ha identificat.\n", getNom()); // Quan un client es desconecta, l'operació readLine() retorna null String message; while((message = in.readLine()) != null){ System.out.printf("%s: %s\n", getNom(), message); } System.out.printf("%s has disconnected.\n", getNom()); close(); } catch (IOException e) { System.err.println("Error while handling client."); System.err.println(e.getMessage()); } } } ``` ### Explicació Aquesta classe implementa les respostes del servidor a un únic client en un fil independent. Aquest fil li demana el nom per identificar el client i després mostrarà tots els missatges que el client envie al servidor fins que aquest acabe la connexió.