Доброго здравия, уважаемые читатели!
Вначале было слово… Вначале я начал писать статью про MQTT client API на ESP32 и ESP-IDF. И, как обычно, перед основным содержимым полагается небольшая теоретическая часть. Но в данном случае получилось так, что теоретическая часть получилась не такой уж и небольшой, а тянет на вполне себе отдельную статью. А кроме того, вынести эти сведения в отдельную статью представляется удобным с той точки зрения, что ссылки на статью можно вставить в другие статьи, а не копипастить один и тот же текст несколько раз.
Дисклеймер. Про MQTT не писал только ленивый. Видно, я он самый и есть, но буду исправляться. Я не использовал в подготовке данной статьи другие похожие статьи, а только данные из официальных спецификаций на сайте https://mqtt.org. Ну и свое понимание, конечно. Ни в коем случае не считаю этот текст истиной в последней инстанции, и в с случае расхождений с официальными источниками следует считать правильными их – на сайте https://mqtt.org.
Введение
MQTT расшифровывается как Message Queuing Telemetry Transport (транспорт телеметрии очереди сообщений). Он был разработан Энди Стэнфордом-Кларком (IBM) и Арленом Ниппером в 1999 году для подключения систем телеметрии нефтепроводов через спутник. Хотя изначально он был запатентованным протоколом, в 2010 году он был опубликован как royalty-free (безвозмездное использование), а в 2014 году стал стандартом OASIS.
MQTT протокол — это чрезвычайно простой и легкий транспортный протокол обмена сообщениями, основанный на принципе “клиент-сервер publish / subscribe”, разработанный для небольших устройств и сетей с низкой пропускной способностью, высокой задержкой или ненадежных сетей. Принципы проектирования таких устройства заключаются в минимизации требований к пропускной способности сети и ресурсам устройств, а также в попытке обеспечить надежность и некоторую степень гарантии доставки. Эти принципы также делают протокол идеальным для мира подключенных устройств «интернета вещей» и для мобильных приложений, где пропускная способность и заряд батареи имеют первостепенное значение.
Протокол MQTT работает поверх TCP/IP или поверх других сетевых протоколов, которые обеспечивают упорядоченные, без потерь, двунаправленные соединения. Его особенности включают:
- Использование шаблона передачи сообщений «публикация/подписка», который обеспечивает распространение сообщений по принципу «один ко многим» и разделение приложений.
- Транспорт сообщений, не зависящий от содержимого передаваемых данных.
- Три уровня QoS (качества обслуживания) при доставке сообщений:
- «Максимум один раз», когда сообщения доставляются в соответствии с наилучшими усилиями операционной среды. При этом вполне может произойти потеря сообщений. Этот уровень может использоваться, например, для передачи данных с датчиков окружающей среды, где не имеет значения, потеряно ли отдельное показание, поскольку следующее будет опубликовано вскоре после этого.
- «Хотя бы один раз» — сообщения гарантированно будут доставлены, но поскольку они могут быть отправлены несколько раз – могут быть дубликаты.
- «Точно один раз», когда сообщение гарантированно будет доставлено ровно один раз. Этот уровень может использоваться, например, в системах выставления счетов, где дублирование или потеря сообщений может привести к неправильному начислению платежей.
- Небольшие транспортные издержки и обмен протоколами сведены к минимуму для уменьшения сетевого трафика.
- Механизм уведомления заинтересованных сторон в случае возникновения нештатного отключения.
Ознакомиться с официальной документацией можно здесь.
Для меня важно, что MQTT “из коробки” и без “доработки напильником” обеспечивает три базовых возможности:
- Двухстороннюю передачу сообщений через “стандартный” промежуточный сервер в сети, что является непременным условием для передачи данных между разными сетями за “NAT-ом”. При этом вам не потребуется поднимать свой сервер и придумывать код на сервере – в сети достаточно публичных серверов. Конечно, никто не запрещает иметь и свой собственный MQTT-сервер.
- Передачу сообщений “один ко многим”. Сообщения от одного отправителя (издателя) могут быть доставлены практически одновременно нескольким получателям (подписчикам).
- Систему уведомлений о подключении устройства к серверу и внезапной потере соединения.
Кроме того, немаловажное значение имеет то, что под данный протокол написано большое количество библиотек и приложений, которые можно использовать в ваших проектах. Как под мобильные операционные системы, так и для настольных компьютеров. Это позволяет в какой-то мере не беспокоится о написании приложения, способного управлять вашим домом-помощником.
Поэтому на настоящий момент я использую MQTT-протокол как основной канал управления своими IoT-устройствами.
Принципы работы [mqtt-v3.1.1]
Издатель не требует каких-либо настроек по количеству или расположению подписчиков, получающих сообщения. Более того, подписчикам не требуется настройка на конкретного издателя. В одной системе может быть несколько серверов (брокеров), распространяющих сообщения. Издатель публикует сообщения с заранее определенной темой (topic) в общем случае без оглядки на то, должен ли их вообще кто-то получить. А подписчик, в свою очередь, может подписаться только на те темы (topic), которые ему “интересны” – и только сообщения в этих темах будут ему доставлены.
На что стоит обратить особое внимание:
- У клиентов нет адресов, как в системах электронной почты, и сообщения клиентам напрямую не отправляются.
- Сообщения отправляются серверу (брокеру) только с определенной темой ( topic ).
- Задача сервера (брокера) MQTT — фильтровать сообщения по темам, а затем рассылать их подписчикам.
- Клиент-подписчик может получать эти сообщения, только подписавшись на эту тему у того же брокера.
- Прямой связи между издателем и подписчиком не существует .
- Все клиенты могут публиковать (транслировать) и подписываться (получать).
- Брокеры MQTT обычно не хранят сообщения (но есть небольшое исключение).
Полезная аналогия — телевидение или радио. Телевещатель транслирует телепрограмму по определенному каналу, а зритель настраивается на этот канал, чтобы посмотреть трансляцию. Прямой связи между вещателем и зрителем нет, программу в данный момент может никто не смотреть, а могут смотреть тысячи и миллионы зрителей.
Терминология [mqtt-v3.1.1]
🔌 Сетевое подключение
Среда передачи данных, предоставляемая базовым транспортным протоколом, используемым MQTT (чаще всего это TCP/IP, но есть версии и для ZigBee и других транспортных протоколов).
- Подключает клиента к серверу.
- Предоставляет средства для отправки упорядоченного потока байтов без потерь в обоих направлениях.
Существует несколько способов подключения клиента к серверу.
- TCP/IP – передача данных осуществляется “поверх” стандартного транспортного протокола TCP/IP. Без какой-либо защиты – все данные отправляются в открытом виде.
Стандартный порт 1883, но на некоторых публичных брокерах может быть другим. - TCP/IP + SSL – то же самое, но используется защищенное соединение. Может быть написано TLS, но это фактически уже то же самое (SSL устарел и почти везде заменен на TLS). Этот вариант особенно актуален, если вы пытаетесь подключиться к публичному серверу с авторизацией.
Стандартный порт 8883, но на некоторых публичных брокерах может быть другим. - Websockets – позволяет получать данные MQTT непосредственно в веб-браузер. Это важно, поскольку веб-браузер может стать фактическим интерфейсом для отображения данных MQTT.
- Websockets + SSL – то же самое, но используется защищенное соединение. Некоторые публичные брокеры даже не допускают возможность подключения через без TLS.
📟 Client :: Клиент
Программа или устройство, использующее MQTT-протокол. Клиент всегда устанавливает сетевое соединение только с сервером, прямые соединения между клиентами не допускаются. Он может:
- Публиковать сообщения, которые могут быть интересны другим клиентам.
- Подписаться на другие темы, чтобы получать сообщения от других клиентов, которые ему интересны.
- Отменить подписку, чтобы удалить свой запрос на сообщения от других клиентов.
- Отключиться от сервера.
В качестве клиента может выступать библиотека или модуль на IoT устройстве, программа для смартфона или программа для компьютера. Обычно приложение-клиент может как публиковать сообщения, так и подписываться на другие темы. Так же работают и библиотеки для Arduino / ESP-IDF. Но есть исключения – некоторые утилиты командной строки разделяют эти функции, что, впрочем, вполне обосновано.
🖥 Server ( Broker ) :: Сервер ( Брокер )
Программа или устройство, выступающее в качестве посредника между клиентами, публикующими сообщения, и клиентами, оформившими подписки. Ранее очень часто использовался термин broker, сейчас же на официальном сайте рекомендуют использовать термин server. Тем не менее, равнозначно можно использовать оба термина.
Сервер:
- Принимает сетевые подключения от клиентов.
- Принимает сообщения, опубликованные клиентами.
- Обрабатывает запросы клиентов на подписку и отмену подписки.
- Пересылает сообщения, соответствующие подпискам клиентов.
Серверов может быть несколько, и они могут быть (но не обязаны) соединены между собой так называемыми мостами. В этом случае часть сообщений может автоматически пересылаться с одного сервера на другой. Например IoT-клиенты могут быть подключены к MQTT-серверу в локальной сети, тот пересылает сообщения на удаленный публичный сервер, который в свою очередь рассылает сообщения на мобильные клиенты в другой сети.
Существует множество MQTT-брокеров, которые можно использовать для тестирования и реальных приложений. Некоторый список публичных серверов можно найти и на данном сайте. Вам не обязательно сразу же создавать свой сервер. Хотя можно это сделать, например, на Raspberry Pi, роутере Keenetic или роутере с OpenWRT.
💬 Payload / message :: Полезная нагрузка / сообщение
Любые данные, передаваемые протоколом MQTT по сети. Когда сообщения транспортируются по MQTT-сети, они обязательно имеют связанное с ними имя темы topic и качество обслуживания QoS. В качестве сообщения может передано всё, что угодно – текст, значения с датчиков, состояние датчиков и т.д. и т.п. И даже двоичные данные.
Например:
topic: temperature payload: 36.6
или например в JSON-формате:
topic: home/indoor payload: {"temperature":25.0,"humidity":42.5}
📚 Topic Name :: Название темы ( Топик )
Текстовая метка, прикрепленная к сообщению, которое сопоставляется с подписками, известными серверу. Сервер отправляет копию сообщения каждому клиенту, имеющему подписку с соответствующим фильтром. Топик может быть максимально простым, несколько примеров ниже:
temperature t1 abc
и структурированным, т.е. состоящим из нескольких отдельных частей, разделенных слешем “/”:
home/kitchen/temperature
Зачем это необходимо? Во первых – для лучшей организации структуры топиков, а кроме того, это очень помогает при подписке с подстановочными знаками (ниже будет понятно почему). Например:
// Кухня: home/kitchen/switch1 home/kitchen/switch2 // Cпальня: home/bedroom/switch1 home/bedroom/switch2 Гараж: garage/switch1
Я настоятельно рекомендую всем создавать топики с “правильными” структурированными и понятными именами – иначе через полгода вы сами не вспомните, где что лежит и когда все это кончится у вас что публикуется.
📥 Subscribe :: Подписка
Подписка включает в себя фильтр тем и максимальный QoS. Подписка связана с одним сеансом. Сеанс может содержать более одной подписки. Каждая подписка в одном и том же сеансе имеет другой фильтр тем.
📕 Topic Filter :: Фильтр тем
Выражение, содержащееся в подписке, для указания интереса к одной или нескольким темам. Фильтр тем может содержать подстановочные знаки:
- # (
твой дом тюрьмарешетка) позволяет элегантно подписаться на все субтопики одной командой - + (плюсик) позволяет подписаться на все топики одного уровня
Например возьмем похожую структуру топиков, которые публикует какой-либо издатель или несколько:
// Кухня: home/kitchen/switch1 home/kitchen/switch2 home/kitchen/sensors/sensor1/temperature home/kitchen/sensors/sensor1/humidity home/kitchen/sensors/sensor2/temperature home/kitchen/sensors/sensor2/humidity // Cпальня: home/bedroom/switch1 home/bedroom/switch2 home/bedroom/sensors/sensor1/temperature home/bedroom/sensors/sensor1/humidity home/bedroom/sensors/sensor2/temperature home/bedroom/sensors/sensor2/humidity // Гараж: garage/switch1
Если мы хотим получать сообщения только на один определенный выключатель на кухне, то должны будем указать при подписке конкретный топик:
subscribe: home/kitchen/switch1
Если нам требуется подписаться на все сенсоры в спальне, можно установить такой фильтр:
subscribe: home/bedroom/sensors/#
Если же нам необходимо получать сообщения о состоянии выключателей в любой комнате, можно использовать такой фильтр:
subscribe: home/+/switch1 home/+/switch2
Как видите, структурированные темы (топики) не только облегчают восприятие, но полезны с технической точки зрения.
🤝 Session :: Сеанс
Взаимодействие между клиентом и сервером с сохранением состояния. Некоторые сеансы длятся только до тех пор, пока длится сетевое соединение, другие могут охватывать несколько последовательных сетевых соединений между клиентом и сервером. Сеанс может быть “чистым”, а может и не умываться неделями.
В случае “чистого сеанса” весь обмен данными между клиентом и сервером начинается с чистого листа – клиент отправляет LWT-сообщение и сам заново запрашивает подписки, сервер в свою очередь отправляет клиенту retained сообщения. Клиенты MQTT по умолчанию устанавливают чистый сеанс с брокером. При нечистом сеансе брокер запомнит клиентские подписки и может хранить какое-то время недоставленные сообщения для клиента. Такое поведение удобно использовать в случае случайного разрыва соединения в результате сетевого сбоя, например.
🔠 Client ID :: Имя клиента или идентификатор клиента
Все клиенты должны иметь имя или идентификатор клиента. Имя клиента используется брокером MQTT для отслеживания подписок и т. д. Имена клиентов должны быть уникальными. Если вы попытаетесь подключиться к брокеру MQTT с тем же именем, что и у уже существующего клиента, то существующее клиентское соединение будет разорвано. Поскольку большинство клиентов MQTT будут пытаться повторно подключиться после отключения, это может привести к возникновению цикла отключения и подключения.
🔢 Пакет управления MQTT
Пакет информации, который отправляется через сетевое соединение. Спецификация MQTT определяет четырнадцать различных типов пакетов управления, один из которых (пакет PUBLISH) непосредственно используется для передачи сообщений.
Кроме этого, есть еще несколько интересных служебных пакетов:
- Birth Message – которое сообщает миру что “я родился и живой”. Сервер таким образом узнает, что клиент подключился, и можно начинать пихать ему данные в случая грязной сессии.
- Keep Alive – которые сообщают брокеру что “я все еще живой” и обычно посылаются каждые 60 секунд (см. настройки клиента). Если брокер не получил это сообщение от клиента, то он принудительно пингует его чтобы выяснить жив ли тот, и если выясняется что он “неживой”, то брокер публикует за клиента за клиента LWT сообщения, чтобы все узнали что тот “скончался”.
Про остальные пакеты вы можете узнать из спецификации протокола [mqtt-v3.1.1]. Для начала применения протокола это знание, конечно, полезно, но не обязательно.
🗞 Last Will and Testament (LWT) :: Последняя воля и завещание
Довольно полезный и интересный подвид сообщения – LWT. Расшифровывается это как “Last Will and Testament”, что буквально переводится как “Последняя воля и завещание”. Суть этого в том, что при подключении клиент может отправить серверу некое сообщение, который последний будет хранить в тайне до тех пор, как клиент не отключится от сервера.
После того как сервер поймет, что клиент “мертв”, он немедленно сделает тайное явным, то есть запишет заранее заданное сообщение в топик. Таким образом подписчики смогут узнать, что новых данных ждать не приходится.
Если издатель отключается обычным образом, последнее LWT-сообщение не отправляется.
Протокол позволяет задать LWT-сообщение для каждого топика в отдельности. Но не все клиенты позволяют это сделать, в некоторых случаях LWT можно “оформить” только на один топик при установке подключения.
Как это можно применить? Например у нас есть топик состояния какого-то устройства:
village/home/main/status
В который при нормальной работе само устройство публикует данные об уровне сигнала WiFi, свободной памяти и наработанном времени:
32 : 06 : 25 RSSI: -61 dBi 93% 0 99%
Эти сведения можно вывести на плитку MQTT-приложения для наблюдения за состоянием устройства.
Если при подключении к брокеру указать для этого же топика LWT-сообщение “OFFLINE“, то при неожиданном обрыве связи с устройством надпись на экране сменится и отобразит новое состояние. Разумеется это только как пример, вы можете применить данный механизм по своему.
🛜 QoS :: качество обслуживания
QoS расшифровывается как Quality Of Service, то есть качество предоставляемой услуги. Для MQTT этот показатель отвечает за вероятность прохождения пакета между двумя точками сети. Флаг QoS может принимать следующие значения, основанные на надежности передачи сообщения:
- 0 = не более одного раза: отправитель пересылает сообщение получателю и забывает об этом. В процессе передачи сообщения могут быть потеряны в канале связи.
- 1 = по крайней мере один раз: получатель подтверждает доставку сообщения. Если подтверждение не было получено, отправитель должен отправить его еще раз и так до получения подтверждения о получении. Сообщения могут дублироваться, но доставка гарантирована.
- 2 = ровно один раз: сервер обеспечивает гарантированную доставку. Сообщения поступают точно один раз без потери или дублирования. Самый медленный и машинотрудозатратный вариант, так как отправитель и получатель дополнительно обмениваются подтверждениями.
В случае QoS > 0, сообщения доставленные на сервер, хранятся им то тех пор, как не будут доставлены всем подписчикам данной темы (топика).
📝 Флаг Retain (retained)
Протокол MQTT никоим образом не предназначен для накопления данных на сервере. Максимум что может храниться на сервере – последнее опубликованное издателем сообщение, если оно было отправлено с флагом retain. Флаг retain (или иногда пишут retained), используемый при публикации сообщения, заставляет сервер хранить последнее полученное сообщение неограниченно долго. При подключении нового клиента с чистой сессией, ему будут отправлены все retained сообщения, на которые он подпишется – сразу после оформления подписки. Это может быть полезно, если сообщения в каком-либо топике появляются сравнительно редко, что позволяет не ждать нового сообщения от издателя, а немедленно получить последнее значение прямо с сервера.
Однако использование этого флага требует известной аккуратности, неверное его использование может привести к невразумительным проблемам. Поэтому если вы пока не понимаете, для чего оно вам это надо – лучше не использовать этот флаг.
FAQ
Вопрос: Можно ли использовать MQTT без брокера?
Ответ: Нет
Вопрос: Могут ли несколько клиентов публиковать сообщения в одном и том же топике?
Ответ: Да
Вопрос: Возможно ли узнать “личность” клиента, опубликовавшего сообщение?
Ответ: Нет, если только клиент не включит эту информацию в топик или полезную информацию.
Вопрос: Что происходит с сообщениями, опубликованными в топиках, на которые никто не подписан?
Ответ: Сервер их сразу же отбрасывает как ненужные
Вопрос: Могу ли я подписаться на топик, в который пока никто не публикует сообщения?
Ответ: Легко
Вопрос: Как я могу узнать, какие топики были опубликованы на сервере?
Ответ: Сервер не хранит список топиков. Единственный способ это узнать – подписаться по фильтру # и ждать какое-то время. Но это не даст 100% результат.
Вопрос: Хранятся ли сообщения на брокере?
Ответ: Только временно. Обычные (не retained) сообщения хранятся до момента их отправки всем подписчикам. Если у сообщения установлен бит retained, то храниться только самое последнее сообщение.
Ссылки
1. MQTT Specifications
2. Хабр :: Протокол MQTT: концептуальное погружение
Пожалуйста, оцените статью:
-= Каталог статей (по разделам) =- -= Архив статей (подряд) =-