9.12. Sockets UDP¶
Le trafic UDP en Python est envoyé et reçu à l’aide de deux méthodes sur un socket de datagramme : sendto() pour expédier un datagramme vers une destination choisie, et recvfrom() pour recevoir un datagramme et savoir d’où il provient. Chaque appel transporte un message autonome ; il n’y a aucun état de connexion.
9.12.1. Envoyer un datagramme¶
L’envoi UDP le plus simple tient en une ligne de Python par-dessus un constructeur de socket : :
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()
Cela envoie b"hello" au port 9000 sur 192.168.1.20 puis passe à autre chose. MicroPython choisit un port source éphémère ; le script n’a rien à lier.
Envoyer la même charge utile vers plusieurs destinations n’est qu’une boucle – le socket est réutilisable entre les envois, et il n’y a aucune connexion à établir : :
targets = [
("192.168.1.20", 9000),
("192.168.1.21", 9000),
("192.168.1.22", 9000),
]
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
for addr in targets:
s.sendto(b"hello", addr)
9.12.2. Recevoir un datagramme¶
Pour recevoir des datagrammes, le socket doit revendiquer un port connu que les expéditeurs utiliseront comme destination. C’est le rôle de l’appel bind() : :
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
data, src = s.recvfrom(1024)
print("from", src, "got", data)
L’adresse "0.0.0.0" signifie « toutes les interfaces IPv4 de la caméra » – quelle que soit l’interface Wi-Fi ou Ethernet qui fait entrer les paquets, le port 9000 appartient à ce socket.
L’argument 1024 de recvfrom() est le nombre maximal d’octets à lire dans le tampon retourné. Les datagrammes UDP dépassant cette taille seront tronqués ; choisissez la valeur en fonction du plus grand datagramme attendu par l’application.
recvfrom() retourne (data, src) : les octets reçus et l’adresse de l’expéditeur. L’adresse de l’expéditeur est ce à quoi répondre, ce qui facilite l’écriture d’un petit protocole requête/réponse : :
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
Par défaut, recvfrom() bloque jusqu’à l’arrivée d’un datagramme. Les techniques pour l’empêcher de bloquer – délais d’expiration, sockets non bloquants, asyncio – figurent sur Sockets avec asyncio.
9.12.3. Une requête et une réponse¶
Deux scripts courts : l’un envoie une requête, l’autre reçoit et répond.
Le récepteur : :
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
req, src = s.recvfrom(64)
print("got", req, "from", src)
s.sendto(b"ack: " + req, src)
L’expéditeur : :
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(2.0) # 2 s reply window
s.sendto(b"ping", ("192.168.1.20", 9000))
try:
reply, _ = s.recvfrom(64)
print("reply:", reply)
except OSError:
print("no reply in 2 s -- packet lost?")
Quelques points à noter dans l’expéditeur :
Pas de
bind()ni deconnect(). Les clients UDP se contentent d’envoyer.settimeout()impose un délai à l’appel de réception. Si aucune réponse n’arrive en deux secondes, l’appel lèveOSErrorau lieu de bloquer indéfiniment – une façon naturelle de détecter un paquet perdu.Le bloc
withferme le socket automatiquement.
9.12.4. Limites de taille des datagrammes¶
Les datagrammes UDP peuvent en théorie atteindre environ 64 Ko, mais la limite pratique est bien plus faible. Chaque lien sur le chemin entre l’expéditeur et le récepteur possède une unité de transmission maximale (MTU) – le plus grand bloc d’octets que ce lien peut transporter en une seule trame. Ethernet et le Wi-Fi plafonnent tous deux cette valeur autour de 1500 octets, et presque tous les chemins internet ramènent à cette limite quelque part.
Lorsqu’un datagramme dépasse la MTU d’un lien qu’il doit traverser, la couche réseau le découpe en fragments plus petits et les réassemble à destination. UDP lui-même ne voit jamais le découpage, mais les fragments présentent plusieurs propriétés gênantes :
Si un seul fragment est perdu, tout le datagramme est rejeté par le récepteur – il n’y a pas de retransmission par fragment. La probabilité de perte croît avec le nombre de fragments.
Certains réseaux et pare-feu rejettent entièrement les paquets fragmentés, les considérant comme suspects.
Le réassemblage consomme de la mémoire au niveau du récepteur, ce qui est rare sur un microcontrôleur.
La règle pratique sur la caméra : maintenir les messages UDP bien en deçà de 1500 octets. Environ 1400 octets laissent de la place pour les en-têtes IP et UDP, toute surcharge de tunnellisation ajoutée par le chemin, et les petites variations de MTU entre les liens Ethernet, Wi-Fi et VPN. Les applications devant envoyer davantage devraient soit découper les données au niveau applicatif, soit passer à TCP, qui gère automatiquement le découpage et le réassemblage.
9.12.5. Pièges courants¶
Oublier qu’UDP peut perdre des paquets. Du code qui fonctionne parfaitement sur un réseau local tranquille échoue parfois de manière subtile sur un réseau plus chargé ou plus étendu. Concevez toujours en tenant compte de la possibilité que le message ne soit pas arrivé.
Récepteur non lié avant que l’expéditeur n’envoie. Un datagramme envoyé vers un port sur lequel personne n’écoute est silencieusement rejeté. Démarrez d’abord le récepteur.
Envoyer un datagramme plus grand que la MTU du chemin. Voir la section précédente – maintenez les messages sous ~1400 octets.
Les techniques ci-dessus couvrent presque tous les usages d’UDP auxquels la caméra recourt. La page suivante fait l’équivalent pour TCP.