9.18. MQTT, байт за байтом¶
К этому моменту у камеры есть всё необходимое, чтобы общаться с реальным сервисом в открытом интернете: TCP-сокет, TLS для его обёртки, DNS для именования узла и asyncio, позволяющий тому же скрипту выполнять другую работу, пока соединение открыто. MQTT – это первый протокол передачи, который объединяет всё это в нечто, что действительно используется в готовом продукте.
Эта страница описывает сам протокол – формат передачи, роли каждого участника и компромиссы в его устройстве – достаточно честно, чтобы входящий в комплект клиент mqtt выглядел как очевидная обёртка над уже известным, а не как шаг в неизвестность.
9.18.1. Pub/sub против request/response¶
HTTP – протокол, к которому большинство проектов с камерой обращаются в первую очередь, – это request/response. Клиент запрашивает у конкретного сервера конкретный ресурс; сервер отвечает. Каждый обмен происходит один-к-одному, и оба конца заранее знают адрес друг друга.
MQTT – это publish/subscribe. Клиенты подключаются к посреднику, называемому брокером. Издатель (publisher) отправляет сообщение в именованную тему (topic), не зная и не интересуясь, кто его слушает. Подписчик (subscriber) сообщает брокеру, какие темы ему нужны, и затем получает каждое сообщение, опубликованное в эти темы. Брокер выполняет рассылку: одна публикация в yard-cam/motion достигает каждого устройства, подписанного на yard-cam/motion, будь их ноль, одно или пятьдесят.
Из этой смены модели следуют три вещи:
Развязка. Издателям не нужно знать о существовании подписчиков. Подписчики могут появляться и исчезать незаметно для издателя. Добавление второй панели мониторинга – это одна строка кода на новой панели; камера при этом не меняется.
Рассылка. Брокер обрабатывает каждую копию, поэтому камера отправляет один пакет независимо от того, сколько устройств его читают. Именно для этого сценария и создавался MQTT.
Асимметрия. Брокер теперь является обязательной частью инфраструктуры – без него протокол не работает. Для домашних проектов это обычно бесплатный публичный брокер (
test.mosquitto.org,broker.hivemq.com) или небольшой брокер, который вы запускаете сами.
9.18.2. Темы¶
Темы – это строки, разделённые слэшами. По соглашению наиболее общее находится слева, наиболее конкретное – справа:
yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3
Два подстановочных символа работают в подписках (но не в публикациях):
+соответствует одному уровню.+/motionподписывает на тему motion каждой камеры;yard-cam/+подписывает на каждую подтему yard-cam.#соответствует одному или нескольким завершающим уровням.yard-cam/#подписывает наyard-cam/motion,yard-cam/temperature,yard-cam/temperature/sensor-3и на всё остальное подyard-cam/. Он должен находиться в конце подписки.
Строки тем чувствительны к регистру. Согласно спецификации, ведущий символ $ обозначает внутренние темы брокера ($SYS/...), в которые издатели не должны писать.
9.18.3. Формат пакета¶
MQTT работает поверх TCP. Каждый управляющий пакет начинается с однобайтового фиксированного заголовка, за которым следует поле Remaining Length переменной длины, затем переменный заголовок, специфичный для типа пакета, и наконец полезная нагрузка. Один и тот же внешний формат охватывает каждую команду – CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT и остальные – поэтому клиент MQTT можно написать в несколько сотен строк.
Фиксированный заголовок занимает один байт:
Биты 7..4 – это тип управляющего пакета.
0x3– это PUBLISH (поэтому первый байт обычно начинается с0x3?).0x1– это CONNECT,0x2– CONNACK,0x8– SUBSCRIBE,0xC– PINGREQ,0xE– DISCONNECT и т. д.Биты 3..0 – это флаги, специфичные для типа пакета. Для PUBLISH флаги кодируют флаг повторной передачи DUP, уровень QoS (2 бита) и флаг RETAIN.
Remaining Length – это целое число переменной длины от 1 до 4 байт, которое считает каждый байт после себя. Старший бит каждого байта является маркером продолжения – 1 означает «следует ещё один байт длины», 0 означает «это последний». Длина менее 128 умещается в один байт; для больших нагрузок используется больше байтов. Максимальная кодируемая длина – 256 МиБ.
Для PUBLISH переменный заголовок – это имя темы (2-байтовая длина, затем байты UTF-8), за которым следует 2-байтовый идентификатор пакета, существующий только при QoS 1 или 2. Оставшиеся байты – это полезная нагрузка, которую протокол рассматривает как непрозрачные байты.
Минимальный PUBLISH уровня QoS 0 со значением ok в тему a/b выглядит так:
30 07 00 03 'a' '/' 'b' 'o' 'k'
30– PUBLISH, все флаги нулевые.07– далее следует 7 байт.00 03– длина темы 3.'a' '/' 'b'– тема.'o' 'k'– полезная нагрузка.
Девять байт на проводе – и сообщение попадает к каждому подписчику на a/b у брокера.
9.18.4. Уровни QoS¶
Quality-of-Service определяет, насколько усердно брокер (и клиент) работают над обеспечением доставки. Три уровня:
QoS 0 – не более одного раза. Отправил и забыл. Пакет PUBLISH отправляется и никогда не подтверждается. Если TCP доставляет, брокер пересылает. Если соединение прерывается во время отправки, сообщение теряется. Большинство сенсорной телеметрии прекрасно обходится QoS 0 – одно пропущенное показание температуры в потоке, выдающем данные каждые 30 секунд, не имеет значения.
QoS 1 – не менее одного раза. Издатель включает идентификатор пакета и ждёт PUBACK. Если PUBACK не приходит до истечения таймаута, издатель повторно передаёт пакет с установленным флагом DUP. Брокер в итоге может доставить подписчику одно и то же сообщение дважды на том же уровне; подписчик должен быть готов обрабатывать дубликаты.
QoS 2 – ровно один раз. Четырёхэтапное рукопожатие (PUBREC / PUBREL / PUBCOMP) гарантирует, что сообщение будет доставлено ровно один раз, даже при переподключениях. Дорого по числу циклов обмена и состоянию брокера. Немногим приложениям с камерой оно нужно.
Входящий в комплект клиент mqtt реализует QoS 0 и QoS 1; при запросе QoS 2 он вызывает исключение. Для камеры, передающей показания датчиков, QoS 0 почти всегда является правильным выбором.
9.18.5. Сохраняемые сообщения и последняя воля¶
Стоит знать о двух функциях, потому что они меняют то, что брокер запоминает о вашей теме.
RETAIN. Если у PUBLISH установлен флаг RETAIN, брокер сохраняет сообщение и пересылает его каждому будущему подписчику в момент его подписки. Так MQTT обрабатывает вопрос «какое сейчас значение?» – датчик публикует своё последнее показание с флагом retain, и панель мониторинга, подписавшаяся десятью минутами позже, всё равно получает самое свежее значение вместо того, чтобы ждать следующей публикации. Повторная публикация с той же темой перезаписывает сохранённое значение; публикация пустой полезной нагрузки очищает его.
Последняя воля. При подключении клиент может передать брокеру «последнюю волю и завещание»: тему, полезную нагрузку, QoS и флаг retain. Если этот клиент отключается некорректно – TCP RESET, потеря питания, обрыв сети без пакета DISCONNECT – брокер публикует завещание от имени клиента. Подписчики видят его как уведомление камеры о том, что она отключилась. Сама камера завещание никогда не отправляет; это делает брокер, потому что к тому моменту камеры уже нет.
9.18.6. Keepalive и переподключение¶
CONNECT несёт интервал keepalive в секундах. Если клиент молчал в течение этого времени, брокер считает его мёртвым. Чтобы предотвратить это, клиент периодически отправляет PINGREQ (один байт: 0xC0) и получает в ответ PINGRESP (0xD0) – наименьший и самый дешёвый сигнал поддержки соединения, который может нести протокол. Большинство приложений с камерой устанавливают keepalive в 30 или 60 секунд.
Если TCP-соединение прерывается, обе стороны замечают это и переподключаются с нуля. Подписки, сделанные до обрыва, теряются, если только клиент не использовал при подключении постоянную сессию; для простых приложений с камерой шаблон с повторной подпиской при переподключении короче и ничуть не хуже.
Этого достаточно, чтобы читать спецификацию MQTT или вручную написать клиент поверх socket.socket. Входящий в комплект клиент в mqtt делает именно это, плюс предоставляет разумный API для прикладного кода.