使用paho-mqtt 模块
# 安装paho-mqtt包
pip3 install paho-mqtt
paho-mqtt主要由三个模块组成:Client模块、Publish模块和Subscribe模块。Publish模块和Subscribe模块使用相对较少。
Client的基本使用流程
Client的基本使用流程如下:
创建客户端实例
使用 connect*() 函数之一连接到代理
调用 loop*() 函数之一来维护与代理的网络流量
使用 subscribe() 订阅主题并接收消息
使用 publish() 将消息发布到代理
使用 disconnect() 断开与代理的连接
Client的构建与重置
# 构建
Client(client_id="", clean_session=True, userdata=None, protocol=MQTTv311, transport="tcp")
# 重置
reinitialise(client_id="", clean_session=True, userdata=None)
参数说明
client_id:
连接到代理时使用的唯一客户端 ID 字符串。如果 client_id 为零长度或 None ,则将随机生成一个。在这种情况下,clean_session 参数必须为 True。
clean_session:
确定客户端类型的布尔值。如果为 True,代理将在断开连接时删除有关此客户端的所有信息。如果为 False,则客户端是持久客户端,并且在客户端断开连接时将保留订阅信息和排队消息。
注意,客户端永远不会在断开连接时丢弃自己的传出消息。调用 connect() 或 reconnect() 将导致消息被重新发送。使用 reinitialise() 将客户端重置为其原始状态。
userdata:
作为 userdata 参数传递给回调的任何类型的用户定义数据。稍后可能会使用 user_data_set() 函数对其进行更新。
protocol:
用于此客户端的 MQTT 协议版本。可以是 MQTTv31 或 MQTTv311
transport:
设置为“websockets”以通过 WebSockets 发送 MQTT。保留默认值“tcp”以使用原始 TCP。
# 构建一个Client
mqttc = mqtt.Client()
# 重置一个Client
mqttc.reinitialise()
# 连接 MQTT
mqttc.connect(host=IP, port=1883, keepalive=60, bind_address="")
# 重连
mqttc.reconnect()
# 断开连接
mqttc.disconnect()
PS: MQTT的本质是一个用以维护客户端与代理之间的长连接的、并且是低消耗的协议。所以,无论对于代理还是对于客户端,他们都需要清楚地知道二者之间的连接是否断开、而且是正常断开还是非正常断开(正常断开(客户端使用disconnect方法)则不需特别操作;非正常断开情况下,客户端需要尝试重新连接,而代理则需要发送遗嘱)。这一点贯穿与MQTT的协议设计与“连接”这一部分的内容。
参数说明
host:
远程代理的主机名或 IP 地址
port:
要连接的服务器主机的网络端口。 默认为 1883。请注意,基于 SSL/TLS 的 MQTT 的默认端口为 8883,因此如果您使用 tls_set() 或 tls_set_context(),则可能需要手动提供端口
keepalive:
与代理通信之间允许的最长间隔(以秒为单位)。 如果没有其他消息正在交换,这将控制客户端向代理发送 ping 消息的速率。
需要指出,MQTT协议规定,在 1.5倍的keepalive时间内,如果代理没有收到来自客户端的任何数据包,那么代理将认为它和这个客户端之间的连接已经断开;而如果客户端没有收到来自 代理的任何数据包,那么这个客户端会认为它和代理之间的连接已经断开。为维持正常的连接,如果代理与客户端之间没有其他数据传输,客户端会每隔keepalive时间向代理发送一次ping消息(由loop()来维护)。keepalive的缺省时间是60s。
bind_address:
假设存在多个接口,要将此客户端绑定到的本地网络接口的 IP 地址
网络回路控制
网络回路控制是客户端对传入与发出数据进行控制的背后驱动力,同时也根据客户端的keepalive设置发送ping消息(心跳报文),以刷新连接。如果不调用网络回路方法,则客户端不会处理传入的网络数据,并且可能无法及时发送传出的网络数据。其作用可由下图表示:
# 相关方法
运行这几个loop 方法后,回调函数就会被执行
loop(timeout=1.0, max_packets=1)
loop_start()
loop_stop(force=False)
loop_forever(timeout=1.0, max_packets=1, retry_first_connection=False)
loop_forever
# loop_forever()
loop_forever()持续阻塞进程(不需外部循环),连接将维持到客户端调用disconnect()。timeout 和 max_packets 参数已过时,应保持未设置。retry_first_connection=True 使其重试第一次连接。警告:这可能会导致客户端不断连接到不存在的主机而不提示失败的情况。由于主线程被阻塞,其他操作无法在主线程执行,包括disconnect()在内的其他操作都需要通过回调函数来执行。
class MQTT(object):
mqtt_broker = '10.0.0.113'
mqtt_port = 1883
# sub_topic_status = "agv/publicMsg/status/#" # 该主题可监听车的状态,如急停
sub_topic_task_status = "agv/publicMsg/status/#"
# client_id = f'python-mqtt-{random.randint(0, 1000)}'
down_message = {'start': (None, None), 'middle': (None, None), 'end': (None, None)}
def __init__(self):
self.run_sub()
def connect_mqtt_sub(self):
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker sub!") # 可用logger替换
else:
print("Failed to connect, return code %d\n", rc) # 可用logger替换
client = mqtt_client.Client()
client.on_connect = on_connect
client.connect(self.mqtt_broker, self.mqtt_port)
return client
# 7、监听mqtt的消息,将到达不同工位点的agv信息存入到相对应的队列中
def sub(self, client: mqtt_client):
'''同一个主题调用一次'''
client.subscribe(self.sub_topic_task_status)
client.message_callback_add(self.sub_topic_task_status, self.handle_down_line)
def handle_down_line(self, mosq, obj, msg):
receive_data = msg.payload.decode() # 接收sub传来的信息
action_data = json.loads(receive_data) # 转换成json
print('here', action_data)
def run_sub(self):
client = self.connect_mqtt_sub()
self.sub(client)
client.loop_forever() # 这是网络循环的阻塞形式,直到客户端调用disconnect()时才会返回。它会自动处理重新连接。
loop()
# loop() 方法
一是循环调用loop()阻塞进程:timeout参数定义了loop()阻塞进程的超时时间,
而max_packets 参数已过时,应保持未设置。timeout 不得超过客户端的 keepalive 值,
否则客户端将被代理定期断开连接。
class MQTT(object):
mqtt_broker = '10.0.0.113'
mqtt_port = 1883
# sub_topic_status = "agv/publicMsg/status/#" # 该主题可监听车的状态,如急停
sub_topic_task_status = "agv/publicMsg/status/#"
# client_id = f'python-mqtt-{random.randint(0, 1000)}'
down_message = {'start': (None, None), 'middle': (None, None), 'end': (None, None)}
def __init__(self):
self.run_sub()
def connect_mqtt_sub(self):
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker sub!") # 可用logger替换
else:
print("Failed to connect, return code %d\n", rc) # 可用logger替换
client = mqtt_client.Client()
client.on_connect = on_connect
client.connect(self.mqtt_broker, self.mqtt_port)
return client
# 7、监听mqtt的消息,将到达不同工位点的agv信息存入到相对应的队列中
def sub(self, client: mqtt_client):
'''同一个主题调用一次'''
client.subscribe(self.sub_topic_task_status)
client.message_callback_add(self.sub_topic_task_status, self.handle_down_line)
def handle_down_line(self, mosq, obj, msg):
receive_data = msg.payload.decode() # 接收sub传来的信息
action_data = json.loads(receive_data) # 转换成json
print('here', action_data)
def run_sub(self):
client = self.connect_mqtt_sub()
self.sub(client)
while True:
time.sleep(1)
client.loop(timeout=1.0)
loop_start() / loop_stop()
# loop_start() / loop_stop()
loop_start()和loop_stop()创建和停止后台线程,以自动调用loop()。这种方式释放了主线程。 loop_start()可以在connect*() 之前或之后调用。 此调用还处理与代理的重新连接。 调用 loop_stop() 停止后台线程。 force 参数当前被忽略。
class MQTT(object):
mqtt_broker = '10.0.0.113'
mqtt_port = 1883
# sub_topic_status = "agv/publicMsg/status/#" # 该主题可监听车的状态,如急停
sub_topic_task_status = "agv/publicMsg/status/#"
# client_id = f'python-mqtt-{random.randint(0, 1000)}'
down_message = {'start': (None, None), 'middle': (None, None), 'end': (None, None)}
def __init__(self):
self.run_sub()
def connect_mqtt_sub(self):
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker sub!") # 可用logger替换
else:
print("Failed to connect, return code %d\n", rc) # 可用logger替换
client = mqtt_client.Client()
client.on_connect = on_connect
client.connect(self.mqtt_broker, self.mqtt_port)
return client
# 7、监听mqtt的消息,将到达不同工位点的agv信息存入到相对应的队列中
def sub(self, client: mqtt_client):
'''同一个主题调用一次'''
client.subscribe(self.sub_topic_task_status)
client.message_callback_add(self.sub_topic_task_status, self.handle_down_line)
def handle_down_line(self, mosq, obj, msg):
receive_data = msg.payload.decode() # 接收sub传来的信息
action_data = json.loads(receive_data) # 转换成json
print('here', action_data)
def run_sub(self):
client = self.connect_mqtt_sub()
self.sub(client)
client.loop_start() # 开启
n = 1
while True:
time.sleep(1)
n += 1
if n >= 5:
client.loop_stop() # 关闭
订阅/取消订阅
使客户端订阅到一个或多个主题,或从相应主题退订。
mqttc.subscribe(("my/topic", 1))
mqttc.subscribe([("my/topic", 0), ("another/topic", 2)])
mqttc.unsubscribe("my/topic")
mqttc.unsubscribe(["my/topic", "another/topic"])
发布
发布会使得消息被发送到代理,然后再由代理发送到订阅匹配主题的任何客户端。
mqttc.publish(topic="my/topic", payload=None, qos=0, retain=True)
MQTT中的QoS等级
MQTT设计了一套保证消息稳定传输的机制,包括消息应答、存储和重传。在这套机制下,提供了三种不同层次QoS(Quality of Service):
QoS0,At most once,至多一次;
QoS1,At least once,至少一次;
QoS2,Exactly once,确保只有一次。
QoS 是消息的发送方(Sender)和接受方(Receiver)之间达成的一个协议:
QoS0 代表,Sender 发送的一条消息,Receiver 最多能收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,也就算了;
QoS1 代表,Sender 发送的一条消息,Receiver 至少能收到一次,也就是说 Sender 向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,但是因为重传的原因,Receiver 有可能会收到重复的消息;
QoS2 代表,Sender 发送的一条消息,Receiver 确保能收到而且只收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,同时保证 Receiver 不会因为消息重传而收到重复的消息。
类的封装
import paho.mqtt.client as mqtt_client
topic = "agv/publicMsg/status/#"
class MqttRoad(object):
def __init__(self, mqtt_host, mqtt_port, mqtt_keepalive):
super(MqttRoad, self).__init__()
client = mqtt_client.Client()
client.on_connect = self.on_connect
client.on_message = self.on_message
client.on_publish = self.on_publish
client.on_subscribe = self.on_subscribe
client.connect(mqtt_host, mqtt_port, mqtt_keepalive) # 600为keepalive的时间间隔
client.subscribe(topic)
client.loop_forever() # 保持连接
def on_connect(self, client, userdata, flags, rc):
print("Connected with result code: " + str(rc))
# 订阅
client.subscribe("mqtt11")
def on_message(self, client, userdata, msg):
print("on_message topic:" + msg.topic + " message:" + str(msg.payload.decode('utf-8')))
# 订阅回调
def on_subscribe(self, client, userdata, mid, granted_qos):
print("On Subscribed: qos = %d" % granted_qos)
pass
# 取消订阅回调
def on_unsubscribe(self, client, userdata, mid):
# print("取消订阅")
print("On unSubscribed: qos = %d" % mid)
pass
# 发布消息回调
def on_publish(self, client, userdata, mid):
# print("发布消息")
print("On onPublish: qos = %d" % mid)
pass
# 断开链接回调
def on_disconnect(self, client, userdata, rc):
# print("断开链接")
print("Unexpected disconnection rc = " + str(rc))
pass
if __name__ == '__main__':
MqttRoad("10.0.0.113", 1883, 600)
参考链接
参考链接
以上内容的参考链接
以上内容的参考链接
Q0s说明
参考链接
参考链接
参考链接
参考链接
参考链接
Python paho-mqtt消息队列