W3docs

Java Datagram Sockets (UDP)

Envoyez et recevez des datagrammes UDP en Java avec DatagramSocket et DatagramPacket.

Socket et ServerSocket utilisent TCP — un flux fiable, ordonné et basé sur les connexions. DatagramSocket utilise UDP, l'autre protocole de transport : sans connexion et basé sur des paquets. Vous ne « vous connectez » pas ; vous envoyez des datagrammes indépendants à une adresse en espérant qu'ils arrivent. Il n'y a ni poignée de main, ni ordonnancement, ni garantie de livraison — en échange d'une absence de surcharge de connexion et d'une latence très faible.

Cette page explique quand UDP est le bon choix, les deux classes principales (DatagramSocket et DatagramPacket), un exemple complet de requête/réponse que vous pouvez exécuter, et les pièges qui guettent les programmeurs UDP débutants. Si vous êtes novice en réseau Java, commencez par l'introduction au réseau.

Quand UDP est le bon outil

UDP échange la fiabilité contre la vitesse et la simplicité. Il convient lorsque :

  • Les pertes occasionnelles sont acceptables — audio/vidéo en direct, état de jeu, télémétrie. Une image perdue vaut mieux qu'une image retardée.
  • Les messages sont petits et autonomes — requêtes DNS, synchronisation horaire NTP.
  • Vous diffusez/multidiffusez vers de nombreux récepteurs — TCP ne le peut pas.

Si vous avez besoin de chaque octet, dans l'ordre (transfert de fichiers, pages web, bases de données), utilisez TCP. De nombreuses applications superposent leur propre mécanisme de fiabilité léger au-dessus d'UDP plutôt que de supporter le coût total de TCP.

Les deux classes

UDP en Java utilise une paire de classes :

  • DatagramSocket — le point de terminaison depuis lequel vous send et sur lequel vous receive. Il n'existe pas de « socket serveur » séparé ; la même classe fait les deux, car il n'y a pas de connexion à accepter.
  • DatagramPacket — un datagramme : un tampon d'octets plus, pour l'envoi, l'adresse de destination et le port ; pour la réception, il est rempli avec l'adresse et le port de l'expéditeur ainsi que la longueur des données.
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 détail crucial : après receive(), lisez exactement in.getLength() octets depuis le tampon — le tampon a une taille fixe, mais le datagramme peut être plus court. Définissez setSoTimeout(ms) pour qu'un paquet perdu ne bloque pas receive() indéfiniment.

Un exemple pratique : une requête/réponse UDP sur la boucle locale

Ce programme exécute un récepteur sur un thread en arrière-plan qui attend un datagramme et répond à son expéditeur, tandis que le thread principal envoie un datagramme et lit l'accusé de réception — un aller-retour UDP complet sur l'interface de boucle locale.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Il n'y a eu ni connect() ni accept(). Les deux extrémités sont simplement des DatagramSocket ; l'expéditeur a envoyé un paquet à une adresse et un port, et le récepteur l'a récupéré. UDP n'a pas de connexion, donc la même classe gère les deux rôles — l'asymétrie des sockets client/serveur de TCP disparaît.
  • Un DatagramPacket transportait à la fois les données et l'adressage. Le récepteur a appris qui avait envoyé la requête via request.getAddress() et request.getPort() et a répondu directement à ce point de terminaison — il n'y a pas de canal persistant, donc chaque réponse doit être adressée explicitement.
  • Le corps a été décodé avec new String(data, 0, getLength(), …), et non la totalité du tampon de 1024 octets. Un datagramme ne remplit qu'une partie d'un tampon fixe ; la lecture de getLength() octets est obligatoire, sinon vous ajoutez des données résiduelles de l'espace de tampon inutilisé.
  • setSoTimeout(2000) protégeait le receive(). Comme UDP ne garantit rien, une réponse perdue bloquerait autrement indéfiniment ; un délai d'expiration transforme « paquet jamais arrivé » en une SocketTimeoutException attrapable que vous pouvez réessayer ou signaler.
  • L'échange a fonctionné ici parce que la boucle locale est sans perte et ordonnée, mais l'API n'a fait aucune telle promesse. Sur un vrai réseau, ce datagramme pourrait disparaître, arriver deux fois, ou arriver après un datagramme ultérieur — c'est précisément pourquoi les applications sensibles à la fiabilité choisissent TCP ou construisent leur propre mécanisme d'accusé de réception au-dessus d'UDP.

Pièges courants

Quelques pièges attrapent presque tout le monde la première fois qu'ils utilisent DatagramSocket :

  • Lire tout le tampon au lieu de getLength() octets. Le tampon a une taille fixe ; le datagramme généralement non. Découpez toujours avec new String(data, 0, packet.getLength(), …) ou Arrays.copyOf(data, packet.getLength()). La réutilisation d'un tampon aggrave les choses — les octets résiduels d'un datagramme précédent plus long apparaissent comme des données résiduelles.
  • Pas de délai d'expiration sur receive(). Comme UDP ne promet jamais la livraison, un paquet perdu laisse receive() bloqué indéfiniment. Appelez setSoTimeout(ms) et gérez la SocketTimeoutException résultante (réessayez, journalisez ou abandonnez).
  • Envoyer plus que ce qui tient dans un datagramme. UDP n'a pas de streaming ; un send() est un paquet. Les charges utiles importantes sont fragmentées par IP et un seul fragment perdu fait tomber le datagramme entier. Gardez les charges utiles petites — environ 512 octets est un plafond sûr qui évite la fragmentation sur la plupart des réseaux.
  • Oublier de fermer le socket. Un DatagramSocket occupe un port OS. Utilisez try-with-resources (il implémente AutoCloseable) ou fermez-le dans un bloc finally pour libérer le port.
  • Supposer que la réponse vient d'où vous l'avez envoyée. receive() écrase l'adresse et le port du paquet avec l'expéditeur réel. Répondez toujours en utilisant packet.getAddress()/packet.getPort() plutôt qu'une destination codée en dur.

Pour une livraison garantie et ordonnée, utilisez les classes TCP dans Java sockets.

Pratique

Pratique
Un agent de surveillance reçoit des datagrammes UDP dans un tampon 'byte[2048]' réutilisé via 'socket.receive(packet)', puis convertit tout le tampon avec 'new String(packet.getData(), StandardCharsets.UTF_8)'. Les messages courts sortent avec des données résiduelles. Quel est le correctif ?
Un agent de surveillance reçoit des datagrammes UDP dans un tampon 'byte[2048]' réutilisé via 'socket.receive(packet)', puis convertit tout le tampon avec 'new String(packet.getData(), StandardCharsets.UTF_8)'. Les messages courts sortent avec des données résiduelles. Quel est le correctif ?
Was this page helpful?