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), которые этот раздел не охватывает.