9.11. Объекты сокетов#
Интерфейс Python к транспортному уровню — это класс socket.socket. Сокет представляет одну конечную точку сетевого диалога — адрес, порт и протокол (UDP или TCP), по которому идёт диалог. Главы об управлении аппаратурой открывали экземпляры UART для общения по проводу; этот раздел открывает экземпляры socket для общения по сети. Схема та же; просто базовая служба гораздо более функциональна.
9.11.1. Создание сокета#
Сокет описывается тремя аргументами: на каком семействе адресов он говорит, какой тип сокета предлагает и какой протокол использует. Значения по умолчанию покрывают случаи, используемые в остальной части этого раздела:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4 TCP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # IPv4 UDP
Две константы, между которыми приложение выбирает почти в каждом случае:
AF_INET— адреса IPv4 (числовой IP плюс порт). Самый распространённый выбор.AF_INET6— эквивалент для IPv6.SOCK_STREAM— TCP-соединение, надёжный поток байтов.SOCK_DGRAM— эквивалент для UDP.
Третий аргумент (протокол) оставлен равным нулю, что выбирает правильное значение по умолчанию на основе первых двух. Полный конструктор описан на странице socket — модуль socket.
9.11.2. Адреса в сокете#
Адрес сокета — это кортеж (host, port):
("192.168.1.50", 80)
("0.0.0.0", 8000)
Host — это IP-адрес в виде строки. Port — это 16-битное целое число, рассмотренное на странице Порты.
Стоит знать несколько особых строк host:
"0.0.0.0"означает «каждый интерфейс IPv4 на этом устройстве». Сервер, привязанный к этому адресу, принимает соединения по любому адресу, которым располагает камера."127.0.0.1"— это localhost; трафик к нему никогда не покидает устройство. Полезно для тестирования."255.255.255.255"— это локальный широковещательный адрес. UDP-датаграмма, отправленная на него, поступает на каждое устройство в локальном сегменте.
Доменные имена вроде "example.com" не являются допустимыми строками host в адресе сокета. Сначала их нужно разрешить в IP; страница Имена и DNS рассматривает вызов getaddrinfo(), который это делает.
9.11.3. Две роли#
Жизненный цикл сокета зависит от того, на какой стороне диалога он находится. Клиентский сокет вызывает connect() (или, для UDP, просто sendto()), чтобы общаться с известным сервером. Серверный сокет вызывает bind(), чтобы занять порт, а затем либо listen() и accept() (для TCP), либо recvfrom() (для UDP), чтобы принимать входящий трафик.
В обоих случаях используется один и тот же конструктор socket; различаются только методы, вызываемые после. Следующие три страницы проходят через практические шаблоны:
UDP-сокеты — отправка и приём датаграмм.
TCP-сокеты — TCP-клиент и сервер.
Сокеты с asyncio — всё вышеперечисленное, но внутри цикла событий
asyncio.
9.11.4. Закрытие сокета#
Каждый сокет хранит небольшой фрагмент состояния операционной системы (резервирование порта, буферы, состояние TCP-соединения). Когда приложение заканчивает с ним работу, close() освобождает это состояние. Забытый сокет — это медленная утечка, которая накапливается; в цикле, открывающем соединения, пропуск close в конечном итоге исчерпает пул доступных сокетов камеры.
Наиболее чистый шаблон — это оператор 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
Сокеты реализуют протокол менеджера контекста, рассмотренный в обзоре Python, поэтому блок with гарантирует вызов close() независимо от того, завершился ли блок нормально или из-за возникновения исключения.
9.11.5. Справочник по socket#
Эта и следующие страницы проходят через API в повествовательной форме. Полный справочник на уровне аргументов по каждому методу, каждому флагу и каждой константе, которые предоставляет модуль, см. в socket — модуль socket. Справочник — это также место, куда стоит заглянуть за менее распространёнными операциями (параметры сокета, членство в multicast-группе, идентификаторы области IPv6), которые этот раздел не охватывает.