UDP in Java - DatagramSocket e DatagramPacket | Tecnologia Anno 5 Modulo 2 Lezione 3 | Prof. Giagnotti
Tecnologia · Anno 5 · Modulo 2 · Lezione 3
✍ Autore: profgiagnotti · Materia: Tecnologia · UDP vs TCP

Protocollo UDP in Java: datagrammi, DatagramSocket e DatagramPacket

Dalla connessione stabile TCP al mondo senza connessione di UDP: ogni pacchetto viaggia da solo, con IP e porta già scritti sopra. Costruiamo un server che risponde in maiuscolo.

Anno 5Modulo 2 Lezione 3UDP DatagramJava
OBIETTIVI DI APPRENDIMENTO
✓Distinguere il funzionamento di UDP da quello di TCP, riconoscendone vantaggi, limiti e ambiti d'uso tipici
✓Comprendere il modello di comunicazione datagram-based e il ruolo delle informazioni contenute in ogni singolo pacchetto
✓Utilizzare le classi Java DatagramSocket e DatagramPacket per inviare e ricevere dati
✓Scrivere, eseguire e leggere un client e un server UDP completi, riconoscendo il ruolo di ogni singola riga di codice

Nella lezione precedente abbiamo costruito un client e un server TCP: una comunicazione affidabile, ordinata, con una connessione stabile che resta aperta finché non la chiudiamo esplicitamente. Questa prova è particolarmente utile per capire il ruolo del protocollo di trasporto sottostante, perché oggi vediamo l'esatto opposto: il protocollo UDP (User Datagram Protocol), dove non esiste alcuna connessione persistente, e ogni singolo messaggio deve portare con sé tutte le informazioni necessarie a raggiungere il destinatario. Costruiremo di nuovo un'applicazione client-server, questa volta basata su datagrammi: il client invierà una stringa, il server la restituirà convertita in maiuscolo.

💬 Nota docente - Perché facciamo UDP dopo TCP

Con TCP avete visto il telefono: compongo numero, aspetto risposta, parlo, chiudo. Con UDP vediamo la posta: scrivo indirizzo su busta, imbucò, non so se arriva, non so se arriva in ordine. Perché vi mostro entrambi? Perché nella vita reale scegliete voi quale usare. Se fate un DNS, un videogioco a 60fps, una chiamata VoIP — usate UDP. Se fate un sito, una banca, una chat che non può perdere messaggi — TCP. Oggi capiamo quando la velocità vale più dell'affidabilità.

1. Il Protocollo UDP in Java

UDP è un protocollo di livello di trasporto che si distingue nettamente da TCP per il suo approccio alla comunicazione. A differenza di TCP, UDP non è orientato alla connessione: tra client e server non viene stabilito alcun canale persistente prima dell'invio dei dati.

Quando un'applicazione utilizza UDP, i messaggi vengono inviati sotto forma di datagrammi, ciascuno dei quali è indipendente dagli altri. Non esiste un meccanismo di handshaking iniziale, e il protocollo non garantisce che i pacchetti arrivino effettivamente a destinazione né che arrivino nello stesso ordine in cui sono stati inviati.

Questo non significa che UDP sia privo di controlli: viene comunque verificata l'integrità dei dati tramite checksum. Tuttavia, eventuali pacchetti persi o duplicati non vengono gestiti automaticamente dal protocollo: se la tua applicazione ha bisogno di quella garanzia, deve implementarla da sé, a livello applicativo.

Proprio grazie a questa semplicità strutturale, UDP risulta estremamente veloce ed efficiente. Per questo motivo viene utilizzato in numerose applicazioni reali, come il servizio DNS, lo streaming multimediale e tutte quelle situazioni in cui la rapidità di trasmissione è più importante dell'affidabilità assoluta — esattamente i casi d'uso che avevamo già anticipato parlando delle famiglie di socket.

📖 Definizione - UDP

User Datagram Protocol: protocollo di trasporto senza connessione, non affidabile, non ordinato, ma velocissimo. Ogni datagramma è indipendente, contiene già IP e porta destinazione, verifica solo checksum. Ideale quando velocità > affidabilità.

AspettoTCP (lezione precedente)UDP (questa lezione)
ConnessioneStabilita prima dello scambio dati (three-way handshake)Assente: ogni datagramma viaggia per conto proprio
AffidabilitàGarantita: nessuna perdita, nessuna duplicazione, ordine preservatoNon garantita: possibili perdite, duplicazioni, riordini
Classi JavaSocket, ServerSocketDatagramSocket, DatagramPacket
VelocitàPiù lenta, per via del controllo di affidabilitàPiù veloce, grazie all'assenza di controlli
Esempi d'usoWeb, email, trasferimento fileDNS, streaming multimediale, gaming online
🎯 Esempio reale - Perché DNS usa UDP?

Quando digiti profgiagnotti.altervista.org il browser fa una richiesta DNS per sapere l'IP. Se usasse TCP: handshake (3 pacchetti) + richiesta + risposta + chiusura (4 pacchetti) = 8 pacchetti per un IP. Con UDP: 1 richiesta + 1 risposta = 2 pacchetti. 4 volte più veloce. Se il pacchetto si perde? Il client lo rimanda dopo 2 secondi. Per una query da 50 byte, UDP è imbattibile.

2. Comunicazione datagram e modello client-server

La comunicazione UDP è spesso definita datagram-based. Ogni messaggio contiene al proprio interno tutte le informazioni necessarie per raggiungere il destinatario, come indirizzo IP e numero di porta. Non esiste quindi uno stato condiviso tra client e server: a differenza di quanto accadeva con lo stream socket TCP, qui non c'è un accept() che "ricorda" chi è connesso.

In un sistema client-server basato su UDP, il client invia una richiesta al server specificando l'indirizzo e la porta di destinazione. Il server, una volta ricevuto il datagramma, elabora il contenuto e invia una risposta utilizzando le informazioni presenti nel pacchetto ricevuto: è il pacchetto stesso, non una connessione aperta, a dire al server dove rispondere.

Questo modello rende la comunicazione semplice e leggera, ma richiede attenzione nella progettazione dell'applicazione, poiché la gestione di errori, ritrasmissioni o perdita di dati deve essere eventualmente implementata a livello applicativo.

💬 Nota docente - La differenza che devi saper disegnare all'orale

Se all'orale vi chiedo "disegnate TCP vs UDP", voglio vedere: TCP = linea continua tra client e server, con scritta "connessione aperta". UDP = tante freccette separate, ognuna con etichetta "IP:porta + dati". In TCP il server ricorda chi è connesso (accept). In UDP il server non ricorda nulla: ogni volta legge IP e porta dal pacchetto che arriva. È questa la differenza concettuale più importante.

3. Le classi Java per la comunicazione UDP

Java fornisce un supporto diretto alla comunicazione UDP attraverso il package java.net, lo stesso che abbiamo già usato per TCP. Le due classi fondamentali, questa volta, sono DatagramSocket e DatagramPacket.

ClasseRuolo
DatagramSocketRappresenta il socket UDP vero e proprio. Può essere creato senza specificare una porta (tipico del client, che ne riceve una dinamica) oppure associato a una porta specifica (tipico del server)
DatagramPacketRappresenta il singolo pacchetto di dati che viaggia sulla rete: contiene i dati da trasmettere, l'indirizzo IP del destinatario e la porta. In ricezione, permette anche di recuperare indirizzo e porta del mittente
📖 Analogia - Telefono vs Busta

DatagramSocket è il "telefono" (il dispositivo fisico che invia/riceve). DatagramPacket è la "busta chiusa" che parte già indirizzata — a differenza di una telefonata TCP, dove prima componi il numero e resti in linea, qui spedisci direttamente la busta e nient'altro. In questa fase conviene concentrarsi prima sul funzionamento generale delle due classi, prima di vederle applicate nel codice completo.

4. Realizzazione di un'applicazione client-server UDP

Per comprendere concretamente il funzionamento di UDP in Java, realizziamo una semplice applicazione client-server. Il client invierà una stringa di testo al server, e il server risponderà restituendo la stessa stringa convertita in maiuscolo. L'esempio è volutamente semplice, ma consente di osservare tutti i passaggi fondamentali della comunicazione UDP.

4.1 Implementazione del server UDP

Il server UDP ha il compito di restare in ascolto su una porta ben definita — in questo esempio la 12345 — ricevere i messaggi inviati dai client ed elaborare una risposta. Dopo aver creato il DatagramSocket, il server entra in un ciclo continuo in cui attende la ricezione dei pacchetti: quando un messaggio arriva, il contenuto viene estratto dal datagramma, convertito in maiuscolo e inviato nuovamente al client, usando l'indirizzo IP e la porta letti direttamente dal pacchetto ricevuto. Se il messaggio ricevuto è la stringa "0", il server termina l'esecuzione e chiude il socket.

💬 Nota docente - Perché questo esempio è furbo

Ho scelto questo esempio perché può essere facilmente modificato in seguito — per esempio calcolando qualcos'altro invece del maiuscolo — senza dover toccare la parte di comunicazione, che resta sempre la stessa. Volete fare un server che conta le vocali? Cambiate solo toUpperCase() con la vostra logica. Volete fare un eco-server? Rimandate indietro la stessa stringa. La parte UDP resta identica: receive → elabora → send.

JAVA - UDPSERVER.JAVA COMMENTATO RIGA PER RIGA
import java.io.*; // per IOException import java.net.*; // per DatagramSocket, DatagramPacket, InetAddress public class UDPServer { // dichiarazione della classe pubblica UDPServer public static void main(String[] args) { // punto di ingresso del programma try { // apre il blocco che gestisce le eccezioni di I/O // crea il socket UDP: il server sarà raggiungibile sulla porta 12345 DatagramSocket UDPserver = new DatagramSocket(12345); DatagramPacket datagrammaRicevuto; // conterrà, di volta in volta, il pacchetto in arrivo DatagramPacket datagrammaDaInviare; // conterrà, di volta in volta, il pacchetto di risposta boolean attivo = true; // condizione del ciclo principale: finché è true, il server resta acceso byte[] bufferIN = new byte[1024]; // area di memoria che ospiterà i dati ricevuti (max 1024 byte) byte[] bufferOUT; // area di memoria che ospiterà i dati da inviare (dimensione variabile) System.out.println("Server avviato..."); // conferma a console che il server è pronto while (attivo) { // ciclo principale: si ripete a ogni nuovo messaggio ricevuto // ricrea il pacchetto di ricezione a ogni giro, puntando al buffer di ingresso datagrammaRicevuto = new DatagramPacket(bufferIN, bufferIN.length); // receive() blocca l'esecuzione finché non arriva un datagramma dalla rete UDPserver.receive(datagrammaRicevuto); // costruisce una String a partire dai soli byte realmente ricevuti (non tutto il buffer) String ricevuto = new String(datagrammaRicevuto.getData(), 0, datagrammaRicevuto.getLength()); System.out.println("Messaggio ricevuto: " + ricevuto); // stampa il messaggio a console // il pacchetto ricevuto "sa" chi l'ha spedito: lo recuperiamo per poter rispondere InetAddress IPClient = datagrammaRicevuto.getAddress(); // IP del client mittente int portaClient = datagrammaRicevuto.getPort(); // porta del client mittente // prepara il messaggio di risposta, trasformandolo in maiuscolo String messaggioOUT = ricevuto.toUpperCase(); bufferOUT = messaggioOUT.getBytes(); // converte la stringa di risposta in byte grezzi // costruisce il pacchetto di risposta, indirizzato esplicitamente a IPClient:portaClient datagrammaDaInviare = new DatagramPacket(bufferOUT, bufferOUT.length, IPClient, portaClient); UDPserver.send(datagrammaDaInviare); // invia il pacchetto di risposta sulla rete // condizione di uscita: se il client ha inviato "0", il server si ferma if (ricevuto.equals("0")) { System.out.println("Arrivederci..."); // messaggio di commiato attivo = false; // fa terminare il ciclo while al prossimo controllo } } UDPserver.close(); // chiude il socket, liberando la porta 12345 } catch (IOException e) { // intercetta qualunque errore di rete/IO nel blocco try e.printStackTrace(); // stampa lo stack completo dell'errore, utile in fase di debug } } }

4.2 Descrizione del codice UDPServer.java

  • Creazione del socket: viene creato un oggetto DatagramSocket sulla porta 12345; si definiscono due buffer (uno per la ricezione, uno per la trasmissione, entrambi da 1024 byte) e una variabile booleana per gestire il ciclo di esecuzione del server.
  • Attesa e ricezione dati dal client: il server attende la ricezione di un pacchetto UDP tramite receive(); il messaggio viene estratto dal datagramma e stampato a console.
  • Preparazione della risposta: il messaggio ricevuto viene convertito in maiuscolo, il server identifica indirizzo IP e porta del client dal pacchetto stesso, e costruisce un nuovo DatagramPacket da inviare come risposta.
  • Gestione dell'uscita: se il client invia "0", il server termina l'elaborazione, esce dal ciclo e chiude il socket.
💡 Tips - Buffer 1024 byte

Perché 1024? È una dimensione comoda per messaggi di testo. Ma UDP ha un limite teorico di 65507 byte di payload (65535 - header IP - header UDP). Se inviate più di 1024 byte e il buffer è più piccolo, i dati in eccesso vengono troncati silenziosamente senza errore — uno dei bug più subdoli di UDP. Per testi brevi 1024 va bene, per file usate buffer più grande o frammentate voi a livello applicativo.

4.3 Implementazione del client UDP

Il client ha un comportamento più semplice rispetto al server. Dopo aver creato il socket, chiede all'utente di inserire una stringa da tastiera; il messaggio viene poi inviato al server sotto forma di datagramma. Successivamente, il client si mette in attesa della risposta: quando il pacchetto arriva, il messaggio viene estratto e visualizzato a schermo, e il socket viene chiuso.

🔍 Nota di laboratorio - Errore corretto

Durante la prova ho trovato un errore nella prima versione del codice: il messaggio di richiesta input conteneva un carattere "n" residuo (probabilmente un \n rimasto a metà da una copia frettolosa), che sarebbe finito stampato a schermo alla lettera. Ho corretto il problema verificando riga per riga il testo passato a println(), ed è un buon promemoria: controlla sempre le stringhe letterali prima di eseguire. In laboratorio ho visto studenti perdere 20 minuti perché avevano scritto "Inserisci una stringan:" invece di "Inserisci una stringa:" — copiate con attenzione.

JAVA - UDPCLIENT.JAVA COMMENTATO RIGA PER RIGA
import java.io.*; // per BufferedReader, InputStreamReader import java.net.*; // per DatagramSocket, DatagramPacket, InetAddress public class UDPClient { // dichiarazione della classe pubblica UDPClient public static void main(String[] args) { // punto di ingresso del programma try { // apre il blocco che gestisce le eccezioni di I/O int portaServer = 12345; // porta su cui sappiamo essere in ascolto il server InetAddress IPServer = InetAddress.getByName("localhost"); // risolve l'indirizzo del server byte[] bufferIN = new byte[1024]; // area di memoria per i dati ricevuti byte[] bufferOUT; // area di memoria per i dati da inviare // stream per leggere una riga di testo digitata dall'utente a tastiera BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); DatagramSocket clientSocket; // il socket UDP del client DatagramPacket datagrammaDaInviare; // il pacchetto che invieremo al server DatagramPacket datagrammaRicevuto; // il pacchetto che riceveremo come risposta // crea il socket senza specificare una porta: il sistema operativo ne assegna una libera clientSocket = new DatagramSocket(); System.out.println("Inserisci una stringa: "); // richiede l'input all'utente // legge la riga digitata dall'utente (bloccante finché non premi Invio) String messaggioDaInviare = input.readLine(); bufferOUT = messaggioDaInviare.getBytes(); // converte la stringa in byte grezzi // costruisce il pacchetto da inviare, indirizzato esplicitamente a IPServer:portaServer datagrammaDaInviare = new DatagramPacket(bufferOUT, bufferOUT.length, IPServer, portaServer); clientSocket.send(datagrammaDaInviare); // invia il pacchetto sulla rete // prepara il pacchetto che ospiterà la risposta, puntando al buffer di ingresso datagrammaRicevuto = new DatagramPacket(bufferIN, bufferIN.length); clientSocket.receive(datagrammaRicevuto); // attende (bloccando) la risposta del server // costruisce una String a partire dall'INTERO buffer (non solo dai byte ricevuti: vedi sotto) String ricevuto = new String(datagrammaRicevuto.getData()); int numCaratteri = datagrammaRicevuto.getLength(); // numero di byte realmente arrivati ricevuto = ricevuto.substring(0, numCaratteri); // taglia via gli eventuali byte residui del buffer System.out.println("Messaggio ricevuto dal server: " + ricevuto); // stampa la risposta clientSocket.close(); // chiude il socket e libera la porta assegnata dinamicamente } catch (IOException e) { // intercetta qualunque errore di rete/IO nel blocco try e.printStackTrace(); // stampa lo stack completo dell'errore, utile in fase di debug } } }

Attenzione — perché serve substring() qui e non nel server

Nel server avevamo usato new String(dati, 0, lunghezza), che estrae subito solo i byte realmente ricevuti. Nel client, il codice costruisce prima la String dall'intero buffer con new String(datagrammaRicevuto.getData()), e solo dopo la taglia con substring(0, numCaratteri). Il risultato finale è identico, ma se dimentichi quel substring() ti ritroverai in coda alla risposta gli eventuali byte "vuoti" rimasti nel buffer da 1024 posizioni: un errore silenzioso, difficile da notare finché non stampi la lunghezza esatta della stringa.

⚠️ Bug silenzioso - Buffer residui

Esempio: invii "ciao" (4 byte) in buffer da 1024. Se fai new String(buffer) senza tagliare, ottieni "ciao" + 1020 caratteri strani (spazi, \0, residui precedenti). Con getLength() sai che sono arrivati 4 byte, con substring(0,4) prendi solo "ciao". Nel server abbiamo usato il costruttore con offset e lunghezza, che fa la stessa cosa in un colpo solo: new String(data, 0, length) è più elegante, preferitelo.

4.4 Descrizione del codice UDPClient.java

  • Configurazione della comunicazione: si definiscono i buffer per trasmissione e ricezione, i riferimenti del server (indirizzo IP e porta 12345) e si crea il DatagramSocket del client.
  • Invio del messaggio al server: il client chiede all'utente una stringa, la converte in byte e la invia tramite un DatagramPacket indirizzato al server.
  • Attesa e ricezione della risposta: il client si blocca in attesa della risposta; una volta ricevuto il pacchetto, il messaggio viene estratto e stampato.
  • Chiusura della connessione: dopo aver ricevuto la risposta, il client chiude il socket e termina l'esecuzione.

5. Il caso limite: terminare il server

Ho provato volutamente anche un caso limite, perché è il tipo di dettaglio che negli esempi didattici viene spesso lasciato implicito: cosa succede se l'utente, dal client, digita esattamente la stringa "0"? Il server la riceve, la converte comunque in maiuscolo e risponde normalmente ("0" maiuscolo è ancora "0"), ma subito dopo aver inviato la risposta controlla il messaggio originale ricevuto: se corrisponde a "0", stampa un messaggio di commiato, porta la variabile attivo a false, e al giro successivo del ciclo while il server esce e chiude il socket. È un meccanismo di terminazione volutamente elementare, adatto a un esempio didattico: in un'applicazione reale utilizzeresti probabilmente un comando dedicato, distinto dai normali messaggi applicativi.

🔧 Miglioria possibile - Comando dedicato

Invece di usare "0" come stringa normale, potreste usare un comando tipo "/quit" o "EXIT". Così evitate che un utente che vuole convertire davvero "0" in maiuscolo faccia spegnere il server per sbaglio. Esempio: if (ricevuto.equalsIgnoreCase("/quit")) attivo = false; — più esplicito e sicuro. In applicazioni reali, i comandi di controllo sono sempre distinti dai dati.

6. Esecuzione e test dell'applicazione

Per testare l'applicazione è sufficiente avviare prima il server e, successivamente, il client, utilizzando Visual Studio Code o un qualsiasi ambiente di sviluppo Java. Inserendo una stringa dal client, il server risponderà restituendo la versione in maiuscolo. Se il client invia il valore "0", il server terminerà la propria esecuzione.

Nel file .zip che trovi nelle risorse ho creato due differenti client testando la possibilità che si possano connettere contemporaneamente. L'output a console è il seguente:

OUTPUT - UDP SERVER
Server avviato... Messaggio ricevuto: ciao mondo Messaggio ricevuto: prova udp Messaggio ricevuto: 0 Arrivederci...
OUTPUT - UDP CLIENT 1
Inserisci una stringa: ciao mondo Messaggio ricevuto dal server: CIAO MONDO
OUTPUT - UDP CLIENT 2 (contemporaneo)
Inserisci una stringa: prova udp Messaggio ricevuto dal server: PROVA UDP
💬 Nota docente - Due client contemporaneamente? Con UDP sì, facile!

Con TCP servivano thread perché accept() creava un socket per ogni client e la connessione restava aperta. Con UDP non c'è connessione: il server fa solo receive() → elabora → send(). Quindi può gestire Client1, poi Client2, poi ancora Client1 senza thread — basta che i pacchetti arrivino uno dopo l'altro. Ho testato due client in contemporanea proprio per farvi vedere questa differenza: con UDP non serve complicarsi la vita per il multi-client base.

6.1 TCP vs UDP fianco a fianco - stessa app, filosofie opposte

Ho confrontato due approcci differenti prima di scegliere questo — TCP nella lezione precedente, UDP in questa — proprio per farteli vedere fianco a fianco: stesso obiettivo (uno scambio di messaggi tra client e server), due filosofie opposte. Con TCP abbiamo aperto una connessione, mantenuta per tutta la comunicazione, e chiusa esplicitamente alla fine. Con UDP non c'è mai stata alcuna connessione: solo pacchetti autosufficienti, spediti e ricevuti uno alla volta. Tenere bene a mente questa differenza ti aiuterà moltissimo quando, in futuro, dovrai scegliere quale protocollo usare per una tua applicazione di rete.

AspettoTCP (L2)UDP (L3)
Socket serverServerSocket(12345) + accept() bloccanteDatagramSocket(12345) + receive() bloccante
Socket clientSocket("localhost",12345) connessioneDatagramSocket() porta dinamica
InvioPrintWriter.println() su streamDatagramPacket + send() con IP:porta
RicezioneBufferedReader.readLine()DatagramPacket + receive() + getData()
Chiusuraclose() chiude connessioneclose() libera porta
Multi-clientServe thread per ogni clientGestito naturalmente senza thread (base)
💡 Tips per l'orale - Domanda killer

"Perché con UDP non serve thread per due client?" Risposta: perché non c'è connessione aperta. Il server UDP è come un ufficio postale: arriva busta, la leggo, rispondo, dimentico tutto. Arriva altra busta, stessa cosa. Con TCP è come telefonata: se parlo con Cliente A, la linea è occupata, Cliente B deve aspettare o devo aprire seconda linea (thread). Questa analogia vi fa prendere 30.

7. 📌 Concetti chiave

  • UDP è senza connessione: ogni datagramma è indipendente e autosufficiente, senza handshake iniziale né garanzie di consegna o ordine. Ogni pacchetto porta IP e porta.
  • UDP verifica integrità con checksum, ma non gestisce perdite o duplicazioni: se serve affidabilità, la implementi tu a livello applicativo.
  • DatagramSocket è il socket UDP: senza porta (client, dinamica) o con porta fissa (server). DatagramPacket è il pacchetto: dati + IP + porta destinazione in invio, IP + porta mittente in ricezione.
  • Modello client-server UDP senza stato: il server ricava indirizzo e porta del client direttamente dal pacchetto ricevuto (getAddress(), getPort()), non da una connessione aperta.
  • Buffer e getLength(): usare sempre getLength() per sapere quanti byte sono arrivati davvero, altrimenti restano byte residui nel buffer da 1024. Meglio new String(data, 0, length) che new String(data) + substring.
  • Caso limite "0": meccanismo didattico di terminazione — in produzione usare comando dedicato tipo "/quit".
  • Rispetto a TCP, UDP è più semplice e veloce, gestisce multi-client base senza thread, ma lascia all'applicazione responsabilità di affidabilità, ordine e gestione errori. Scelta dipende da compromesso velocità vs affidabilità.
🐙 Laboratori Pratici

Vuoi passare all'azione? Accedi alla repository ufficiale GitHub del corso per scaricare il file del laboratorio pratico

🛠️ Esercizi Socket UDP →

Vuoi consultare la sezione dedicata ai laboratori sul sito? Accedi cliccando il pulsante qui sotto.

📊 Sezione dedicata ai Laboratori

Vuoi consultare la sezione dedicata alle guide sul sito? Accedi cliccando il pulsante qui sotto.

🧪 Guida preparazione ambiente di lavoro →

📚 Prossimo passo - Thread e multi-client TCP

Nella prossima lezione torniamo a TCP ma lo rendiamo davvero professionale: server multi-thread che gestisce N client contemporanei con while(true) + new Thread(). Se questa lezione su UDP è chiara, avrete capito perché a volte UDP basta e a volte serve TCP con thread. Esercizio: modificate UDPServer per contare le vocali invece di fare maiuscolo — se ci riuscite senza toccare la parte di comunicazione, avete capito la separazione logica.