9.12. UDP socketek

Az UDP-forgalmat a Pythonban egy datagram-socket két metódusával küldjük és fogadjuk: a sendto() egy kiválasztott célállomásra küld el egy datagramot, a recvfrom() pedig fogad egy datagramot, és kideríti, honnan érkezett. Minden hívás egy önálló üzenetet mozgat; nincs kapcsolati állapot.

9.12.1. Egy datagram küldése

A legegyszerűbb UDP-küldés egyetlen sor Python egy socket-konstruktor tetején:

import socket

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

Ez elküldi a b"hello" üzenetet a 192.168.1.20 cím 9000 portjára, majd elsétál. A MicroPython egy efemer forrásportot választ; a szkriptnek semmit sem kell lekötnie.

Ugyanazon hasznos teher elküldése sok célállomásra csupán egy ciklus – a socket újrahasználható a küldések között, és nincs felépítendő kapcsolat:

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. Egy datagram fogadása

A datagramok fogadásához a socketnek le kell foglalnia egy ismert portot, amelyet a küldők célállomásként használnak majd. Ez a bind() hívás:

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)

A "0.0.0.0" cím azt jelenti, hogy „a kamera minden IPv4 interfésze” – bármelyik Wi-Fi vagy Ethernet interfész hozza is be a csomagokat, a 9000 port ehhez a sockethez tartozik.

A recvfrom() 1024 argumentuma a visszaadott pufferbe beolvasandó bájtok legnagyobb száma. Az ennél nagyobb UDP-datagramok csonkolódnak; az értéket úgy válaszd meg, hogy illeszkedjen a legnagyobb datagramhoz, amelyre az alkalmazás számít.

A recvfrom() a (data, src) párost adja vissza: a fogadott bájtokat és a küldő címét. A küldő címe az, amelyre válaszolni kell, ami megkönnyíti egy kis kérés/válasz protokoll megírását:

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

Alapértelmezés szerint a recvfrom() blokkol, amíg meg nem érkezik egy datagram. Azok a minták, amelyekkel elkerülhető a blokkolás – időtúllépések, nem blokkoló socketek, asyncio – az Socketek asyncióval oldalon találhatók.

9.12.3. Egy kérés és egy válasz

Két rövid szkript: az egyik kérést küld, a másik fogad és válaszol.

A fogadó:

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)

A küldő:

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

Néhány említésre méltó dolog a küldőben:

  • Nincs bind() és nincs connect(). Az UDP-kliensek egyszerűen csak küldenek.

  • A settimeout() határidőt szab a fogadó hívásra. Ha két másodpercen belül nem érkezik válasz, a hívás OSError kivételt vált ki ahelyett, hogy örökké blokkolna – ez természetes módja egy elveszett csomag felismerésének.

  • A with blokk automatikusan lezárja a socketet.

9.12.4. A datagram méretkorlátai

Az UDP-datagramok elméletben akár körülbelül 64 KB méretűek is lehetnek, de a gyakorlati korlát ennél jóval kisebb. A küldő és a fogadó közötti útvonal minden szakaszának van egy maximális átviteli egysége (MTU) – a legnagyobb egybefüggő bájtblokk, amelyet az adott szakasz egyetlen képkockában továbbítani tud. Az Ethernet és a Wi-Fi egyaránt körülbelül 1500 bájtnál korlátozza ezt, és szinte minden internetes útvonal valahol ehhez a korláthoz vezet vissza.

Amikor egy datagram meghaladja egy általa átszelendő szakasz MTU-ját, a hálózati réteg kisebb töredékekre osztja, majd a célállomáson újra összeállítja. Maga az UDP soha nem látja a felosztást, de a töredékeknek több kényelmetlen tulajdonságuk is van:

  • Ha bármelyik töredék elveszik, az egész datagram eldobódik a fogadónál – nincs töredékenkénti újraküldés. Az elvesztés valószínűsége a töredékek számával együtt nő.

  • Egyes hálózatok és tűzfalak teljesen eldobják a töredezett csomagokat, gyanúsnak tartva azokat.

  • Az újraösszeállítás memóriába kerül a fogadónál, amiből egy mikrovezérlőn kevés áll rendelkezésre.

A gyakorlati szabály a kamerán: tartsd az UDP-üzeneteket jóval 1500 bájt alatt. Körülbelül 1400 bájt helyet hagy az IP- és UDP-fejléceknek, az útvonal által hozzáadott alagútkezelési többletnek, valamint az Ethernet, Wi-Fi és VPN szakaszok közötti kisebb MTU-eltéréseknek. Az ennél többet küldeni kívánó alkalmazásoknak vagy az alkalmazási rétegben kell darabolniuk az adatot, vagy át kell térniük TCP-re, amely automatikusan kezeli a felosztást és az újraösszeállítást.

9.12.5. Gyakori buktatók

  • Annak figyelmen kívül hagyása, hogy az UDP csomagokat veszíthet. Az a kód, amely egy csendes helyi hálózaton tökéletesen működik, néha kifinomult módokon hibázik egy forgalmasabb vagy szélesebb hálózaton. Mindig úgy tervezz, hogy az üzenet esetleg meg sem érkezik.

  • A fogadó nincs lekötve, mielőtt a küldő küld. Egy olyan portra küldött datagram, amelyet senki nem figyel, csendben eldobódik. Először a fogadót indítsd el.

  • Az útvonal MTU-jánál nagyobb datagram küldése. Lásd az előző szakaszt – tartsd az üzeneteket ~1400 bájt alatt.

A fenti minták szinte minden olyan UDP-felhasználást lefednek, amelyhez a kamera nyúl. A következő oldal ugyanezt teszi a TCP esetében.