(原创)MQTT协议分析

2017/01/11 网络协议

1. 简介

MQTT(Message Queue Telemetry Transport)中文叫做遥测信息队列传输协议,听这名字就知道它应该是用来传输简短信息的协议。

不错,MQTT是IBM于1999年发布,基于TCP/IP协议,专用于机器与机器(M2M)和物联网(IoT)的一种短小精悍的一个协议。

img

上图就是它的协议关系结构:

整个协议关系分三个角色:

  • server 服务器 (也就是上图的代理,3.1.1之前叫做broker)
  • publisher 发布者
  • subscriber 订阅者

一般发布者和订阅者为同一个角色。

协议行为也分三个:

  • 订阅消息
  • 发布消息
  • 推送消息

因此整套协议完全按照观察者模式来设计,主要提供订阅/发布动作,特点是简约、轻量、易于使用,针对受限环境(带宽低、网络延迟高、网络通信不稳定)。

目前该协议版本已经到了V3.1.1,具体版本变化可以进官网查看。

2. 格式

img

上图就是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 可变头部

img

上图即为该业务的可变头部描述:

  • 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 有效载荷

有效载荷包含如下几个部分:(协议中出现的顺序也按照如下排列)

  1. Client Identifier 客户ID。
  2. Will Topic 关心的主题,要想使用Will Flag必须是1。
  3. Will Message Will消息定义了客户端异常离线时服务器发送给Will主题的消息内容,要想使用Will Flag必须是1。
  4. User Name 用户名。
  5. Password 用户密码。

2.2.2 CONNACK

CONNACK:服务器给客户端的连接请求响应。

2.2.2.1 可变头部

img

上图即为该业务的可变头部描述:

  • Connect Acknowledge Flags:最后1位表示连接通知标志,其它位保留。
  • Connect Return code :连接返回代码:
    1. 0:0x00 Connection Accepted
    2. 1:0x01 Connection Refused, unacceptable protocol version
    3. 2:0x02 Connection Refused, identifier rejected
    4. 3:0x03 Connection Refused, Server unavailable
    5. 4:0x04 Connection Refused, bad user name or password
    6. 5:0x05 Connection Refused, not authorized

2.2.2.2 有效载荷

2.2.3 PUBLISH

PUBLISH: 发布消息给服务器。

2.2.3.1 可变头部

img

上图即为该业务的可变头部描述:

  • 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 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息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 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息ID值。

2.2.5.2 有效载荷

2.2.6 PUBREL

PUBREL:发布释放消息,该业务只有在Qos 为2时才会使用。

消息可以是发布者客户端对服务器发送给它的 PUBREC 消息的回应,也可是服务器对订阅者客户端发送给它的 PUBREC 消息的回应。它是QoS级别2协议流中的第3个消息。

2.2.6.1 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息ID值。

2.2.6.2 有效载荷

2.2.7 PUBCOMP

PUBCOMP:发布完成消息,该业务只有在Qos 为2时才会使用。

消息可以是服务器对发布者客户端发送给它的 PUBREL 消息的回应,也可以是订阅者客户端对服务器发送给它的 PUBREL 消息的回应。它是QoS级别2协议流中的第4个消息。

2.2.7.1 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息ID值。

2.2.7.2 有效载荷

2.2.8 SUBSCRIBE

SUBSCRIBE:消息允许客户端向服务器订阅一个或多个感兴趣的主题。这些主题的发布消息都会通过PUBLISH 消息从服务器发送给客户端。SUBSCRIBE 消息还可以分别对各个主题指定客户端期望得到的QoS级别。。

2.2.8.1 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息ID值。

2.2.8.2 有效载荷

有效载荷包含如下几个部分:(协议中出现的顺序也按照如下排列)

  1. Topic Name:订阅的主题名。
  2. Requested QoS:订阅主题的等级。

2.2.9 SUBACK

SUBACK:服务器通过发送 SUBACK 消息来确认其已收到 SUBSCRIBE 消息。 SUBACK 消息包含一系列通过授权的QoS级别。它们的顺序与 SUBSCRIBE 消息里订阅的主题的顺序一致 。

2.2.9.1 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息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 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息ID值。

2.2.10.2 有效载荷

Topic Name:订阅的主题名。

2.2.11 UNSUBACK

UNSUBACK:服务器通过发送 UNSUBACK 消息来确认其已收到 UNSUBSCRIBE 消息。

2.2.11.1 可变头部

img

上图即为该业务的可变头部描述:

包含两个字节的消息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

知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

站内搜索

    撩我备注-博客

    joinee

    目录结构