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 nincsconnect(). 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ásOSErrorkivételt vált ki ahelyett, hogy örökké blokkolna – ez természetes módja egy elveszett csomag felismerésének.A
withblokk 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.