1. 简介
MQTT(Message Queue Telemetry Transport)中文叫做遥测信息队列传输协议,听这名字就知道它应该是用来传输简短信息的协议。
不错,MQTT是IBM于1999年发布,基于TCP/IP协议,专用于机器与机器(M2M)和物联网(IoT)的一种短小精悍的一个协议。
上图就是它的协议关系结构:
整个协议关系分三个角色:
- server 服务器 (也就是上图的代理,3.1.1之前叫做broker)
- publisher 发布者
- subscriber 订阅者
一般发布者和订阅者为同一个角色。
协议行为也分三个:
- 订阅消息
- 发布消息
- 推送消息
因此整套协议完全按照观察者模式来设计,主要提供订阅/发布动作,特点是简约、轻量、易于使用,针对受限环境(带宽低、网络延迟高、网络通信不稳定)。
目前该协议版本已经到了V3.1.1,具体版本变化可以进官网查看。
2. 格式
上图就是MQTT协议的基本消息格式,整个消息格式分成两部分:
- 固定头部
- 业务内容
注意:
本协议所有消息数据的值都是以big-endian(大端)模式存储:
数据的高位字节存放在内存的低地址中,数据的低位字节存放在内存高地址中。
一个16位字在内存中的存放顺序是先最高有效位(MSB),然后再最低有效位(LSB)。
2.1 固定头部
该部分包含两个字节,如图中所示,字节1包含消息标志,字节2指示业务内容的字节长度。
2.1.1 Message Type
占4个位,表示本消息类型:
- 0:Reserved 保留
- 1:CONNECT 客户端请求连接服务器
- 2:CONNACK 连接确认
- 3:PUBLISH 发布消息
- 4:PUBACK 发布确认
- 5:PUBREC 发布接收(有保证的交付第1部分)
- 6:PUBREL 发布释放(有保证的交付第2部分)
- 7:PUBCOMP 发布完成(有保证的交付第3部分)
- 8:SUBSCRIBE 客户端订阅请求
- 9:SUBACK 订阅确认
- 10:UNSUBSCRIBE 客户端取消订阅请求
- 11:UNSUBACK 取消订阅确认
- 12:PINGREQ PING请求
- 13:PINGRESP PING回复
- 14:DISCONNECT 客户端断开连接
- 15:Reserved 保留
2.1.2 DUP
占1个位,全称是Duplicate delivery of a PUBLISH Control Packet,表示发布的重复标志位。
当客户端或服务器试图重发 PUBLISH、PUBREL、SUBSRIBE、UNSUBSCRIBE 消息时,该标志位要被置位(即设为1),一般用在发布消息的时候。
这适用于消息的QoS标志值大于0的情况,此时消息确认是必需的。
当DUP位被置位时,可变头部将包含一个消息ID。
消息的接收者应当将该标志视为该消息之前可能已收到的提示消息,而不该依赖于它进行消息重复检测。
2.1.3 QoS
占1个位,全称是 PUBLISH Quality of Service,表示设置服务的发布质量。
- 0 : At most once delivery 至多一次
- 1 : At least once delivery 至少一次
- 2 : Exactly once delivery 精确一次
- x : Reserved – must not be used 保留
2.1.4 RETAIN
占1个位,全称是 PUBLISH Retain flag,该标志位只用于指示 PUBLISH 消息的通道是否保持。
- 0 : 不保持
- 1 : 保持
2.1.5 Remaining Length
该部分可以有1-4个字节,表示业务内容的字节长度。
该部分本身的字节数是根据业务内容的长度不同而变化的。
该可变长度编码方案如下:
每个字节的低7位(7-0位)编码剩余长度的数据,第8位表示后面是否还有编码剩余长度的字节。
即每个字节编码128个值和一个“延续位”。所以只用一个字节时,最大只可表示127字节的长度。
举例如下:
十进制数字64只需用1个字节来编码,即0x40。
十进制数字321(=65 + 2x128)则需要用2个字节来编码,其中第1个字节为1100 0001,该字节的低7位表示65,第8位表示后面还有字节,
第2个字节为0000 0010,表示2x128。
因此协议限制该字段最大为4个字节,这允许应用程序发送的最大消息长度为268435455(256MB),即0xFF,0xFF,0xFF,0x7F。
2.2 业务内容
从上图可以看出,业务内容依据不同业务所包含的格式也不一样,不过总的来说可以分成可变头部和有效载荷两部分。
下面我们列举不同业务来做进一步解释。
2.2.1 CONNECT
CONNECT:客户端请求连接服务器。
2.2.1.1 可变头部
上图即为该业务的可变头部描述:
- Protocol Name 固定6个字节, 00 04 后面跟个 M Q T T。(3.1.1之前版本叫做 MQIsdp)。
- Protocol Level 固定1个字节,04 代表版本号。
- Connect Flags 固定1个字节,传送连接标志,各个标志字面上很好理解。
- Keep Alive 固定2个字节,表示保活时间,一般为十秒:00 0A。
2.2.1.2 有效载荷
有效载荷包含如下几个部分:(协议中出现的顺序也按照如下排列)
- Client Identifier 客户ID。
- Will Topic 关心的主题,要想使用Will Flag必须是1。
- Will Message Will消息定义了客户端异常离线时服务器发送给Will主题的消息内容,要想使用Will Flag必须是1。
- User Name 用户名。
- Password 用户密码。
2.2.2 CONNACK
CONNACK:服务器给客户端的连接请求响应。
2.2.2.1 可变头部
上图即为该业务的可变头部描述:
- Connect Acknowledge Flags:最后1位表示连接通知标志,其它位保留。
- Connect Return code :连接返回代码:
- 0:0x00 Connection Accepted
- 1:0x01 Connection Refused, unacceptable protocol version
- 2:0x02 Connection Refused, identifier rejected
- 3:0x03 Connection Refused, Server unavailable
- 4:0x04 Connection Refused, bad user name or password
- 5:0x05 Connection Refused, not authorized
2.2.2.2 有效载荷
无
2.2.3 PUBLISH
PUBLISH: 发布消息给服务器。
2.2.3.1 可变头部
上图即为该业务的可变头部描述:
- Topic Name:主题名字。
- Packet Identifier :消息ID。
2.2.3.2 有效载荷
用户自定义数据
2.2.4 PUBACK
PUBACK:发布消息响应。
消息是对QoS级别1的 PUBLISH 消息的回应。当发布者客户端发送 PUBLISH 消息给服务器,服务器有义务回复 PUBACK 消息。同样地,当服务器发送 PUBLISH 消息给订阅者客户端,客户端也有义务回复 PUBACK 消息。
2.2.4.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.4.2 有效载荷
无
2.2.5 PUBREC
PUBREC:发布接收消息,该业务只有在Qos 为2时才会使用。
消息是对QoS级别2的 PUBLISH 消息的回应。它是QoS级别2协议流中的第2个消息。PUBREC 消息可以是服务器对发布者客户端发送的 PUBLISH 消息的回应,也可以是订阅者客户端对服务器发送的 PUBLISH 消息的回应。
2.2.5.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.5.2 有效载荷
无
2.2.6 PUBREL
PUBREL:发布释放消息,该业务只有在Qos 为2时才会使用。
消息可以是发布者客户端对服务器发送给它的 PUBREC 消息的回应,也可是服务器对订阅者客户端发送给它的 PUBREC 消息的回应。它是QoS级别2协议流中的第3个消息。
2.2.6.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.6.2 有效载荷
无
2.2.7 PUBCOMP
PUBCOMP:发布完成消息,该业务只有在Qos 为2时才会使用。
消息可以是服务器对发布者客户端发送给它的 PUBREL 消息的回应,也可以是订阅者客户端对服务器发送给它的 PUBREL 消息的回应。它是QoS级别2协议流中的第4个消息。
2.2.7.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.7.2 有效载荷
无
2.2.8 SUBSCRIBE
SUBSCRIBE:消息允许客户端向服务器订阅一个或多个感兴趣的主题。这些主题的发布消息都会通过PUBLISH 消息从服务器发送给客户端。SUBSCRIBE 消息还可以分别对各个主题指定客户端期望得到的QoS级别。。
2.2.8.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.8.2 有效载荷
有效载荷包含如下几个部分:(协议中出现的顺序也按照如下排列)
- Topic Name:订阅的主题名。
- Requested QoS:订阅主题的等级。
2.2.9 SUBACK
SUBACK:服务器通过发送 SUBACK 消息来确认其已收到 SUBSCRIBE 消息。 SUBACK 消息包含一系列通过授权的QoS级别。它们的顺序与 SUBSCRIBE 消息里订阅的主题的顺序一致 。
2.2.9.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.9.2 有效载荷
Return Code:1个字节
- 0x00 - Success - Maximum QoS 0
- 0x01 - Success - Maximum QoS 1
- 0x02 - Success - Maximum QoS 2
- 0x80 - Failure
2.2.10 UNSUBSCRIBE
UNSUBSCRIBE:客户端可以通过发送 UNSUBSCRIBE 消息给服务器来退订相应的主题。
2.2.10.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.10.2 有效载荷
Topic Name:订阅的主题名。
2.2.11 UNSUBACK
UNSUBACK:服务器通过发送 UNSUBACK 消息来确认其已收到 UNSUBSCRIBE 消息。
2.2.11.1 可变头部
上图即为该业务的可变头部描述:
包含两个字节的消息ID值。
2.2.11.2 有效载荷
无
2.2.12 PINGREQ
PINGREQ:消息从连接中的客户端发送给服务器,表示询问服务器:“你还活着吗”。
2.2.12.1 可变头部
无
2.2.12.2 有效载荷
无
2.2.13 PINGRESP
PINGRESP:息是服务器对客户端发送给它的 PINGREQ 消息的回应,表示回答客户端:“对,哥还活着”。
2.2.13.1 可变头部
无
2.2.13.2 有效载荷
无
2.2.14 DISCONNECT
DISCONNECT:当客户端想要断开它的TCP/IP连接时,通过向服务器发送 DISCONNECT 消息告知服务器。这是为了干净地断开连接,而不仅仅是断开连线。 如果客户端在连接时将clean session标志置位,则之前保持的客户端的信息将会被丢弃。 服务器不应该仅仅依赖客户端发送的 DISCONNECT 消息来关闭TCP/IP连接 。
2.2.14.1 可变头部
无
2.2.14.2 有效载荷
无
3. 主题通配符
3.1 层次分隔符
斜线(/)是用来分隔一棵主题树里的各个层次,它提供了一个层次结构的主题空间。主题层次分隔符的引入为解决主题冲突有重要的意义
3.2 多层次通配符
多层次通配符可以表示0个或任意多个层次。
例如,如果你想要订阅了 finance/stock/ibm/#,则你可以收到如下主题的消息:
finance/stock/ibm
finance/stock/ibm/closingprice
finance/stock/ibm/currentprice
3.3 单层次通配符
加号(+)是用来匹配一个主题层次的通配符。
例如:finance/stock/+ 可以匹配 finance/stock/ibm 和 finance/stock/xyz ,但不能匹配 finance/stock/ibm/closingprice。
4. 实践
我们可以利用python雨来来实践该协议。
hbmqtt是服务器版本,当然也包含客户端。
paho仅仅是客户端。
读者可以去下载这两个代码跑跑看,里面都有demo。
例如:(我运行hbmqtt工程中的client_subscribe.py模块)
C:\Python36\python3.exe G:/MQTT/hbmqtt-master/samples/client_subscribe.py
[2017-01-12 17:03:02,223] {client_subscribe.py:25} INFO - Subscribed
1: $SYS/broker/uptime => bytearray(b'2026643 seconds')
2: $SYS/broker/load/bytes/received/1min => bytearray(b'1962696.67')
3: $SYS/broker/load/bytes/received/5min => bytearray(b'2051496.52')
4: $SYS/broker/load/bytes/received/15min => bytearray(b'2063470.34')
5: $SYS/broker/load/bytes/sent/1min => bytearray(b'3940079.57')
6: $SYS/broker/load/bytes/sent/5min => bytearray(b'4128673.69')
7: $SYS/broker/load/bytes/sent/15min => bytearray(b'3951893.08')
8: $SYS/broker/load/messages/received/1min => bytearray(b'18956.21')
9: $SYS/broker/load/messages/received/5min => bytearray(b'17624.29')
10: $SYS/broker/load/messages/received/15min => bytearray(b'17473.97')
11: $SYS/broker/load/messages/sent/1min => bytearray(b'37213.88')
12: $SYS/broker/load/messages/sent/5min => bytearray(b'35393.72')
13: $SYS/broker/load/messages/sent/15min => bytearray(b'34886.69')
...
[2017-01-12 17:03:46,534] {client_subscribe.py:32} INFO - UnSubscribed