9.12. UDP-socketit

UDP-liikennettä lähetetään ja vastaanotetaan Pythonissa kahdella datagram-socketin metodilla: sendto() lähettää datagrammin valittuun kohteeseen, ja recvfrom() vastaanottaa datagrammin ja selvittää, mistä se tuli. Jokainen kutsu siirtää yhden itsenäisen viestin; yhteystilaa ei ole.

9.12.1. Datagrammin lähettäminen

Yksinkertaisin UDP-lähetys on yksi rivi Pythonia socket-konstruktorin päällä:

import socket

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

Tuo lähettää b"hello" -viestin porttiin 9000 osoitteessa 192.168.1.20 ja jatkaa eteenpäin. MicroPython valitsee tilapäisen lähdeportin; skriptin ei tarvitse sitoa mitään.

Saman hyötykuorman lähettäminen moneen kohteeseen on vain silmukka – socket on uudelleenkäytettävissä lähetysten välillä, eikä yhteyttä tarvitse muodostaa:

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. Datagrammin vastaanottaminen

Datagrammien vastaanottamiseksi socketin on varattava tunnettu portti, jota lähettäjät käyttävät kohteenaan. Se tehdään bind() -kutsulla:

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)

Osoite "0.0.0.0" tarkoittaa ”jokaista kameran IPv4-rajapintaa” – riippumatta siitä, mikä Wi-Fi- tai Ethernet-rajapinta tuo paketit sisään, portti 9000 kuuluu tälle socketille.

Argumentti 1024 recvfrom() -metodille on enimmäismäärä tavuja, jotka luetaan palautettuun puskuriin. Tätä suuremmat UDP-datagrammit katkaistaan; valitse arvo vastaamaan suurinta datagrammia, jota sovellus odottaa.

recvfrom() palauttaa (data, src): vastaanotetut tavut sekä lähettäjän osoitteen. Lähettäjän osoite on se, johon vastataan, mikä tekee pienen pyyntö/vastaus-protokollan kirjoittamisesta helppoa:

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

Oletusarvoisesti recvfrom() estyy, kunnes datagrammi saapuu. Mallit sen estymättömäksi tekemiseen – aikakatkaisut, estymättömät socketit, asyncio – ovat sivulla Socketit asyncion kanssa.

9.12.3. Pyyntö ja vastaus

Kaksi lyhyttä skriptiä: toinen lähettää pyynnön, toinen vastaanottaa ja vastaa.

Vastaanottaja:

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ähettäjä:

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

Muutama huomionarvoinen seikka lähettäjässä:

  • Ei bind() -kutsua eikä connect() -kutsua. UDP-asiakkaat vain lähettävät.

  • settimeout() asettaa määräajan vastaanottokutsulle. Jos vastausta ei saavu kahdessa sekunnissa, kutsu nostaa OSError -poikkeuksen sen sijaan että estyisi ikuisesti – luonteva tapa havaita kadonnut paketti.

  • with -lohko sulkee socketin automaattisesti.

9.12.4. Datagrammin kokorajoitukset

UDP-datagrammit voivat teoriassa olla jopa noin 64 KB, mutta käytännön raja on paljon pienempi. Jokaisella lähettäjän ja vastaanottajan välisen polun linkillä on Maximum Transmission Unit (MTU) – suurin yksittäinen tavulohko, jonka linkki voi kuljettaa yhdessä kehyksessä. Sekä Ethernet että Wi-Fi rajaavat tämän noin 1500 tavuun, ja lähes jokainen internet-polku palautuu jossakin kohtaa tuohon rajaan.

Kun datagrammi ylittää ylittämänsä linkin MTU:n, verkkokerros pilkkoo sen pienemmiksi fragmenteiksi ja kokoaa ne uudelleen kohteessa. UDP itse ei koskaan näe pilkkomista, mutta fragmenteilla on useita hankalia ominaisuuksia:

  • Jos yksikin fragmentti katoaa, koko datagrammi hylätään vastaanottajalla – fragmenttikohtaista uudelleenlähetystä ei ole. Häviötodennäköisyys kasvaa fragmenttimäärän myötä.

  • Jotkin verkot ja palomuurit hylkäävät pilkotut paketit kokonaan ja pitävät niitä epäilyttävinä.

  • Uudelleenkokoaminen kuluttaa muistia vastaanottajalla, mistä mikrokontrollerissa on pulaa.

Käytännön sääntö kameralla: pidä UDP-viestit selvästi alle 1500 tavussa. Noin 1400 tavua jättää tilaa IP- ja UDP-otsikoille, polun mahdollisesti lisäämälle tunnelointiylätyölle sekä pienille MTU:n vaihteluille Ethernet-, Wi-Fi- ja VPN-linkkien välillä. Sovellusten, joiden on lähetettävä tätä enemmän, tulisi joko paloitella data sovelluskerroksessa tai vaihtaa TCP:hen, joka hoitaa pilkkomisen ja uudelleenkokoamisen automaattisesti.

9.12.5. Yleisiä sudenkuoppia

  • Sen unohtaminen, että UDP voi kadottaa paketteja. Koodi, joka toimii täydellisesti hiljaisessa paikallisverkossa, epäonnistuu joskus hienovaraisilla tavoilla vilkkaammassa tai laajemmassa verkossa. Suunnittele aina sen varalta, että viesti ei saapunut perille.

  • Vastaanottajaa ei ole sidottu ennen kuin lähettäjä lähettää. Porttiin, jota kukaan ei kuuntele, lähetetty datagrammi hylätään hiljaisesti. Käynnistä vastaanottaja ensin.

  • MTU:ta suuremman datagrammin lähettäminen polulla. Katso edellinen osio – pidä viestit alle ~1400 tavussa.

Yllä olevat mallit kattavat lähes kaikki UDP-käytöt, joihin kameralla tarvitaan. Seuraava sivu tekee vastaavan TCP:lle.