9.12. UDP sokety#

UDP provoz se v Pythonu odesílá a přijímá dvěma metodami na datagramovém soketu: sendto() pro vyslání datagramu na zvolený cíl a recvfrom() pro přijetí datagramu a zjištění, odkud přišel. Každé volání přenáší jednu samostatnou zprávu; neexistuje žádný stav spojení.

9.12.1. Odeslání datagramu#

Nejjednodušší UDP odeslání je jeden řádek Pythonu nad konstruktorem soketu:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()

Tím se odešle b"hello" na port 9000 na 192.168.1.20 a dál se nic neděje. MicroPython zvolí dočasný zdrojový port; skript nemusí nic navazovat.

Odeslání stejných dat na mnoho cílů je jen smyčka – soket je mezi odesláními opakovaně použitelný a není třeba navazovat žádné spojení:

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. Přijetí datagramu#

Aby mohl soket přijímat datagramy, musí obsadit známý port, který odesílatelé použijí jako svůj cíl. To je volání 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)

Adresa "0.0.0.0" znamená „každé IPv4 rozhraní na kameře“ – ať už pakety přivádí jakékoli Wi-Fi nebo Ethernetové rozhraní, port 9000 patří tomuto soketu.

Argument 1024 u recvfrom() je maximální počet bajtů, které se mají přečíst do vráceného bufferu. UDP datagramy větší než tato hodnota budou oříznuty; zvolte hodnotu odpovídající největšímu datagramu, který aplikace očekává.

recvfrom() vrací (data, src): přijaté bajty a adresu odesílatele. Adresa odesílatele je to, na co odpovědět, což usnadňuje napsání malého protokolu typu požadavek/odpověď:

while True:
    request, src = s.recvfrom(1024)
    if request == b"ping":
        s.sendto(b"pong", src)

Ve výchozím nastavení recvfrom() blokuje, dokud nedorazí datagram. Vzory, jak zařídit, aby neblokoval – časové limity, neblokující sokety, asyncio – najdete na stránce Sockety s asyncio.

9.12.3. Požadavek a odpověď#

Dva krátké skripty: jeden odešle požadavek, druhý přijme a odpoví.

Příjemce:

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)

Odesílatel:

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?")

Pár věcí, které stojí za zmínku u odesílatele:

  • Žádné bind() ani connect(). UDP klienti prostě odesílají.

  • settimeout() nastaví na volání příjmu časový limit. Pokud do dvou sekund nedorazí žádná odpověď, volání vyvolá OSError místo nekonečného blokování – přirozený způsob, jak zjistit ztracený paket.

  • Blok with soket automaticky zavře.

9.12.4. Omezení velikosti datagramu#

UDP datagramy mohou být teoreticky velké až přibližně 64 KB, ale praktický limit je mnohem menší. Každá linka na cestě mezi odesílatelem a příjemcem má svou Maximum Transmission Unit (MTU) – největší jednotlivý blok bajtů, který daná linka dokáže přenést v jednom rámci. Ethernet i Wi-Fi tuto hodnotu omezují na zhruba 1500 bajtů a téměř každá internetová cesta se k tomuto limitu někde vrací.

Když datagram překročí MTU linky, kterou musí překročit, síťová vrstva ho rozdělí na menší fragmenty a v cíli je opět složí. Samotné UDP rozdělení nikdy nevidí, ale fragmenty mají několik nepříjemných vlastností:

  • Pokud se ztratí byť jen jeden fragment, celý datagram se na příjemci zahodí – neexistuje žádné opětovné vysílání jednotlivých fragmentů. Pravděpodobnost ztráty roste s počtem fragmentů.

  • Některé sítě a firewally zahazují fragmentované pakety úplně a považují je za podezřelé.

  • Opětovné složení stojí paměť na straně příjemce, které je na mikrokontroléru nedostatek.

Praktické pravidlo na kameře: udržujte UDP zprávy výrazně pod 1500 bajty. Zhruba 1400 bajtů ponechává prostor pro IP a UDP hlavičky, případnou režii tunelování, kterou cesta přidá, a drobné odchylky MTU mezi Ethernetem, Wi-Fi a VPN linkami. Aplikace, které potřebují odeslat více, by měly buď rozdělit data na úrovni aplikace, nebo přejít na TCP, které rozdělování a opětovné skládání zvládá automaticky.

9.12.5. Časté chyby#

  • Zapomenutí, že UDP může ztrácet pakety. Kód, který funguje dokonale na klidné lokální síti, někdy selhává nenápadnými způsoby na rušnější nebo rozsáhlejší síti. Vždy počítejte s možností, že zpráva nedorazila.

  • Příjemce není navázán dříve, než odesílatel odešle. Datagram odeslaný na port, na kterém nikdo nenaslouchá, je tiše zahozen. Spusťte nejprve příjemce.

  • Odeslání datagramu většího, než je MTU cesty. Viz předchozí část – udržujte zprávy pod ~1400 bajty.

Výše uvedené vzory pokrývají téměř každé použití UDP, po kterém kamera sáhne. Následující stránka popisuje totéž pro TCP.