9.12. UDP-Sockets¶
UDP-Verkehr wird in Python mit zwei Methoden eines Datagramm-Sockets gesendet und empfangen: sendto(), um ein Datagramm an ein gewähltes Ziel abzufeuern, und recvfrom(), um ein Datagramm zu empfangen und herauszufinden, woher es kam. Jeder Aufruf bewegt eine in sich abgeschlossene Nachricht; es gibt keinen Verbindungszustand.
9.12.1. Ein Datagramm senden¶
Der einfachste UDP-Sendevorgang ist eine Zeile Python auf einem Socket-Konstruktor:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()
Das sendet b"hello" an Port 9000 auf 192.168.1.20 und macht weiter. MicroPython wählt einen flüchtigen Quellport; das Skript muss nichts binden.
Dieselben Nutzdaten an viele Ziele zu senden, ist einfach eine Schleife – der Socket ist zwischen Sendevorgängen wiederverwendbar, und es muss keine Verbindung aufgebaut werden:
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. Ein Datagramm empfangen¶
Um Datagramme zu empfangen, muss der Socket einen bekannten Port belegen, den die Sender als ihr Ziel verwenden. Das ist der bind()-Aufruf:
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)
Die Adresse "0.0.0.0" bedeutet „jede IPv4-Schnittstelle auf der Kamera“ – egal welche WLAN- oder Ethernet-Schnittstelle Pakete hereinbringt, Port 9000 gehört zu diesem Socket.
Das Argument 1024 für recvfrom() ist die maximale Anzahl von Bytes, die in den zurückgegebenen Puffer gelesen werden. UDP-Datagramme über dieser Größe werden abgeschnitten; wähle den Wert passend zum größten Datagramm, das die Anwendung erwartet.
recvfrom() gibt (data, src) zurück: die empfangenen Bytes und die Adresse des Senders. Die Adresse des Senders ist diejenige, an die zu antworten ist, was es einfach macht, ein kleines Anfrage/Antwort-Protokoll zu schreiben:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
Standardmäßig blockiert recvfrom(), bis ein Datagramm eintrifft. Die Muster, um dies nicht-blockierend zu gestalten – Timeouts, nicht-blockierende Sockets, asyncio – finden sich auf Sockets mit asyncio.
9.12.3. Eine Anfrage und eine Antwort¶
Zwei kurze Skripte: eines sendet eine Anfrage, eines empfängt und antwortet.
Der Empfänger:
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)
Der Sender:
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?")
Ein paar erwähnenswerte Dinge beim Sender:
settimeout()setzt eine Frist für den Empfangsaufruf. Trifft innerhalb von zwei Sekunden keine Antwort ein, löst der AufrufOSErroraus, anstatt für immer zu blockieren – eine natürliche Möglichkeit, ein verlorenes Paket zu erkennen.Der
with-Block schließt den Socket automatisch.
9.12.4. Größenbeschränkungen für Datagramme¶
UDP-Datagramme können theoretisch bis zu etwa 64 KB groß sein, aber die praktische Grenze ist deutlich kleiner. Jede Verbindung auf dem Weg zwischen Sender und Empfänger hat eine Maximum Transmission Unit (MTU) – den größten einzelnen Byteblock, den diese Verbindung in einem Einzelbild übertragen kann. Sowohl Ethernet als auch WLAN begrenzen dies auf etwa 1500 Bytes, und nahezu jeder Internetpfad führt irgendwo auf diese Grenze zurück.
Wenn ein Datagramm die MTU einer zu überquerenden Verbindung überschreitet, teilt die Netzwerkschicht es in kleinere Fragmente auf und setzt sie am Ziel wieder zusammen. UDP selbst sieht die Aufteilung nie, aber Fragmente haben mehrere unbequeme Eigenschaften:
Geht auch nur ein einziges Fragment verloren, wird das gesamte Datagramm beim Empfänger verworfen – es gibt keine Neuübertragung pro Fragment. Die Verlustwahrscheinlichkeit wächst mit der Anzahl der Fragmente.
Einige Netzwerke und Firewalls verwerfen fragmentierte Pakete vollständig und behandeln sie als verdächtig.
Das Wiederzusammensetzen kostet Speicher beim Empfänger, der auf einem Mikrocontroller knapp ist.
Die praktische Regel auf der Kamera: Halte UDP-Nachrichten deutlich unter 1500 Bytes. Etwa 1400 Bytes lassen Raum für die IP- und UDP-Header, etwaigen Tunneling-Overhead, den der Pfad hinzufügt, und kleine Schwankungen der MTU zwischen Ethernet-, WLAN- und VPN-Verbindungen. Anwendungen, die mehr senden müssen, sollten die Daten entweder auf Anwendungsebene in Stücke aufteilen oder auf TCP umsteigen, das die Aufteilung und das Wiederzusammensetzen automatisch übernimmt.
9.12.5. Häufige Fallstricke¶
Vergessen, dass UDP Pakete verlieren kann. Code, der in einem ruhigen lokalen Netzwerk einwandfrei funktioniert, schlägt in einem stärker ausgelasteten oder größeren Netzwerk manchmal auf subtile Weise fehl. Plane immer für die Möglichkeit, dass die Nachricht nicht angekommen ist.
Empfänger nicht gebunden, bevor der Sender sendet. Ein an einen Port gesendetes Datagramm, an dem niemand lauscht, wird stillschweigend verworfen. Starte zuerst den Empfänger.
Ein Datagramm senden, das größer als die MTU des Pfades ist. Siehe den vorherigen Abschnitt – halte Nachrichten unter ~1400 Bytes.
Die obigen Muster decken nahezu jede UDP-Verwendung ab, auf die die Kamera zurückgreift. Die nächste Seite tut das Äquivalente für TCP.