9.18. MQTT, байт за байтом¶
На цьому етапі камера має все необхідне для зв’язку з реальним сервісом в інтернеті: TCP-сокет, TLS для шифрування, DNS для визначення адреси вузла та asyncio, щоб той самий скрипт міг виконувати іншу роботу, поки з’єднання відкрите. MQTT — це перший мережевий протокол, який об’єднує всі ці компоненти в щось, що реально використовується у розгорнутому продукті.
Ця сторінка охоплює сам протокол – формат на рівні дротових даних, ролі кожного учасника та компроміси в його дизайні – достатньо докладно, щоб вбудований клієнт mqtt виглядав як очевидна обгортка вже відомого, а не стрибок у невідоме.
9.18.1. Pub/sub проти запит/відповідь¶
HTTP – протокол, до якого більшість проєктів із камерою звертається першочергово – є запит/відповідь. Клієнт запитує у конкретного сервера конкретний ресурс; сервер відповідає. Кожен обмін є «один до одного», і обидва кінці заздалегідь знають адреси один одного.
MQTT є публікація/підписка. Клієнти підключаються до третьої сторони посередині, яка називається брокером. Видавець надсилає повідомлення в іменовану тему, не знаючи і не дбаючи про те, хто слухає. Підписник повідомляє брокеру, які теми він хоче отримувати, і отримує всі повідомлення, опубліковані в цих темах. Брокер здійснює розподіл: одна публікація в 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. Кожен керуючий пакет починається з однобайтного фіксованого заголовку, за яким слідує поле змінної довжини Залишкова довжина, потім змінний заголовок конкретного типу пакету, а потім навантаження. Однаковий зовнішній формат охоплює всі команди – 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.
Залишкова довжина — це цілочисельне значення змінної довжини від 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 для коду застосунку.