Eclipse Paho™MQTT Python客户端

Eclipse Paho™MQTT Python客户端

这里可以找到完整的留档

警告重大变更-2.0版包含重大变更;请参阅发行说明迁移详细信息

本文档描述了Eclipse PahoMQTT Python客户端库的源代码,该库实现了MQTT协议的5.0、3.1.1和3.1版本。

此代码提供了一个客户端类,使应用程序能够连接到MQTT代理以发布消息、订阅主题和接收已发布的消息。它还提供了一些帮助函数,使向MQTT服务器发布一次性消息变得非常简单。

它支持Python 3.7+。

MQTT协议是一种机器对机器(M2M)/"物联网"连接协议。作为一种极其轻量级的发布/订阅消息传输方式,它在需要较小代码占用和/或网络带宽至关重要的远程位置连接中非常有用。

Paho是一个Eclipse基金会项目。

内容

安装

最新版本的稳定版本可以在Python软件包索引(PyPi)中找到,并可以使用以下命令进行安装:

pip install paho-mqtt

或 使用 virtualenv

virtualenv paho-mqtt
source paho-mqtt/bin/activate
pip install paho-mqtt

要获取完整的代码,包括示例和测试,您可以克隆git仓库:

git clone https://github.com/eclipse/paho.mqtt.python

一旦您有了代码,也可以从您的仓库中安装它:

cd paho.mqtt.python
pip install -e .

要执行所有测试(包括MQTT v5测试),您还需要在paho.mqtt.python文件夹中克隆paho.mqtt.testing:

git clone https://github.com/eclipse/paho.mqtt.testing.git
cd paho.mqtt.testing
git checkout a4dc694010217b291ee78ee13a6d1db812f9babd

已知限制

以下是已知的未实现的MQTT功能。

当clean_session为False时,会话只存储在内存中,不会持久化。这意味着当客户端重新启动(不仅仅是重新连接,通常是因为程序被重新启动)时,会话会丢失。这可能导致消息丢失。

客户端会话的以下部分丢失:

  • QoS 2从服务器接收但尚未完全确认的消息。

    由于客户端将盲目确认任何PUBCOMP(QoS 2事务的最后一条消息),它不会挂起但会丢失此QoS 2消息。

  • 已发送到服务器但尚未完全确认的QoS 1和QoS 2消息。

    这意味着传递给publish()的消息可能会丢失。这可以通过确保传递给publish()的所有消息都有一个相应的on_publish()调用或使用wait_for_publish来缓解。

    这也意味着代理可能已经在会话中拥有QoS2消息。由于客户端以空会话开始,它不知道这一点并将重用mid。这个问题还没有修复。

此外,当clean_session为True时,此库将在网络重新连接时重新发布QoS > 0消息。这意味着QoS > 0消息不会丢失。但标准规定我们应该丢弃任何已经发送了 publish 数据包的消息。我们的选择意味着我们不符合标准,并且QoS 2可能会被接收两次。

如果您需要QoS 2的唯一一次交付保证,应将clean_session设置为False。

使用和API

详细的API留档可在线获取,也可以从docs/构建,示例可在示例目录中找到。

该软件包提供了两个模块,一个全面的Client和一些用于简单发布或订阅的辅助工具。

入门指南

这里有一个简单的例子,它会订阅代理$SYS主题树并打印出结果消息:

import paho.mqtt.client as mqtt

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, reason_code, properties):
    print(f"Connected with result code {reason_code}")
    # Subscribing in on_connect() means that if we lose the connection and
    # reconnect then subscriptions will be renewed.
    client.subscribe("$SYS/#")

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))

mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqttc.on_connect = on_connect
mqttc.on_message = on_message

mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)

# Blocking call that processes network traffic, dispatches callbacks and
# handles reconnecting.
# Other loop*() functions are available that give a threaded interface and a
# manual interface.
mqttc.loop_forever()

客户端

您可以将客户端类作为实例、在类中使用或通过继承来使用。一般使用流程如下:

  • 创建客户端实例
  • 使用connect*()函数连接到代理服务器
  • 调用loop*()函数以保持与代理服务器的网络流量
  • 使用subscribe()订阅主题并接收消息
  • 使用publish()将消息发布到代理
  • 使用disconnect()断开与代理的连接

回调将被调用,以便应用程序按需处理事件。这些回调在下面有描述。

网络循环

这些函数是客户端的驱动力。如果不调用它们,传入的网络数据将不会被处理,传出的网络数据也不会被发送。管理网络循环有四种选择。其中三种在这里描述,第四种在“外部事件循环支持”下面描述。不要混合使用不同的循环函数。

loop_start/loop_stop
mqttc.loop_start()

while True:
    temperature = sensor.blocking_read()
    mqttc.publish("paho/temperature", temperature)

mqttc.loop_stop()

这些函数实现了一个线程化的网络循环接口。在connect*()函数之前或之后调用一次loop_start(),会在后台运行一个线程来自动调用loop()。这释放了主线程用于其他可能阻塞的工作。这个调用也处理与代理服务器的重新连接。调用loop_stop()来停止后台线程。如果你调用disconnect(),循环也会被停止。

loop_forever()
mqttc.loop_forever(retry_first_connection=False)

这是一个阻塞形式的网络循环,直到客户端调用disconnect()才会返回。它会自动处理重新连接。

除了在使用connect_async时的第一次连接尝试外,使用retry_first_connection=True使其重试第一次连接。

警告:这可能导致客户端不断连接到不存在的主机而不失败的情况。

循环()
run = True
while run:
    rc = mqttc.loop(timeout=1.0)
    if rc != 0:
        # need to handle error, possible reconnecting or stopping the application

定期调用以处理网络事件。此调用在select()中等待,直到网络套接字可用于读写(如果适用),然后处理传入/传出的数据。此函数最多阻塞timeout秒。timeout不得超过客户端的keepalive值,否则您的客户端将定期被代理服务器断开连接。

使用这种类型的循环,需要您处理重新连接策略。

回调

与paho-mqtt交互的接口包括一些由库在发生某些事件时调用的回调函数。

回调是您代码中定义的函数,用于实现对那些事件所需的操作。这可以是简单地打印接收到的消息或更复杂的行为。

回调API是版本化的,所选版本是您提供给Client构造器的CallbackAPIVersion。目前支持两个版本:

  • CallbackAPIVersion.VERSION1:它是在paho-mqtt 2.0之前使用的历史性版本。这是在引入CallbackAPIVersion之前使用的API。这个版本已被弃用,将在paho-mqtt 3.0中移除。 
  • CallbackAPIVersion.VERSION2:这个版本在MQTT 3.x和MQTT 5.x协议之间更加一致。由于在可用时总是提供reason code和properties,因此它对于MQTT 5.x用户来说也更加易用。建议所有用户升级到这个版本。强烈推荐给MQTT 5.x用户使用。

以下是存在的回调:

  • on_connect(): 当从代理收到CONNACK时调用。该调用可能是针对一个被拒绝的连接,检查reason_code以查看连接是否成功或被拒绝。
  • on_connect_fail(): 当TCP连接未能建立时,由loop_forever()和loop_start()调用。当直接使用connect()或reconnect()时不会调用此回调。只有在loop_start()和loop_forever()进行的自动(重新)连接后才会调用此回调。
  • on_disconnect(): 当连接关闭时调用。
  • on_message(): 当从代理收到MQTT消息时调用。
  • on_publish(): 当向代理发送MQTT消息时调用。根据QoS级别,回调在不同时刻被调用:
    • 对于QoS == 0, 一旦消息通过网络发送就立即调用。这可能是在相应的publish()返回之前。
    • 对于QoS == 1, 当从代理接收到相应的PUBACK时调用。
    • 对于QoS == 2, 当从代理接收到相应的PUBCOMP时调用。
  • on_subscribe(): 当从代理接收到SUBACK时调用。
  • on_unsubscribe(): 当从代理接收到UNSUBACK时调用。
  • on_log(): 当库记录一条消息时调用。
  • on_socket_open, on_socket_close, on_socket_register_write, on_socket_unregister_write: 用于外部循环支持的回调。请参阅下面的详细信息。

有关每个回调的签名,请参阅在线留档

订阅者示例
import paho.mqtt.client as mqtt

def on_subscribe(client, userdata, mid, reason_code_list, properties):
    # Since we subscribed only for a single channel, reason_code_list contains
    # a single entry
    if reason_code_list[0].is_failure:
        print(f"Broker rejected you subscription: {reason_code_list[0]}")
    else:
        print(f"Broker granted the following QoS: {reason_code_list[0].value}")

def on_unsubscribe(client, userdata, mid, reason_code_list, properties):
    # Be careful, the reason_code_list is only present in MQTTv5.
    # In MQTTv3 it will always be empty
    if len(reason_code_list) == 0 or not reason_code_list[0].is_failure:
        print("unsubscribe succeeded (if SUBACK is received in MQTTv3 it success)")
    else:
        print(f"Broker replied with failure: {reason_code_list[0]}")
    client.disconnect()

def on_message(client, userdata, message):
    # userdata is the structure we choose to provide, here it's a list()
    userdata.append(message.payload)
    # We only want to process 10 messages
    if len(userdata) >= 10:
        client.unsubscribe("$SYS/#")

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Failed to connect: {reason_code}. loop_forever() will retry connection")
    else:
        # we should always subscribe from on_connect callback to be sure
        # our subscribed is persisted across reconnections.
        client.subscribe("$SYS/#")

mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqttc.on_connect = on_connect
mqttc.on_message = on_message
mqttc.on_subscribe = on_subscribe
mqttc.on_unsubscribe = on_unsubscribe

mqttc.user_data_set([])
mqttc.connect("mqtt.eclipseprojects.io")
mqttc.loop_forever()
print(f"Received the following message: {mqttc.user_data_get()}")
发布者示例
import time
import paho.mqtt.client as mqtt

def on_publish(client, userdata, mid, reason_code, properties):
    # reason_code and properties will only be present in MQTTv5. It's always unset in MQTTv3
    try:
        userdata.remove(mid)
    except KeyError:
        print("on_publish() is called with a mid not present in unacked_publish")
        print("This is due to an unavoidable race-condition:")
        print("* publish() return the mid of the message sent.")
        print("* mid from publish() is added to unacked_publish by the main thread")
        print("* on_publish() is called by the loop_start thread")
        print("While unlikely (because on_publish() will be called after a network round-trip),")
        print(" this is a race-condition that COULD happen")
        print("")
        print("The best solution to avoid race-condition is using the msg_info from publish()")
        print("We could also try using a list of acknowledged mid rather than removing from pending list,")
        print("but remember that mid could be re-used !")

unacked_publish = set()
mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqttc.on_publish = on_publish

mqttc.user_data_set(unacked_publish)
mqttc.connect("mqtt.eclipseprojects.io")
mqttc.loop_start()

# Our application produce some messages
msg_info = mqttc.publish("paho/test/topic", "my message", qos=1)
unacked_publish.add(msg_info.mid)

msg_info2 = mqttc.publish("paho/test/topic", "my message2", qos=1)
unacked_publish.add(msg_info2.mid)

# Wait for all message to be published
while len(unacked_publish):
    time.sleep(0.1)

# Due to race-condition described above, the following way to wait for all publish is safer
msg_info.wait_for_publish()
msg_info2.wait_for_publish()

mqttc.disconnect()
mqttc.loop_stop()
日志

客户端会发出一些在故障排除期间可能有用的日志消息。启用日志的最简单方式是调用enable_logger()函数。可以提供自定义的日志记录器,或者使用默认的日志记录器。

示例:

import logging
import paho.mqtt.client as mqtt

logging.basicConfig(level=logging.DEBUG)

mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqttc.enable_logger()

mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
mqttc.loop_start()

# Do additional action needed, publish, subscribe, ...
[...]

也可以定义一个on_log回调,它将接收所有日志消息的副本。示例:

import paho.mqtt.client as mqtt

def on_log(client, userdata, paho_log_level, messages):
    if paho_log_level == mqtt.LogLevel.MQTT_LOG_ERR:
        print(message)

mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqttc.on_log = on_log

mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
mqttc.loop_start()

# Do additional action needed, publish, subscribe, ...
[...]

与Paho日志级别的对应关系如下:

Paho日志
MQTT_LOG_ERRlogging.ERROR
MQTT_LOG_WARNINGlogging.WARNING
MQTT_LOG_NOTICElogging.INFO (没有直接等效)
MQTT_LOG_INFOlogging.INFO
MQTT_LOG_DEBUGlogging.DEBUG
外部事件循环支持

为了支持像asyncio这样的其他网络循环(见示例),该库暴露了一些方法和支持用例的回调。

存在以下循环方法:

  • loop_read:应在套接字准备好读取时调用。
  • loop_write:应该在套接字准备好写入并且库想要写入数据时调用。
  • loop_misc:应该每隔几秒钟调用一次来处理消息重试和ping。

在伪代码中,它给出了以下内容:

while run:
    if need_read:
        mqttc.loop_read()
    if need_write:
        mqttc.loop_write()
    mqttc.loop_misc()

    if not need_read and not need_write:
        # But don't wait more than few seconds, loop_misc() need to be called regularly
        wait_for_change_in_need_read_or_write()
    updated_need_read_and_write()

棘手的部分在于实现对need_read / need_write的更新和等待条件更改。为了支持此功能,存在以下方法:

  • socket():当TCP连接打开时,返回套接字对象。此调用对于基于select的循环特别有用。参见examples/loop_select.py。

  • want_write():如果有待写入的数据,则返回true。这接近于上述伪代码中的need_writew,但您也应该检查套接字是否已准备好写入。

  • 回调on_socket_*

    • on_socket_open:打开套接字时调用。
    • on_socket_close:在套接字即将关闭时调用。
    • on_socket_register_write:当客户端想要在套接字上写入数据时调用
    • on_socket_unregister_write:当套接字上没有更多数据要写入时调用。

    回调对于注册或注销套接字的事件循环特别有用 阅读+写作。参见examples/loop_asyncio.py

回调在以下顺序中始终被调用:

  • on_socket_open
  • 零次或多次:
    • on_socket_register_write
    • on_socket_unregister_write
  • on_socket_close
全局辅助函数

客户端模块还提供了一些全局帮助函数。

topic_matches_sub(sub, topic)可以用来检查主题是否与订阅匹配。

例如:

主题foo/bar将匹配订阅的foo/#+/bar

主题non/matching将不匹配订阅non/+/+

发布

该模块提供一些辅助函数,以便一次性直接发布消息。换句话说,它们适用于您有单个/多个消息想要发布到代理服务器的情况,然后断开连接,无需做其他操作。

提供的两个函数是single()和multiple()。

这两个函数都包括对MQTT v5.0的支持,但当前不允许设置连接或发送消息时的任何属性。

Single

向代理服务器发布单个消息,然后干净地断开连接。

示例:

import paho.mqtt.publish as publish

publish.single("paho/test/topic", "payload", hostname="mqtt.eclipseprojects.io")
Multiple

向代理服务器发布多个消息,然后干净地断开连接。

示例:

from paho.mqtt.enums import MQTTProtocolVersion
import paho.mqtt.publish as publish

msgs = [{'topic':"paho/test/topic", 'payload':"multiple 1"},
    ("paho/test/topic", "multiple 2", 0, False)]
publish.multiple(msgs, hostname="mqtt.eclipseprojects.io", protocol=MQTTProtocolVersion.MQTTv5)

订阅

这个模块提供了一些辅助函数,以方便直接订阅和处理消息。

提供的两个函数是simple()和callback()。

这两个函数都支持MQTT v5.0,但目前不支持设置连接或订阅时的任何属性。

简单订阅

订阅一组主题并返回接收到的消息。这是一个阻塞函数。

示例:

import paho.mqtt.subscribe as subscribe

msg = subscribe.simple("paho/test/topic", hostname="mqtt.eclipseprojects.io")
print("%s %s" % (msg.topic, msg.payload))
使用回调订阅

订阅一组主题并使用用户提供的回调处理接收到的消息。

示例:

import paho.mqtt.subscribe as subscribe

def on_message_print(client, userdata, message):
    print("%s %s" % (message.topic, message.payload))
    userdata["message_count"] += 1
    if userdata["message_count"] >= 5:
        # it's possible to stop the program by disconnecting
        client.disconnect()

subscribe.callback(on_message_print, "paho/test/topic", hostname="mqtt.eclipseprojects.io", userdata={"message_count": 0})

报告错误

请在Issues · eclipse/paho.mqtt.python · GitHub报告问题跟踪器中的错误。

更多信息

Paho客户端的讨论发生在Eclipse paho-dev邮件列表中

关于MQTT协议本身(不是这个库)的一般问题在MQTT Google Group中讨论。

通过MQTT社区网站可以获得更多信息。

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值