Java Datagram Sockets (UDP)
Invia e ricevi datagrammi UDP in Java con DatagramSocket e DatagramPacket.
Socket e ServerSocket utilizzano TCP — uno stream affidabile, ordinato e basato su connessione. DatagramSocket utilizza UDP, l'altro protocollo di trasporto: senza connessione e basato su pacchetti. Non si "connette"; si lanciano datagrammi indipendenti verso un indirizzo sperando che arrivino. Non c'è handshake, nessun ordinamento e nessuna garanzia di consegna — in cambio, nessun overhead di connessione e latenza molto bassa.
Questa pagina illustra quando UDP è la scelta giusta, le due classi principali (DatagramSocket e DatagramPacket), un esempio completo di richiesta/risposta che puoi eseguire, e le insidie che colpiscono i programmatori UDP alle prime armi. Se sei nuovo al networking in Java, inizia con l'introduzione al networking.
Quando UDP è lo strumento giusto
UDP scambia affidabilità per velocità e semplicità. È adatto quando:
- La perdita occasionale è accettabile — audio/video in diretta, stato di gioco, telemetria. Un frame perso è meglio di uno in ritardo.
- I messaggi sono piccoli e autonomi — query DNS, sincronizzazione NTP.
- Si fa broadcast/multicast a molti ricevitori — TCP non lo consente.
Se hai bisogno di ogni byte, in ordine (trasferimento file, pagine web, database), usa TCP. Molte applicazioni implementano la propria leggera affidabilità sopra UDP piuttosto che pagare il costo completo del TCP.
Le due classi
UDP in Java utilizza una coppia:
DatagramSocket— l'endpoint da cui si fasende su cui si fareceive. Non esiste un "server socket" separato; la stessa classe gestisce entrambi, perché non c'è una connessione da accettare.DatagramPacket— un singolo datagramma: un buffer di byte più, per l'invio, l'indirizzo e la porta di destinazione; per la ricezione, viene riempito con l'indirizzo e la porta del mittente e la lunghezza dei dati.
DatagramSocket socket = new DatagramSocket(9000); // bind to receive on 9000
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
DatagramPacket out = new DatagramPacket(data, data.length, address, port);
socket.send(out); // fire and forget
byte[] buf = new byte[1024];
DatagramPacket in = new DatagramPacket(buf, buf.length);
socket.receive(in); // blocks; fills buf + sender infoUn dettaglio critico: dopo receive(), leggi esattamente in.getLength() byte dal buffer — il buffer è di dimensione fissa ma il datagramma potrebbe essere più corto. Imposta setSoTimeout(ms) in modo che un pacchetto perso non blocchi receive() per sempre.
Un esempio pratico: richiesta/risposta UDP su loopback
Questo programma esegue un ricevitore su un thread in background che attende un datagramma e risponde al mittente, mentre il thread principale invia un datagramma e legge il messaggio di conferma — un round trip UDP completo sull'interfaccia di loopback.
Cosa ricavare dall'esecuzione:
- Non c'è stato nessun
connect()e nessunaccept(). Entrambi gli estremi sono sempliciDatagramSocket; il mittente ha inviato un pacchetto a un indirizzo e una porta, e il ricevitore lo ha catturato. UDP non ha connessione, quindi la stessa classe gestisce entrambi i ruoli — l'asimmetria dei socket client/server TCP scompare. - Un
DatagramPackettrasportava sia i dati che l'indirizzamento. Il ricevitore ha appreso chi ha inviato la richiesta darequest.getAddress()erequest.getPort()e ha risposto direttamente a quell'endpoint — non esiste un canale persistente, quindi ogni risposta deve essere indirizzata esplicitamente. - Il corpo è stato decodificato con
new String(data, 0, getLength(), …), non l'intero buffer da 1024 byte. Un datagramma riempie solo una parte di un buffer fisso; leggeregetLength()byte è obbligatorio, altrimenti si aggiungono dati spuri dallo spazio inutilizzato del buffer. setSoTimeout(2000)ha protetto ilreceive(). Poiché UDP non garantisce nulla, una risposta persa altrimenti bloccherebbe per sempre; un timeout trasforma "il pacchetto non è mai arrivato" in unaSocketTimeoutExceptiongestibile che puoi riprovare o segnalare.- Lo scambio ha funzionato qui perché il loopback non ha perdite ed è ordinato, ma l'API non ha fatto tale promessa. Su una rete reale questo datagramma potrebbe sparire, arrivare due volte, o arrivare dopo uno successivo — ed è esattamente per questo che le applicazioni che richiedono affidabilità scelgono TCP o costruiscono il proprio schema di acknowledgement sopra UDP.
Insidie comuni
Alcune trappole colpiscono quasi tutti la prima volta che usano DatagramSocket:
- Leggere l'intero buffer invece di
getLength()byte. Il buffer è di dimensione fissa; il datagramma di solito non lo è. Usa semprenew String(data, 0, packet.getLength(), …)oArrays.copyOf(data, packet.getLength()). Riutilizzare un buffer peggiora la situazione — i byte residui di un datagramma precedente più lungo appaiono come dati spuri in coda. - Nessun timeout su
receive(). Poiché UDP non garantisce mai la consegna, un pacchetto perso lasciareceive()bloccato per sempre. ChiamasetSoTimeout(ms)e gestisci laSocketTimeoutExceptionrisultante (riprova, registra nel log, oppure rinuncia). - Inviare più di quanto contenga un singolo datagramma. UDP non ha streaming; un
send()corrisponde a un pacchetto. I payload di grandi dimensioni vengono frammentati da IP e un singolo frammento perso scarta l'intero datagramma. Mantieni i payload piccoli — circa 512 byte è un limite sicuro che evita la frammentazione sulla maggior parte delle reti. - Dimenticare di chiudere il socket. Un
DatagramSocketmantiene una porta del sistema operativo. Usa try-with-resources (implementaAutoCloseable) o chiudilo in un bloccofinallyin modo che la porta venga rilasciata. - Assumere che la risposta provenga da dove hai inviato.
receive()sovrascrive l'indirizzo e la porta del pacchetto con il mittente effettivo. Rispondi sempre usandopacket.getAddress()/packet.getPort()invece di una destinazione hard-coded.
Per una consegna garantita e ordinata, utilizza le classi TCP in Java sockets.