In questa guida, JAVA – Server Multithread, realizziamo delle applicazioni di rete a livello di Socket con protocollo TCP in JAVA. L’idea di base è di consentire una connessione multipla di diversi client ad un server. Qui in basso e nella sezione download si possono scaricare i sorgenti
Sommario
Introduzione
Nello sviluppo di applicazioni di rete, un server deve essere in grado di gestire più client contemporaneamente, spesso in numero variabile e non prevedibile.
Un’implementazione basata su un singolo flusso di esecuzione risulterebbe rapidamente inefficiente, poiché ogni richiesta bloccherebbe le successive.
In questo articolo viene analizzato il concetto di server multithread in Java, affrontando sia gli aspetti teorici sia l’implementazione pratica tramite socket TCP.
L’obiettivo è comprendere:
- il funzionamento dei thread in Java;
- il ruolo dell’interfaccia
Runnablee della classeThread; - la realizzazione di un server capace di gestire più connessioni simultanee.
Prerequisiti
Prima di proseguire è consigliato predisporre correttamente l’ambiente di sviluppo.
La procedura completa è disponibile nella guida dedicata:
Server Multithread e Thread in Java
Quando più client si collegano allo stesso server, l’uso di un singolo flusso di esecuzione comporterebbe il blocco del servizio durante la gestione di ogni richiesta.
Il multithreading consente di risolvere questo problema assegnando a ciascun client un thread dedicato.
Concetti fondamentali
Un processo è composto da:
- Codice: la logica eseguibile
- Risorse: memoria, file, variabili
Il thread rappresenta l’unità di esecuzione che utilizza le risorse del processo.
Più thread possono convivere nello stesso processo condividendone lo spazio di memoria.
Thread Safety
Non tutte le applicazioni sono adatte all’esecuzione concorrente.
Un programma è thread-safe se garantisce che l’accesso simultaneo ai dati non produca risultati incoerenti.
Java fornisce diversi strumenti per la gestione delle risorse condivise, tra cui:
synchronizedlockvolatile
Creazione dei Thread in Java
Java offre due modalità principali:
- Implementazione dell’interfaccia
Runnable - Estensione della classe
Thread
In questo articolo viene privilegiata la prima soluzione, più flessibile e modulare.
Le interfacce in Java e il loro ruolo nel Multithreading
In Java, un’interfaccia rappresenta un contratto che definisce un insieme di metodi (astratti, dei default e statici) che una classe si impegna a implementare. A differenza delle classi, un’interfaccia non fornisce una concreta implementazione del comportamento, ma stabilisce cosa una classe deve fare, non come.
L’implementazione di un’interfaccia avviene tramite la parola chiave implements, e l’annotazione @Override consente di indicare esplicitamente che un metodo sta sovrascrivendo un metodo dichiarato nell’interfaccia.
Questo meccanismo è particolarmente importante nella progettazione di applicazioni modulari e riusabili, ed è alla base di molte soluzioni adottate da Java, inclusa la gestione dei thread tramite l’interfaccia Runnable.
Perché Runnable è un’interfaccia
Runnable impone l’implementazione di un solo metodo:
void run();Separando il comportamento del thread dal meccanismo di esecuzione, Java consente:
- maggiore modularità;
- riusabilità del codice;
- possibilità di estendere altre classi.
Per questi motivi, Runnable rappresenta la soluzione preferibile nelle applicazioni multithread moderne.
Implementazione dell’interfaccia Runnable
L’interfaccia Runnable definisce un unico metodo:
void run();Quando un oggetto Runnable viene passato a un’istanza di Thread e si invoca start(), il metodo run() viene eseguito in modo concorrente.
Esempio di Implementazione
// Implementazione dell'interfaccia Runnable
class MyTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Iterazione: " + i);
try {
Thread.sleep(1000); // Simula un'attività con una pausa di 1 secondo
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
// Creazione delle attivazioni (istanze di MyTask)
Runnable task1 = new MyTask();
Runnable task2 = new MyTask();
// Creazione degli esecutori (oggetti Thread)
Thread thread1 = new Thread(task1, "Thread 1");
Thread thread2 = new Thread(task2, "Thread 2");
// Avvio dei thread
thread1.start();
thread2.start();
}
}
Spiegazione del Codice
- La classe
MyTaskimplementaRunnable, definendo il metodorun(), che stampa un messaggio a ogni iterazione. -
Nel
main():- Vengono create due istanze della classe
MyTask. - Le istanze vengono passate a due oggetti
Thread. - I thread vengono avviati con
start(), eseguendo il metodorun()in parallelo.
- Vengono create due istanze della classe
Output Atteso:
Thread 1 - Iterazione: 0
Thread 2 - Iterazione: 0
Thread 1 - Iterazione: 1
Thread 2 - Iterazione: 1
Thread 1 - Iterazione: 2
Thread 2 - Iterazione: 2
...
Server TCP Multithread con Runnable
Obiettivo
Realizzare un server TCP che:
- accetta più client contemporaneamente,
- riceve stringhe di testo,
- restituisce la versione in maiuscolo.
Ogni client viene gestito tramite un thread dedicato.
Struttura del Server
Per realizzare il server, dobbiamo creare due classi principali:
-
Classe
Server(gestisce le connessioni e avvia i thread per ogni client). -
Classe
ServerThread(implementa l’interfacciaRunnablee gestisce la comunicazione con il client).
Passaggi chiave per l’implementazione del Server
- Creare la classe
Servercon:- Un costruttore per inizializzare il server.
-
Un metodo
connetti()che permette di accettare le connessioni dai client. -
Il metodo
main()che crea un’istanza del server e avvia la gestione delle connessioni.
- Creare la classe
ServerThreadcon:-
Un costruttore che riceve il
Socketdel client. -
Il metodo
run()che gestisce la comunicazione con il client (lettura, elaborazione e risposta).
-
Un costruttore che riceve il
Classe Server (Server.java)
import java.io.*;
import java.net.*;
// Classe principale del server
public class Server {
private static final int PORT = 12345; // Porta su cui il server ascolta
private ServerSocket serverSocket;
// Costruttore
public Server(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void Connetti() {
System.out.println("Server in ascolto sulla porta " + PORT);
try {
while (true) {
// Attende una connessione da un client
Socket clientSocket = serverSocket.accept();
System.out.println("Client connesso: " + clientSocket.getInetAddress());
// Crea un nuovo thread per gestire la comunicazione con il client
ServerThread attivaThread = new ServerThread(clientSocket); //attivo il thread
Thread thread = new Thread(attivaThread);//attivo l'esecutore
thread.start(); // Avvio il thread
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocket.close(); // Chiude il ServerSocket alla fine
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
Server server = new Server(PORT);
server.Connetti();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Classe per gestire la comunicazione con un client
class ServerThread implements Runnable {
private Socket clientSocket; // Socket del client
private BufferedReader in;
private PrintWriter out;
public ServerThread(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try {
// Crea i canali di comunicazione
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
out = new PrintWriter(clientSocket.getOutputStream(), true);
// Benvenuto e richiesta nome al client
String benvenuto = "Ciao, dimmi il tuo nome: ";
out.println(benvenuto);
// Lettura del nome del client
String nome = in.readLine();
System.out.println(nome + " si è connesso! IP: " + clientSocket.getInetAddress() + " Porta: " + clientSocket.getPort());
// Ciclo per leggere le stringhe inviate dal client
String inputLine;
while (true) {
out.println("Scrivi una stringa (0=Esci): ");
inputLine = in.readLine();
if (inputLine.equals("0")) {
System.out.println(nome + " si è disconnesso");
break; // Termina la connessione se il client invia "0"
}
// Risponde con la stringa in maiuscolo
out.println(inputLine.toUpperCase());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// Chiude le risorse
in.close();
out.close();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Struttura del Client
Il client stabilisce una connessione con il server, invia stringhe di testo e riceve le risposte elaborate.
Passaggi chiave per l’implementazione del Client
- Creare la classe Client con:
- Un costruttore
-
Un metodo crea_
canali()che permette di creare i canali di comunicazione con il Server -
Il metodo
comunica()per comunicare con il Server - Il metodo chiudi_connessione() per chiudere la connessione con il Server
- il metodo main()
Classe Client (Client1.java)
package cs_tcp_06_Multithread_toUpperCase_runnable;
import java.io.;
import java.net.;
public class Client1 {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
private BufferedReader tastiera;
private static final String SERVER_ADDRESS = "localhost"; // Indirizzo del server
private static final int PORT = 12345; // Porta del server
// Costruttore
public Client1(String host, int port) throws IOException {
socket = new Socket(SERVER_ADDRESS, PORT);
tastiera = new BufferedReader(new InputStreamReader(System.in));
}
// Metodo per creare i canali di comunicazione
private void creaCanali() throws IOException {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
}
// Metodo per la comunicazione con il server
private void comunica() throws IOException {
String messaggioDalServer;
String userInput;
// Ricezione messaggio di benvenuto
messaggioDalServer = in.readLine();
System.out.println(messaggioDalServer);
// Invia il nome al server
userInput = tastiera.readLine();
out.println(userInput); // Invio del nome al server
// Ciclo per inviare messaggi al server
while (true) {
// Ricevi il messaggio dal server (invito a scrivere una stringa)
messaggioDalServer = in.readLine();
System.out.println(messaggioDalServer);
// Leggi input dell'utente da tastiera
userInput = tastiera.readLine();
out.println(userInput); // Invia la stringa al server
// Se l'input è "0", termina la connessione
if (userInput.equals("0")) {
System.out.println("Disconnessione dal server...");
break;
}
// Ricevi e stampa la risposta del server
messaggioDalServer = in.readLine();
System.out.println("Risposta dal server: " + messaggioDalServer);
}
// Chiudi le risorse alla fine
chiudiConnessione();
}
// Metodo per chiudere le risorse
private void chiudiConnessione() throws IOException {
in.close();
out.close();
socket.close();
tastiera.close();
System.out.println("Connessione chiusa.");
}
public static void main(String[] args) throws IOException {
Client1 client1 = new Client1(SERVER_ADDRESS, PORT);
client1.creaCanali();
client1.comunica();
}Considerazioni finali
Il client, insieme al server multithread, realizza un semplice ma completo sistema client–server TCP in Java.
Ogni client comunica con il server attraverso una connessione dedicata, mentre il server è in grado di gestire più client simultaneamente grazie all’uso dei thread.
Questo esempio rappresenta una base solida per comprendere la comunicazione di rete in Java e per sviluppare applicazioni distribuite più avanzate.
Runnable vs Thread
Oltre alla soluzione con Runnable, è possibile implementare un server multithread estendendo direttamente la classe Thread. Questo approccio permette di associare il comportamento del thread alla gestione del singolo client senza dover creare un oggetto Thread separato.
L’esempio proposto replica lo stesso esercizio: il server accetta più connessioni, riceve stringhe dai client e restituisce le stesse stringhe in maiuscolo. L’unica differenza è che la classe che gestisce il client estende Thread.
Server.java
package cs_tcp_07_Multithread_toUpperCase_extendsThread;
import java.io.*;
import java.net.*;
// Classe principale del server
public class Server {
private static final int PORT = 12345; // Porta su cui il server ascolta
private ServerSocket serverSocket;
// Costruttore
public Server(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
// Metodo per iniziare ad ascoltare le connessioni
public void Connetti() {
System.out.println("Server in ascolto sulla porta " + PORT);
try {
while (true) {
// Attende una connessione da un client
Socket clientSocket = serverSocket.accept();
System.out.println("Client connesso: " + clientSocket.getInetAddress());
// Assegna un nuovo thread per gestire la comunicazione con il client
ServerThread thread = new ServerThread(clientSocket);
thread.start(); // Avvia il thread
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocket.close(); // Chiude il ServerSocket alla fine
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
Server server = new Server(PORT);
server.Connetti();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Sottoclasse di Thread per gestire la comunicazione con un client
class ServerThread extends Thread {
private Socket clientSocket;
private BufferedReader in;
private PrintWriter out;
//costruttore
public ServerThread(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try {
// Crea i canali di comunicazione
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
out = new PrintWriter(clientSocket.getOutputStream(), true);
// Benvenuto e richiesta nome al client
String benvenuto = "Ciao, dimmi il tuo nome: ";
out.println(benvenuto);
// Lettura del nome del client
String nome = in.readLine();
System.out.println(nome + " si è connesso! IP: " + clientSocket.getInetAddress() + " Porta: " + clientSocket.getPort());
// Ciclo per leggere le stringhe inviate dal client
String inputLine;
while (true) {
out.println("Scrivi una stringa (0=Esci): ");
inputLine = in.readLine();
if (inputLine.equals("0")) {
System.out.println(nome + " si è disconnesso");
break; // Termina la connessione se il client invia "0"
}
// Risponde con la stringa in maiuscolo
out.println(inputLine.toUpperCase());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// Chiude le risorse
in.close();
out.close();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Spiegazione del codice:
- La classe
ServerThreadestendeThread. Questo permette di chiamare direttamentestart()sul thread appena creato senza dover passare un oggettoRunnable. - Il flusso di comunicazione con il client è identico all’esempio precedente: messaggio di benvenuto, lettura del nome e ciclo di scambio stringhe.
- Il server può gestire più client contemporaneamente grazie alla creazione di un nuovo thread per ogni connessione.
Client1.java
Il client rimane invariato rispetto all’esempio con Runnable, perché non dipende dalla modalità con cui il server gestisce i thread
Test dell’applicazione
Per verificare il funzionamento:
- Aprire Visual Studio Code o un altro IDE.
- Eseguire il Server (
Server.java). - Avviare uno o più Client (
Client1.java). - Digitare i messaggi nel client e osservare la risposta in maiuscolo dal server.
- Inserire
"0"per terminare la sessione del client.
Considerazioni didattiche
- Questo esempio mostra che Java permette due approcci principali per creare server multithread:
- Implementare
Runnablee passarlo a unThread. - Estendere direttamente la classe
Thread.
- Implementare
- Il comportamento finale è identico, ma l’estensione di
Threadè più immediata per chi vuole associare direttamente il thread alla gestione del client. - La scelta tra
Runnableed estensione diThreaddipende spesso da esigenze di progettazione e dalla necessità di ereditare da altre classi.
Nella sezione Download dedicata puoi scaricare i codici sorgente di applicazioni Server Multithread con utilizzo di Socket TCP

Lascia un commento