9.11. Obiekty gniazd

Interfejsem Python do warstwy transportowej jest klasa socket.socket. Gniazdo reprezentuje jeden punkt końcowy konwersacji sieciowej – adres, port oraz protokół (UDP lub TCP), na którym odbywa się konwersacja. Rozdziały o sterowaniu sprzętem otwierały instancje UART, aby komunikować się po przewodzie; ta sekcja otwiera instancje socket, aby komunikować się w sieci. Model jest taki sam; bazowa usługa jest tylko znacznie bardziej rozbudowana.

9.11.1. Tworzenie gniazda

Gniazdo opisują trzy argumenty: jaką rodzinę adresów obsługuje, jaki typ gniazda oferuje i jakiego protokołu używa. Wartości domyślne pokrywają przypadki używane w pozostałej części tej sekcji:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # IPv4 TCP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    # IPv4 UDP

Dwie stałe, między którymi aplikacja wybiera niemal w każdym przypadku:

  • AF_INET – adresy IPv4 (numeryczny IP plus port). Najczęstszy wybór. AF_INET6 to odpowiednik dla IPv6.

  • SOCK_STREAM – połączenie TCP, niezawodny strumień bajtów. SOCK_DGRAM to odpowiednik dla UDP.

Trzeci argument (protokół) pozostaje równy zero, co wybiera właściwą wartość domyślną na podstawie dwóch pierwszych. Pełny konstruktor jest udokumentowany w socket — moduł socket.

9.11.2. Adresy w gnieździe

Adres gniazda to krotka (host, port)

("192.168.1.50", 80)
("0.0.0.0", 8000)

Host to adres IP w postaci łańcucha znaków. Port to 16-bitowa liczba całkowita omówiona w Porty.

Warto znać kilka specjalnych łańcuchów host:

  • "0.0.0.0" oznacza „każdy interfejs IPv4 na tym urządzeniu”. Serwer powiązany z tym adresem akceptuje połączenia na dowolnym adresie posiadanym przez kamerę.

  • "127.0.0.1" to localhost – ruch do niego nigdy nie opuszcza urządzenia. Przydatny do testowania.

  • "255.255.255.255" to lokalny adres rozgłoszeniowy. Datagram UDP wysłany na ten adres trafia do każdego urządzenia w lokalnym segmencie.

Nazwy domenowe takie jak "example.com" nie są prawidłowymi łańcuchami host w adresie gniazda. Trzeba je najpierw rozwiązać do adresu IP; Nazwy i DNS omawia wywołanie getaddrinfo(), które to robi.

9.11.3. Dwie role

Cykl życia gniazda zależy od tego, po której stronie konwersacji się ono znajduje. Gniazdo klienta wywołuje connect() (lub, w przypadku UDP, po prostu sendto()), aby komunikować się ze znanym serwerem. Gniazdo serwera wywołuje bind(), aby zarezerwować port, a następnie albo listen() i accept() (dla TCP), albo recvfrom() (dla UDP), aby odbierać przychodzący ruch.

W obu przypadkach używany jest ten sam konstruktor socket; różnią się tylko metody wywoływane później. Następne trzy strony przeprowadzają przez praktyczne wzorce:

9.11.4. Zamykanie gniazda

Każde gniazdo przechowuje niewielki fragment stanu systemu operacyjnego (rezerwację portu, bufory, stan TCP połączenia). Gdy aplikacja skończy z niego korzystać, close() zwalnia ten stan. Zapomniane gniazdo to powolny wyciek, który się kumuluje; w pętli otwierającej połączenia pominięcie close w końcu wyczerpie pulę dostępnych gniazd kamery.

Najczystszym wzorcem jest instrukcja with

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(addr)
    s.send(b"GET / HTTP/1.0\r\n\r\n")
    ...
# socket is closed automatically here, even on error

Gniazda implementują protokół menedżera kontekstu omówiony w przeglądzie języka Python, więc blok with gwarantuje wywołanie close() niezależnie od tego, czy blok zakończył się normalnie, czy przez zgłoszenie wyjątku.

9.11.5. Dokumentacja modułu socket

Ta i kolejne strony przeprowadzają przez API w formie opisowej. Pełny opis na poziomie argumentów każdej metody, każdej flagi i każdej stałej udostępnianej przez moduł znajdziesz w socket — moduł socket. Dokumentacja jest też miejscem, w którym należy szukać rzadziej używanych operacji (opcji gniazda, członkostwa w grupach multicast, identyfikatorów zakresu IPv6), których ta sekcja nie omawia.