W3docs

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 fa send e su cui si fa receive. 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 info

Un 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.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Non c'è stato nessun connect() e nessun accept(). Entrambi gli estremi sono semplici DatagramSocket; 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 DatagramPacket trasportava sia i dati che l'indirizzamento. Il ricevitore ha appreso chi ha inviato la richiesta da request.getAddress() e request.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; leggere getLength() byte è obbligatorio, altrimenti si aggiungono dati spuri dallo spazio inutilizzato del buffer.
  • setSoTimeout(2000) ha protetto il receive(). Poiché UDP non garantisce nulla, una risposta persa altrimenti bloccherebbe per sempre; un timeout trasforma "il pacchetto non è mai arrivato" in una SocketTimeoutException gestibile 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 sempre new String(data, 0, packet.getLength(), …) o Arrays.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 lascia receive() bloccato per sempre. Chiama setSoTimeout(ms) e gestisci la SocketTimeoutException risultante (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 DatagramSocket mantiene una porta del sistema operativo. Usa try-with-resources (implementa AutoCloseable) o chiudilo in un blocco finally in 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 usando packet.getAddress()/packet.getPort() invece di una destinazione hard-coded.

Per una consegna garantita e ordinata, utilizza le classi TCP in Java sockets.

Pratica

Pratica
Un agente di monitoraggio riceve datagrammi UDP in un buffer 'byte[2048]' riutilizzato tramite 'socket.receive(packet)', poi converte l'intero buffer con 'new String(packet.getData(), StandardCharsets.UTF_8)'. I messaggi brevi escono con dati spuri in coda. Qual è la soluzione?
Un agente di monitoraggio riceve datagrammi UDP in un buffer 'byte[2048]' riutilizzato tramite 'socket.receive(packet)', poi converte l'intero buffer con 'new String(packet.getData(), StandardCharsets.UTF_8)'. I messaggi brevi escono con dati spuri in coda. Qual è la soluzione?
Was this page helpful?