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; различаются только методы, вызываемые после. Следующие три страницы проходят через практические шаблоны:

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