初识mqtt
MQTT 是一种基于 发布/订阅(publish/subscribe) 模式的“轻量级”通讯协议,该协议建立在TCP/IP协议上。MQTT最大优点在于,可以以极少的代码和有限的宽带为远程连接设备提供实时可靠的消息服务。作为一种低开销、低宽带占用的即时通讯协议,使其在物联网、小型设备、移动开发等方面有比较广泛的应用。
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe),形式如下:
设计规范
- 精简,不添加可有可无的功能。
- 发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递。
- 允许用户动态创建主题,零运维成本。
- 把输入量降低到最低以提高传输效率。
- 把低宽带、高延迟、不稳定的网络因素考虑在内。
- 支持连续的会话控制。
- 理解客户端计算能力可能很低。
- 提供服务质量管理
- 假设数据不可知不强求传输数据的类型与格式保持灵活性。
主要特性
- 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
- 对负载内容屏蔽的消息传输
- 使用tcp/ip提供网络连接
- 有三种消息发布质量
- 至多一次:消息发布完全依赖底层TCP/IP网络。会发生消息丢失或重复
- 至少一次:确保消息到达,但消息重复可能会发生。
- 只有一次:确保消息到达一次
- 小型传输,开销很小(固定长度的头部字节是2字节),协议交换最小化,以降低网络流量。
- 使用Last Will和Testament特性通知有关各方客户端异常中断的机制
- Last Will:即遗言机制,用于通知同一主题下的去他设备发送遗言的设备已经断开连接。
- Testament:遗嘱机制,功能类似Last Will
MQTT协议原理
MQTT协议实现方式
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中消息的发布者和订阅者都是客户端,消息的代理是服务器,消息发布者可以同时是订阅者。 MQTT传输的消息分为:
- Topic(主题):可以理解为接头暗号,订阅者订阅后(即口号对上后),就会收到该主题的消息内容(payload)
- payload(负载):可以理解为消息内容,是指订阅者具体需要的内容/
网路传输与应用消息
MQTT会构建镀层网络传输:他将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(Qos)和主题名(Topic)相关联。
MQTT客户端
一个使用MQTT协议的应用程序或者设备,他总是建立到服务器的网络连接。客户端可以:
- 发布其他客户端可能会订阅的信息
- 订阅其他客户端信息
- 退订或删除应用程序的消息
- 断开与服务器连接
MQTT服务器
MQTT服务器以称为消息代理(Broker),可以是一个程序或一台设备他是位于消息发布者和订阅者之间,它可以:
- 接受来自客户端的连接
- 接受客户端发布的应用信息
- 处理来自客户端的订阅退订请求
- 向订阅的客户端转发应用程序消息
MQTT协议中的订阅、主题、会话
订阅(Subscription)
订阅包含主题筛选器(Topic Filter)和最大服务质量(Qos)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每个会话中的每个订阅都有一个不同的主题筛选器。
会话(Session)
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在与一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
主题名(Topic Name)
连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅者所匹配标签的每个客户端。
主题筛选器(Topic Filter)
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。
负载(Payload)
消息订阅者所具体接受的内容。
MQTT协议中的方法
MQTT协议中定义了一些方法(也被称为动作),用于来表示对确定资源所进行操作,这个资源可以代表预先存在的数据或动态生成数据,这取决于服务器的实现。通常来说,资源指服务器上的文件或输出。主要方法有:
- Connect:与服务器建立连接,连接成功后有个回调函数(如下是python代码)
# 链接mqtt服务器函数
def on_mqtt_connect(self):
self.client.connect(self.MQTTHOST, self.MQTTPORT, 60)
# 开始监听
self.client.loop_start()
# 链接完成后的回调函数
def on_connect(self, client, userdata, flags, rc):
logging.info("+++ Connected with result code {} +++".format(str(rc)))
self.client.subscribe(self.topic_from_base)
复制代码
- Disconnect: 等待MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话。
- Subscribe:等待完成订阅。(python代码如下)
# 订阅函数
def subscribe(self):
self.client.subscribe(self.topic_from_base, 1)
# 消息到来处理函数
self.client.on_message = self.on_message
# 接收到信息后的回调函数, (client:客户端信息,userdata:用户信息,msg:消息体)
def on_message(self, client, userdata, msg):
pass
复制代码
- UnSubscribe:等待服务器取消客户端的一个或多个订阅。
- Publish:MQTT客户端发送消息请求,发送完成后返回应用程序线程。(python代码如下)
# 数据发送函数
def publish(self, index, fuc):
# mqtt发送
self.client.publish(
self.topic_to_base, #主题
to_base_data # 消息题
)
复制代码
MQTT协议数据包结构
在MQTT协议中,一个MQTT数据包由:固定头(Fixed header)、可变头(Variable header)、消息题(Payload)三部分组成。MQTT数据包结构如下:
- 固定头(Fixed header): 存在与所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
- 可变头(Variable header):存在与部分MQTT数据包中,数据包类型决定了可变头是否存在及其具内容。
- 消息体(Payload):存在于部分MQTT数据包中,表示客户端收到的具体内容。
MQTT固定头
固定头存在与所有MQTT数据包中,使用两个字节看,共十六位 其结构如下:
MQTT数据包类型
位置:Byte 1中的4-7位。
使用四位二进制,可代表十六种类型消息:
除去0和15位置属于保留待用,共十四种消息时间类型。
标志位
位置:Byte 1 中的0-3
- DUP :保证消息可靠传输,默认为0,只占用一个字节,表示第一次发送。不能用于检测消息重复发送等。只适用于客户端或服务器端尝试重发PUBLISH, PUBREL, SUBSCRIBE 或 UNSUBSCRIBE消息,注意需要满足条件:当QoS > 0消息需要回复确认 此时,在可变头部需要包含消息ID,当值为1时,表示当前效益先前已经被传送过。
- Qos(服务质量):
- RETAIN:发布保留标识,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它,如果设有那么推送至当前订阅者后释放
剩余长度
位置:Byte 2
固定头的第二字节用来保存变长头部和消息体的总大小的,但不是直接保存的。这一字节是可以扩展,其保存机制,前7位用于保存长度,后一部用做标识。当最后一位为1时,表示长度不足,需要使用二个字节继续保存。例如:计算出后面的大小为0
MQTT可变头
MQTT数据包中包含一个可变头,它驻位于固定的头和负载之间。很多类型数据包中都包括一个2字节的数据包标识字段,这些类型的包有:PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK。可变头部内容字节长度 + Playload/负荷字节长度 = 剩余长度,这个是需要牢记的可变头的内容因数据包类型而不同,较常的应用是作为包的标识:
消息体(Payloa)
Payload消息体位MQTT数据包的第三部分,包含CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四种类型的消息:
- CONNECT:消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码。
- SUBSCRIBE:消息体内容是一系列的要订阅的主题以及QoS。
- SUBACK:消息体内容是服务器对于SUBSCRIBE所申请的主题及QoS进行确认和回复。
- UNSUBSCRIBE:消息体内容是要订阅的主题。
python 简单测试代码
# -*- coding: utf-8 -*-
import paho.mqtt.client as mqtt
import time
class Mqtt(object):
def __init__(self):
self.MQTTHOST = "********"
self.MOTTPORT = "********"
self.client_id = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
self.client = mqtt.Client(self.client_id)
self.client.username_pw_set()
# 设置链接上服务器后回调函数
self.client.on_connect = self.on_connect
# 设置接收到服务器消息后回调函数
self.client.on_message = self.on_message
# 连接服务器,维持心跳为60秒
self.client.connect(self.MQTTHOST, self.MOTTPORT, 60)
self.client.loop_forever()
def on_connect(self,client, userdata, flag, rc):
print("Connected with result code " + str(rc))
client.subscribe("test")
def on_publish(self, topic, payload, qos):
self.client.publish(topic, payload, qos)
print(topic+"消息发送成功。。。。。")
return 1
def on_message(self, client, userdata, msg):
print(msg.topic+ "" +msg.payload.decode("utf-8"))
return 1
def on_subscribe(self, topic):
self.client.subscribe(topic, 1)
self.client.on_message = self.on_message
return 1
if __name__ == "__main__":
mqtt = Mqtt()
mqtt.on_publish("test", "helloWord",1)
复制代码
参考:
https://www.jianshu.com/p/5c42cb0ed1e9
http://www.runoob.com/w3cnote/mqtt-intro.html
http://www.blogjava.net/yongboy/archive/2014/02/07/409587.html
新手上车,请多指教,如有问题,请邮件联系:young5678@qq.com