9.15. Имена и DNS¶
Каждая страница до сих пор использовала числовые IP-адреса – 192.168.1.20 и подобные. Реальные приложения почти никогда так не делают. Серверы именуются example.com или api.example.com, и приложение во время выполнения находит для имени IP, чтобы отправлять пакеты. Этот поиск – Domain Name System, или DNS.
9.15.1. Во что разрешается имя¶
Имя – это просто метка. example.com само по себе не несёт никакой IP-информации – его нужно искать, так же как номер телефона ищется в телефонной книге. Инфраструктура DNS – это распределённая телефонная книга интернета, а результат поиска – это один или несколько IP-адресов, к которым камера может подключиться.
example.com -> 93.184.216.34
Одно имя часто разрешается в несколько адресов (для балансировки нагрузки, географической избыточности, версий одного сервиса по IPv4 и IPv6). Подходит любой из них; приложение выбирает один и пробует его, переходя к следующему в случае неудачи.
9.15.2. Как происходит поиск¶
Когда камера запрашивает example.com:
Камера отправляет небольшую UDP-датаграмму (да, UDP – см. UDP – отправь пакет и надейся на лучшее) на настроенный DNS-сервер. Адрес DNS-сервера пришёл из того же обмена DHCP, который выдал камере её собственный IP.
У DNS-сервера ответ может уже находиться в кэше (его недавно спрашивали). Если так, он отвечает немедленно.
Если нет, DNS-сервер обходит глобальную иерархию DNS: спрашивает у корневых серверов о
.com, спрашивает у тех серверов оexample.com, спрашивает у тех серверов об имени. Весь обход дерева скрыт от камеры; камера видит один запрос и один ответ.DNS-сервер кэширует результат на будущее и отправляет ответ обратно камере в виде ещё одной UDP-датаграммы.
Весь обмен обычно занимает несколько миллисекунд при тёплом кэше и до сотни или около того при холодном.
9.15.3. Интерфейс Python¶
Функция getaddrinfo() выполняет поиск и возвращает адрес, готовый для передачи конструктору сокета:
import socket
addr = socket.getaddrinfo("example.com", 80)[0][-1]
print(addr)
# ('93.184.216.34', 80)
Сигнатура – getaddrinfo(host, port). Возвращаемое значение – это список кортежей из 5 элементов (по одному на каждый разрешённый адрес); [0] выбирает первый из них, а [-1] выбирает последнее поле, которым является кортеж (ip, port), который можно сразу передать сокету:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(socket.getaddrinfo("example.com", 80)[0][-1])
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
...
Большинство кода камеры, общающегося с удалённым сервером, начинается с этой одной строки. Помощники asyncio (asyncio.open_connection()) выполняют поиск внутри себя, если вместо числового IP передано имя, поэтому асинхронный код обычно не вызывает getaddrinfo() напрямую.
9.15.4. Что может пойти не так¶
Нет доступного DNS-сервера. Если Wi-Fi только что поднялся и связь нестабильна, первый вызов
getaddrinfo()может завершиться по таймауту. ВозбуждаетсяOSError; повторите попытку, когда связь стабилизируется.Имя не существует. Опечатка или устаревшее имя возбуждает
OSErrorпосле того, как DNS-сервер вернёт «нет такого имени». Код ошибки отличает это от «DNS-сервер недоступен», но для большинства приложений с камерой политика повтора/отказа одинакова.Возвращённый адрес не работает. DNS может вернуть адрес, на котором сервис больше не размещён. Решение – перейти к следующей записи в списке результатов
getaddrinfo()или выполнить поиск имени снова позже (к тому времени кэш DNS, скорее всего, обновится).Captive-порталы. Некоторые сети Wi-Fi перехватывают DNS и возвращают IP страницы captive-портала для всего. Камера будет казаться подключённой, но получаемые данные не будут соответствовать тому, что отправил бы реальный сервис. Не часто встречается в развёрнутых средах, но случается в Wi-Fi на конференциях и подобных сетях.
9.15.5. Собственное имя камеры¶
Страницы до сих пор искали имена других устройств. У камеры также есть собственное имя, которое она объявляет в локальной сети, когда запрашивает адрес. По умолчанию это обобщённый идентификатор, различающийся в зависимости от платы; network.hostname() переопределяет его на что-то, что распознает остальная сеть:
import network
network.hostname("kitchen-cam")
# ... then bring the link up as usual ...
Установите имя хоста до поднятия интерфейса, чтобы имя ушло в составе первоначального запроса адреса камеры, а не после.
Теперь происходят две вещи, как только камера присоединилась к сети. Во-первых, большинство домашних маршрутизаторов регистрируют имена хостов, которым они выдают адреса, в своём локальном поиске, так что другие устройства могут обращаться к камере как kitchen-cam – не зная числового адреса, который маршрутизатор ей назначил. (Корпоративные сети могут поддерживать это, а могут и нет; поведение зависит от маршрутизатора.) Во-вторых, камера сама запускает mDNS-ответчик прямо из коробки, поэтому то же имя также доступно как kitchen-cam.local в любой сети, клиенты которой понимают mDNS – а это делают большинство современных настольных операционных систем.
Примечание
Передавайте в network.hostname() голое имя хоста – просто "kitchen-cam", без суффикса .local. Форму .local добавляет mDNS во время поиска; встраивание её в имя хоста заставляет камеру объявлять kitchen-cam.local как обычное имя хоста, что не соответствует ожиданиям ни одной из сторон поиска.
9.15.6. Когда имён недостаточно¶
Есть несколько ситуаций, в которых DNS не помогает:
Обнаружение в локальной сети. Стандартный DNS отвечает на вопросы об именах, зарегистрированных в глобальном каталоге; он ничего не знает об устройствах в локальном сегменте. Multicast DNS (mDNS) – это система, которая заполняет этот пробел. Каждое участвующее устройство присоединяется к специальной multicast-группе в локальной сети и слушает запросы; когда устройство запрашивает имя, оканчивающееся на
.local, то устройство, которое владеет этим именем, отвечает напрямую. Никакого центрального сервера, никакой настройки DNS. Bonjour на устройствах Apple, Avahi на Linux и Windows 10+ – все говорят на одном протоколе, поэтому имяkitchen-cam.local, настроенное в предыдущем разделе, разрешается в домашней сети без какой-либо дополнительной настройки.Сторона камеры в этом обмене – это ответчик, и он уже работает. Чего у камеры нет, так это резолвера – другой половины, которая позволила бы скрипту спросить у сети «где
printer.local?» и получить ответ. Входящий в комплект код mDNS работает только как ответчик (встраиваемые устройства обычно являются тем, что находят, а не тем, кто ищет). Когда обнаружение должно идти в другом направлении, UDP-широковещание (см. UDP – отправь пакет и надейся на лучшее) – более простое решение для случая локального сегмента, либо чисто питоновский модуль, такой как cbrand/micropython-mdns, добавляет полноценный резолвер.Имена IPv6.
getaddrinfo()возвращает результаты как IPv4, так и IPv6, если доступны оба. Выберите семейство, которое может использовать сокет приложения.
Для большей части кода на стороне камеры getaddrinfo – это одна строка в начале любой функции, открывающей сетевое соединение. Примеры в других местах этого раздела, работавшие с "192.168.1.20", точно так же работали бы с публичным именем вроде "api.example.com" – нужно просто сначала выполнить разрешение.