9.12. Gniazda UDP¶
Ruch UDP w Pythonie jest wysyłany i odbierany za pomocą dwóch metod gniazda datagramowego: sendto() do wysłania datagramu pod wybrane miejsce docelowe oraz recvfrom() do odebrania datagramu i ustalenia, skąd przyszedł. Każde wywołanie przenosi jeden samodzielny komunikat; nie ma żadnego stanu połączenia.
9.12.1. Wysyłanie datagramu¶
Najprostsze wysłanie UDP to jeden wiersz Pythona na bazie konstruktora gniazda:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()
To wysyła b"hello" na port 9000 pod adresem 192.168.1.20 i nie wymaga niczego więcej. MicroPython wybiera efemeryczny port źródłowy; skrypt nie musi niczego wiązać.
Wysyłanie tego samego ładunku do wielu miejsc docelowych to po prostu pętla – gniazdo można ponownie wykorzystać między wysyłkami i nie ma żadnego połączenia do skonfigurowania:
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. Odbieranie datagramu¶
Aby odbierać datagramy, gniazdo musi zająć znany port, którego nadawcy będą używać jako miejsca docelowego. Służy do tego wywołanie 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)
Adres "0.0.0.0" oznacza „każdy interfejs IPv4 na kamerze” – niezależnie od tego, który interfejs Wi-Fi czy Ethernet doprowadza pakiety, port 9000 należy do tego gniazda.
Argument 1024 przekazany do recvfrom() to maksymalna liczba bajtów odczytywanych do zwracanego bufora. Datagramy UDP większe niż ten rozmiar zostaną obcięte; dobierz wartość do największego datagramu, jakiego spodziewa się aplikacja.
recvfrom() zwraca (data, src): odebrane bajty oraz adres nadawcy. Adres nadawcy to to, na co należy odpowiedzieć, co ułatwia napisanie prostego protokołu żądanie/odpowiedź:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
Domyślnie recvfrom() blokuje wykonanie, dopóki nie nadejdzie datagram. Wzorce pozwalające uniknąć blokowania – limity czasu, gniazda nieblokujące, asyncio – znajdują się na stronie Gniazda z asyncio.
9.12.3. Żądanie i odpowiedź¶
Dwa krótkie skrypty: jeden wysyła żądanie, drugi odbiera i odpowiada.
Odbiorca:
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)
Nadawca:
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?")
Kilka rzeczy wartych uwagi po stronie nadawcy:
Brak
bind()i brakconnect(). Klienci UDP po prostu wysyłają.settimeout()ustawia limit czasu na wywołanie odbioru. Jeśli odpowiedź nie nadejdzie w ciągu dwóch sekund, wywołanie zgłaszaOSErrorzamiast blokować w nieskończoność – naturalny sposób na wykrycie zgubionego pakietu.Blok
withautomatycznie zamyka gniazdo.
9.12.4. Ograniczenia rozmiaru datagramu¶
Datagramy UDP mogą teoretycznie osiągać około 64 KB, ale praktyczny limit jest znacznie mniejszy. Każde łącze na drodze między nadawcą a odbiorcą ma Maximum Transmission Unit (MTU) – największy pojedynczy blok bajtów, jaki to łącze może przenieść w jednej ramce. Zarówno Ethernet, jak i Wi-Fi ograniczają to do około 1500 bajtów, a niemal każda ścieżka internetowa gdzieś trafia na ten limit.
Gdy datagram przekracza MTU łącza, które musi pokonać, warstwa sieciowa dzieli go na mniejsze fragmenty i scala je z powrotem w miejscu docelowym. Sam protokół UDP nigdy nie widzi tego podziału, ale fragmenty mają kilka niewygodnych właściwości:
Jeśli choć jeden fragment zostanie zgubiony, cały datagram jest odrzucany u odbiorcy – nie ma retransmisji poszczególnych fragmentów. Prawdopodobieństwo utraty rośnie wraz z liczbą fragmentów.
Niektóre sieci i zapory sieciowe całkowicie odrzucają pofragmentowane pakiety, traktując je jako podejrzane.
Ponowne scalanie zużywa pamięć u odbiorcy, której na mikrokontrolerze jest niewiele.
Praktyczna zasada na kamerze: utrzymuj komunikaty UDP wyraźnie poniżej 1500 bajtów. Około 1400 bajtów pozostawia miejsce na nagłówki IP i UDP, ewentualny narzut tunelowania dodawany przez ścieżkę oraz niewielkie wahania MTU między łączami Ethernet, Wi-Fi i VPN. Aplikacje, które muszą wysłać więcej, powinny albo podzielić dane na fragmenty w warstwie aplikacji, albo przejść na TCP, który obsługuje dzielenie i scalanie automatycznie.
9.12.5. Częste pułapki¶
Zapominanie, że UDP może gubić pakiety. Kod, który działa bezbłędnie w cichej sieci lokalnej, czasem zawodzi w subtelny sposób w bardziej obciążonej lub rozległej sieci. Zawsze projektuj z myślą o tym, że komunikat mógł nie dotrzeć.
Odbiorca niezwiązany z portem zanim nadawca wyśle. Datagram wysłany na port, na którym nikt nie nasłuchuje, jest po cichu odrzucany. Uruchom najpierw odbiorcę.
Wysyłanie datagramu większego niż MTU ścieżki. Zobacz poprzedni rozdział – utrzymuj komunikaty poniżej ~1400 bajtów.
Powyższe wzorce obejmują niemal każde zastosowanie UDP, po jakie sięga kamera. Następna strona robi to samo dla TCP.