什麼是異步
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