9.12. UDP-socketar¶
UDP-trafik i Python skickas och tas emot med två metoder på en datagram-socket: sendto() för att skicka iväg ett datagram till en vald destination, och recvfrom() för att ta emot ett datagram och ta reda på var det kom ifrån. Varje anrop flyttar ett självständigt meddelande; det finns inget anslutningstillstånd.
9.12.1. Skicka ett datagram¶
Den enklaste UDP-sändningen är en rad Python ovanpå en socket-konstruktor:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()
Det skickar b"hello" till port 9000 på 192.168.1.20 och går vidare. MicroPython väljer en tillfällig källport; skriptet behöver inte binda någonting.
Att skicka samma nyttolast till många destinationer är bara en slinga – socketen är återanvändbar mellan sändningar, och det finns ingen anslutning att upprätta:
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. Ta emot ett datagram¶
För att ta emot datagram måste socketen göra anspråk på en känd port som avsändarna kommer att använda som sin destination. Det är anropet 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)
Adressen "0.0.0.0" betyder ”varje IPv4-gränssnitt på kameran” – oavsett vilket Wi-Fi- eller Ethernet-gränssnitt som tar in paket tillhör port 9000 denna socket.
Argumentet 1024 till recvfrom() är det maximala antalet byte som ska läsas in i den returnerade bufferten. UDP-datagram som är större än detta kommer att trunkeras; välj värdet så att det matchar det största datagram som programmet förväntar sig.
recvfrom() returnerar (data, src): de byte som tagits emot, och avsändarens adress. Avsändarens adress är vad man ska svara till, vilket gör det enkelt att skriva ett litet förfrågan/svar-protokoll:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
Som standard blockerar recvfrom() tills ett datagram anländer. Mönstren för att få det att inte blockera – timeouter, icke-blockerande socketar, asyncio – finns på Sockets med asyncio.
9.12.3. En förfrågan och ett svar¶
Två korta skript: ett skickar en förfrågan, ett tar emot och svarar.
Mottagaren:
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)
Avsändaren:
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ågra saker värda att notera i avsändaren:
Ingen
bind()och ingenconnect(). UDP-klienter bara skickar.settimeout()sätter en tidsgräns på mottagningsanropet. Om inget svar kommer inom två sekunder utlöser anropetOSErrori stället för att blockera för evigt – ett naturligt sätt att upptäcka ett förlorat paket.Blocket
withstänger socketen automatiskt.
9.12.4. Gränser för datagramstorlek¶
UDP-datagram kan i teorin vara upp till cirka 64 KB, men den praktiska gränsen är mycket mindre. Varje länk i sökvägen mellan avsändaren och mottagaren har en Maximum Transmission Unit (MTU) – det största enskilda byteblock som länken kan bära i en bildruta. Både Ethernet och Wi-Fi sätter taket för detta till cirka 1500 byte, och nästan varje internetväg går tillbaka till den gränsen någonstans.
När ett datagram överskrider MTU för en länk det måste passera, delar nätverkslagret upp det i mindre fragment och sätter ihop dem igen vid destinationen. UDP självt ser aldrig uppdelningen, men fragment har flera obekväma egenskaper:
Om ett enda fragment går förlorat kastas hela datagrammet vid mottagaren – det finns ingen omsändning per fragment. Sannolikheten för förlust växer med antalet fragment.
Vissa nätverk och brandväggar kastar fragmenterade paket helt och hållet och behandlar dem som misstänkta.
Återmontering kostar minne hos mottagaren, vilket på en mikrokontroller finns i knapp tillgång.
Den praktiska regeln på kameran: håll UDP-meddelanden väl under 1500 byte. Cirka 1400 byte lämnar utrymme för IP- och UDP-headers, eventuell tunnlingsoverhead som sökvägen lägger till, och små variationer i MTU mellan Ethernet-, Wi-Fi- och VPN-länkar. Program som behöver skicka mer än så bör antingen dela upp data på applikationslagret eller byta till TCP, som hanterar uppdelningen och återmonteringen automatiskt.
9.12.5. Vanliga fallgropar¶
Att glömma att UDP kan förlora paket. Kod som fungerar perfekt på ett tyst lokalt nätverk misslyckas ibland på subtila sätt på ett mer trafikerat eller större nätverk. Designa alltid för möjligheten att meddelandet inte kom fram.
Mottagaren är inte bunden innan avsändaren skickar. Ett datagram som skickas till en port som ingen lyssnar på kastas tyst. Starta mottagaren först.
Att skicka ett datagram större än sökvägens MTU. Se föregående avsnitt – håll meddelanden under ~1400 byte.
Mönstren ovan täcker nästan all UDP-användning som kameran behöver. Nästa sida gör motsvarande för TCP.