Python MQTT 異步框架 —— HBMQTT

什麼是異步

CPU 的速度遠遠快於磁盤、網絡等 IO 操作,而在一個線程中,無論 CPU 執行得再快,遇到 IO 操作時,都得停下來等待讀寫完成,這無疑浪費了許多時間。

爲了解決這個問題,Python 加入了異步 IO 的特性。在 Python 3.4 中,正式將 asyncio 納入標準庫中,並在 Python 3.5 中,加入了 async/await 關鍵字。用戶可以很輕鬆的使用在函數前加入 async 關鍵字,使函數變成異步函數。

在 Python 的 MQTT 客戶端庫中,HBMQTT 是最早支持異步 IO 的 Python MQTT 庫。

HBMQTT 庫

HBMQTT 是基於 Python 編寫的開源庫,實現了 MQTT 3.1.1 協議,特性如下:

  • 支持 QoS 0, QoS 1 以及 QoS 2 消息
  • 客戶端自動重連
  • 支持 TCP 和 WebSocket
  • 支持 SSL
  • 支持插件系統

下面我們將演示如何使用 Python MQTT 異步框架 - HBMQTT,輕鬆實現一個具備 MQTT 發佈、訂閱功能的異步 Demo。

項目初始化

確定 Python 版本

本項目使用 Python 3.6 進行開發測試,讀者可用如下命令確認 Python 的版本。

因爲需要使用 async 關鍵字,需要確保 Python 版本不低於 Python 3.5

➜  ~ python3 --version
Python 3.6.7

使用 Pip 安裝 HBMQTT 庫

Pip 是 Python 的包管理工具,該工具提供了對 Python 包的查找、下載、安裝和卸載功能。

pip3 install -i https://pypi.doubanio.com/simple hbmqtt

連接 MQTT 服務器

本文將使用 EMQ X 提供的免費公共 MQTT 服務器,該服務基於 EMQ X 的MQTT 物聯網雲平臺創建。服務器接入信息如下:

  • Broker: broker.emqx.io
  • TCP Port: 1883
  • Websocket Port: 8083

首先,導入 MQTT 客戶端庫。

from hbmqtt.client import MQTTClient

client = MQTTClient()
# 連接服務器
client.connect('mqtt://broker.emqx.io/')
# 斷開連接
client.disconnect()

異步寫法如下:

async def test_pub():
    client = MQTTClient()
    await client.connect('mqtt://broker.emqx.io/')
    await client.disconnect()

發佈消息

發佈消息函數爲 MQTTClient 類的 publish 函數。

client = MQTTClient()
# 函數的三個參數分別爲主題、消息內容、QoS
client.publish('a/b', b'TEST MESSAGE WITH QOS_0', qos=QOS_0)

異步寫法如下:

async def test_pub():
    client = MQTTClient()
    await Client.connect('mqtt://broker.emqx.io/')
    await asyncio.gather(
        client.publish('a/b', b'TEST MESSAGE WITH QOS_0', qos=QOS_0),
        client.publish('a/b', b'TEST MESSAGE WITH QOS_1', qos=QOS_1),
        client.publish('a/b', b'TEST MESSAGE WITH QOS_2', qos=QOS_2)
    )
    logging.info("messages published")
    await Client.disconnect()

在這段代碼中,我們將三個發送消息函數放進 asyncio 的任務列表裏,它們將會依次被運行。當所有任務都完成後,斷開連接。

訂閱消息

定閱消息函數爲 MQTTClient 類中的 subscribe 函數。

client = MQTTClient()
# 訂閱
client.subscribe([
  ('topic/0', QOS_0),
  ('topic/1', QOS_1),  
])
# 取消訂閱
client.unsubscribe([
  ('topic/0', QOS_0),
]

異步寫法如下:

async def test_sub():
    client = MQTTClient()
    await client.connect('mqtt://broker.emqx.io/')
    await client.subscribe([
            ('a/b', QOS_1),
         ])
    for i in range(0, 10):
        message = await client.deliver_message()
        packet = message.publish_packet
        print(f"{i}:  {packet.variable_header.topic_name} => {packet.payload.data}")
    await client.disconnect()

在這段代碼中,我們在接收消息時設置了 await 等待,當代碼執行到如下位置時,CPU 會先去執行其它任務,直到有消息傳達,再將其打印。

message = await client.deliver_message()

最終,程序會等待 10 次消息接收,然後關閉連接。

完整代碼

消息訂閱代碼

# sub.py
# python 3.6+

import asyncio
import logging

from hbmqtt.client import MQTTClient
from hbmqtt.mqtt.constants import QOS_1


async def test_sub():
    client = MQTTClient()
    await client.connect('mqtt://broker.emqx.io/')
    await client.subscribe([
        ('a/b', QOS_1),
    ])
    for i in range(0, 10):
        message = await client.deliver_message()
        packet = message.publish_packet
        print(f"{i}:  {packet.variable_header.topic_name} => {packet.payload.data}")
    await client.disconnect()


if __name__ == '__main__':
    formatter = "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
    logging.basicConfig(level=logging.INFO, format=formatter)
    asyncio.run(test_sub())

消息發佈代碼

# pub.py
# python 3.6+

import asyncio
import logging

from hbmqtt.client import MQTTClient
from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2


async def test_pub():
    client = MQTTClient()

    await client.connect('mqtt://broker.emqx.io/')
    await asyncio.gather(
        client.publish('a/b', b'TEST MESSAGE WITH QOS_0', qos=QOS_0),
        client.publish('a/b', b'TEST MESSAGE WITH QOS_1', qos=QOS_1),
        client.publish('a/b', b'TEST MESSAGE WITH QOS_2', qos=QOS_2)
    )
    logging.info("messages published")
    await client.disconnect()


if __name__ == '__main__':
    formatter = "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
    logging.basicConfig(level=logging.INFO, format=formatter)
    asyncio.run(test_pub())

測試

消息發佈

運行 MQTT 消息發佈代碼,我們將看到客戶端連接成功,並且成功發佈消息。

如下爲 MQTT X 客戶端成功接收到 HBMQTT 客戶端發佈的消息:

消息訂閱

運行 MQTT 消息訂閱代碼,我們將看到客戶端連接成功,此時客戶端正在等待消息進入

使用 MQTT X 客戶端連接 broker.emqx.io,然後向主題 a/b 發送 10 次消息

回到終端,我們看到客戶端接收並打印消息,並且在收到 10 條消息後,主動退出了程序。

總結

至此,我們完成了 HBMQTT 庫連接到公共 MQTT 服務器,並實現了測試客戶端與 MQTT 服務器的連接、消息發佈和訂閱。通過使用 Python 異步 IO 執行消息的發送接收,可以幫助我們實現更加高效的 MQTT 客戶端。

接下來我們將會陸續發佈更多關於物聯網開發及 Python 的相關文章,敬請關注。

版權聲明: 本文爲 EMQ 原創,轉載請註明出處。

原文鏈接:https://www.emqx.cn/blog/python-async-mqtt-client-hbmqtt

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章