网络编程——物联网利器MQTT通信协议详解(四)

文章大纲

引言

书接上文网络编程——物联网利器MQTT通信协议详解(四) 系列文章链接:

6、MQTT控制包之PUBLISH包的结构

PUBLISH控制包可以从服务端发送给客户端也可以从客户端发送给服务端,来运送应用消息

6.1 固定包头

PUBLISH的固定包头与其他有所不同,格式如下所示

Figure 3.10 - PUBLISH Packet fixed header
|Bit        |7  |6  |5  |4                  |3          |2  |1      |0
|byte 1     |MQTT Control Packet type (3)   |DUP flag   |QoS level  |RETAIN
|           |0  |0  |1  |1                  |X          |X  |X      |X
|byte 2     |Remaining Length
6.1.1 DUP

位于固定包头第1个字节的位 3 处(byte 1,bit 3)

  • 如果DUP标识被设置为0,则表示服务端或客户端第一次尝试发送PUBLISH包。换言之,所有QoS为0的消息DUP标识必须也设置为0。
  • 如果DUP标识被设置为1,则表示可能是在重复发送早前尝试发送过的数据包。换言之,当客户端或是服务端试图重新发送PUBLISH包的时候,DUP标识必须被设置为1。

服务端收到带有DUP值的PUBLISH包,当服务端发送这个PUBLISH包给订阅者的时候,DUP的值不会传播。发送的PUBLISH包的DUP标识独立于收到的PUBLISH包的DUP标识,它的值只由是否是重复发送决定

接收方在收到DUP标识设置为1的控制包时,不能当作已经收到较早的控制包。DUP标识只作用于控制包本身,而不是它包含的应用程序消息。使用QoS 1时,有可能为客户获得发布包DUP标志设置为0,包含一个已经收到过的应用程序的重复信息,但是有不同的数据包标识。

6.1.2 QoS

位于固定包头第1个字节的位 2–1(byte 1,bits 2-1)表明了应用消息传送的质量。

|QoS value |Bit 2 |bit 1 |Description|
|—|----|—|
|0 |0 |0 |At most once delivery|
|1 |0 |1 |At least once delivery|
|2 |1 |0 |Exactly once delivery|
|- |-1 |1 |Reserved – must not be used|

PUBLISH包必定不能将QoS的两个bit都设为1(即QoS value不能设置为3,二进制 11)。如果服务端或客户端收到的PUBLISH包中QoS的两个bit都设置为1的话,必须关闭网络连接。

6.1.3 RETAIN

位于固定包头的**第1个字节的位0 **(byte 1,bit 0),RETAIN标识被设置为1,在一个从客户端发送到服务端的PUBLISH包中,服务端必须存储应用消息和QoS,以便可以发送给之后订阅这个话题的订阅者。当一个新的订阅发生,最后一个保留的消息,如果有的话,而且匹配订阅话题,必须发送给订阅者。

  • 如果服务端收到一个QoS 0并且RETAIN标识设置为1的消息,它必须清空之前为这个话题保存的所有消息。服务端应该存储新的QoS 0的消息作为这个话题新的保留消息,但是也可以选择在任何时候清空保留消息——如果这样做了,那这个话题就没有保留消息了;
  • 如果消息是作为客户端新的订阅的结果从服务端发送PUBLISH包给客户端,服务端必须将RETAIN标识设置为1,当PUBLISH包发送给客户端,必须设置RETAIN为0,因为不管标识如何设置,它都是已订阅的消息。

RETAIN标识被设置为1而且载荷包含0个字节的PUBLISH包也会被服务端像平常一样处理,发送给匹配话题的客户端。而且所有这个话题下的保留消息都会被清除,这个话题接下来的订阅者都不会收到保留消息

"平常"意味着现存客户端收到的消息RETAIN标识都没有设置。0字节的保留消息一定不会作为保留消息存储在服务端

如果RETAIN标识位0,在客户端发送给服务端的PUBLISH包中,服务端一定不能存储这个消息,也一定不能删除或替换任何已存在的保留消息

6.1.4、Remaining Length

剩余长度为可变包头的长度加上载荷的长度

6.2 可变包头

可变包头按顺序包含以下字段:话题名,包唯一标识。

6.2.1 话题名

话题名指载荷数据发布的信息通道,话题名必须是PUBLISH包可变包头的第一个字段且是以是UTF-8编码字符串。PUBLISH包中的话题名一定不能包含通配符,服务端发送给客户端的PUBLISH包中的话题名必须匹配订阅话题过滤器。但是因为服务端允许重写话题名,所以可能话题名和最初的PUBLISH包不同。

6.2.2 包唯一标识

包唯一标识只在QoS等级为1或2的PUBLISH包中,包标唯一标识的更多信息见2.1节。

6.3 载荷

载荷包含了发布的应用消息,内容和格式由应用决定。载荷的长度可以由固定包头中的Remaining Length减去可变包头长度得到,也适用于载荷长度为零的PUBLISH包。

6.4 响应

PUBLISH包的接收方必须根据,由PUBLISH包的QoS来决定如何响应。

QoS Level Expected Response
QoS 0 None
QoS 1 PUBACK Packet
QoS 2 PUBREC Packet

6.5 行为

为了分发给匹配订阅的客户端,客户端需要使用PUBLISH包先发送应用消息给服务端,再由服务端使用PUBLISH包发送应用消息给每一个匹配订阅的客户端。当客户端通过带有通配符的话题过滤器订阅的时候,订阅之间可能会有重叠,以至于发布的消息会匹配多个过滤器。这种情况下,服务端必须使用客户端所有匹配的订阅中的最大的QoS来派发消息[且服务端可能会发送多个消息副本,每个匹配的订阅使用相应的QoS发送一次。当收到PUBLISH包的时候,接收者的行为依赖于QoS等级。

7、MQTT控制包之PUBACK包的结构

发布确认PUBACK包,用于响应QoS等级为1 的PUBLISH包,无载荷

7.1、固定包头

PUBACK包的固定包头结构与其他包头一致,其剩余长度为可变包头的长度即2

7.2、可变包头

包含需要确认的那个PUBLISH包的包唯一标识。

Figure3.13 - PUBACK Packet variable header

|Bit        |7 |6 |5 |4 |3 |2 |1 |0
|byte 1     |Packet Identifier MSB
|byte 2     |Packet Identifier LSB

8、MQTT 控制包之PUBREC 包

发布收到PUBREC包,用于响应QoS等级为2 的PUBLISH包,是QoS 2协议交换的第二个包,无载荷

8.1、固定包头

PUBREC包的固定包头结构与其他包头一致,其剩余长度为可变包头的长度即2

8.2、可变包头

包含需要确认的那个PUBLISH包的包唯一标识。

9、MQTT 控制包之PUBREL 包(收到QoS 2的发布)

发布完成PUBREL包,用于响应PUBREC包,是QoS 2协议交换的第三个包,无载荷

9.1、固定包头

PUBREL控制包的固定包头的位3,2,1,0是保留位且必须被分别设置为0,0,1,0。服务端必须将其他值作为畸形,并且关闭网络连接。


|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (6)   |Reserved
|           |0      |1      |1      |0      |0      |0      |1      |0
|byte 2     |Remaining Length (2)
|           |0      |0      |0      |0      |0      |0      |1      |0

其剩余长度为可变包头的长度即2

9.2、可变包头

包含需要确认的那个PUBREC包的包唯一标识。

10、MQTT 控制包之PUBCOMP包(QoS 2发布接收)

发布PUBCOMP包,用于响应PUBREL包,是QoS 2协议交换的第四个也是最后一个包,无载荷

10.1、固定包头

PUBREC包的固定包头结构与其他包头一致,其剩余长度为可变包头的长度即2

10.2、可变包头

包含需要确认的那个PUBREL包的包唯一标识。

11、MQTT 控制包之SUBSCRIBE包

SUBSCRIBE包从客户端发送到服务端创建一个或多个订阅,每个订阅注册客户端感兴趣的一个或多个话题。服务端向客户端发送PUBLISH包来分发匹配订阅的应用消息。SUBSCRIBE包也(为每个订阅)指定服务端发送给客户端的应用消息的最大QoS。

11.1、固定包头

SUBSCRIBE控制包的固定包头的3,2,1,0位是保留位且必须设置为0,0,1,0。服务端必须把其他值作为畸形对待,然后关闭网络连接。

|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (8)   |Reserved
|           |1      |0      |0      |0      |0      |0      |1      |0
|byte 2     |Remaining Length

剩余长度为可变包头长度(即2)加上载荷的长度

11.2、可变包头

可变包头包含包唯一标识,其他参见2节MQTT可变包头。

11.3、载荷

SUBSCRIBE包的载荷包含了话题过滤器的列表(指示客户端想要订阅的主题),主题过滤器必须是UTF-8编码字符串。服务端应该支持包含通配符的话题过滤器。如果选择不支持带有通配符的话题过滤器,就必须拒绝所有带有通配符过滤器的订阅。另外每个过滤器后面都带有一个字节的QoS(指定了服务端发送给客户端应用消息的最大QoS等级)。所以SUBSCRIBE包的载荷至少包含一个成对的话题过滤器/QoS且以话题过滤器/QoS对依次连续被封装,没有载荷的SUBSCRIBE包是违反协议的。

|Description    |7 |6 |5 |4 |3 |2 |1 |0
|Topic Filter
|byte 1         |Length MSB
|byte 2         |Length LSB
|bytes 3..N     |Topic Filter
|Requested QoS
|               |Reserved         |QoS
|byte N+1       |0 |0 |0 |0 |0 |0 |X |X

11.4、响应

服务端收到客户端发来的SUBSCRIBE包,服务端必须响应一个包含与相应SUBSCRIBE包相同的包唯一标识SUBACK包。服务端在发送SUBACK包之前,就可以开始发送订阅的PUBLISH包。如果服务端收到一个SUBSCRIBE包,其中包含的话题过滤器与一个已经存在的话题过滤器完全相同,服务端必须用新的订阅替换已经存在的订阅。如果新的订阅中的话题过滤器与之前订阅中的完全一样,但是最大QoS的值可能不同。所有保存的符合该话题过滤器的消息必须被重发,但是发布流一定不能被打断。如果话题过滤器与已存在订阅的过滤器都不相同,新的订阅会被创建,并且所有的匹配的保留消息都会被发送。如果服务端收到的SUBSCRIBE包包含多个话题过滤器,必须像顺序的收到多个SUBSCRIBE包一样处理它,然后再把所有的响应合并为一个SUBACK响应从服务端发送到客户端的SUBACK包中,每一个话题过滤器/QoS对都有一个对应的返回码。返回码必须是这次订阅的最大QoS,否则就订阅失败。服务端可能会授予一个较低等级的最大QoS,相比较于订阅者要求的。订阅响应的载荷消息的QoS必须是原始发布消息的最小QoS,而且是服务端授予的最大QoS。服务端可以给订阅者发布多个消息拷贝,来应对原始消息使用QoS 1发布,最大QoS为QoS 0的情况。

12、MQTT控制包之SUBACK

订阅确认SUBACK包是从服务端发送给客户端用来确认收到并处理了SUBSCRIBE包。SUBACK包包含了返回码的列表,指定了SUBSCRIBE包中的每个订阅的最大QoS等级。

12.1、固定包头


|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (9)   |Reserved
|           |1      |0      |0      |1      |0      |0      |0      |0
|byte 2     |Remaining Length

可变长度为可变包头的长度(2字节)再加上载荷的长度。

12.2、可变包头

可变包头包含了需要确认的SUBSCRIBE包的包唯一标识

12.3、载荷

载荷包含了返回码的列表。每个返回码对应SUBSCRIBE包中需要被确认的一个话题过滤器。SUBACK包中的返回码的顺序必须匹配SUBSCRIBE包中的话题过滤器的顺序

|Bit     |7 |6 |5 |4 |3 |2 |1 |0
|        |Return Code
|byte 1  |X |0 |0 |0 |0 |0 |X |X

允许的返回码:

  • 0x00 - 成功 - 最大QoS为0
  • 0x01 - 成功 - 最大QoS为1
  • 0x02 - 成功 - 最大QoS为2
  • 0x80 - 失败

除了0x00,0x01,0x02,0x80之外的SUBACK返回码是保留的且不允许使用。

13、 UNSUBSCRIBE

退订话题UNSUBSCRIBE包从客户端发往服务端用来退订话题

13.1、固定包头

UNSUBSCRIBE控制包固定包头的3,2,1,0位是预留的,而且必须分别被设置为0,0,1,0。服务端必须把其他值作为畸形且关闭网络连接。


|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (10)  |Reserved
|           |1      |0      |1      |0      |0      |0      |1      |0
|byte 2     |Remaining Length

剩余长度为可变包头的长度(即2字节)加上载荷的长度。

13.2、可变包头

可变包头包含了包唯一标识。

|Bit        |7 |6 |5 |4 |3 |2 |1 |0
|byte 1     |Packet Identifier MSB
|byte 2     |Packet Identifier LSB

13.3、载荷

UNSUBSCRIBE包包含了客户端希望退订的话题过滤器列表,UNSUBSCRIBE包的载荷必须包含至少一个话题过滤器,没有载荷的UNSUBSCRIBE包是违反协议的。

13.4、响应

UNSUBSCRIBE包中的话题过滤器(无论是否包含通配符),都必须逐个字符的与客户端在服务器中的现有订阅集合做比较。如果过滤器匹配成功,那么删除相应的订阅,否则就不处理。

如果服务端删除了订阅:

  • 必须停止追加新的派发给客户端的消息[MQTT-3.10.4-2]。
  • 必须完成所有已经开始发送给客户端的QoS 1或QoS 2消息的派发。
  • 可以继续发送派发消息队列中已经存在的消息给客户端。

服务端必须发送UNSUBACK包来响应UNSUBSCRIBE,UNSUBACK包必须和UNSUBSCRIBE包有相同的包唯一标识。即使没有话题订阅被删除,服务端也必须响应一个UNSUBACK。如果服务端收到一个UNSUBSCRIBE包,包含多个话题过滤器,服务端必须像按顺序收到多个UNSUBSCRIBE包一样来处理,但是只发送一个UNSUBACK包来响应。

14、MQTT控制包之 UNSUBACK

退订确认UNSUBACK包从服务端发往客户端来确认收到UNSUBSCRIBE包无载荷。

14.1 固定包头

Figure 3.31 – UNSUBACK Packet fixed header

|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (11)  |Reserved
|           |1      |0      |1      |1      |0      |0      |0      |0
|byte 2     |Remaining Length (2)
|           |0      |0      |0      |0      |0      |0      |1      |0

剩余长度为可变包头的长度即2字节

14.2 可变包头

可变包头包含了需要确认的UNSUBSCRIBE包的包唯一标识。

Figure 3.32 – UNSUBACK Packet variable header

|Bit        |7 |6 |5 |4 |3 |2 |1 |0
|byte 1     |Packet Identifier MSB
|byte 2     |Packet Identifier LSB

15、MQTT控制包之PINGREQ

PING请求PINGREQ包从客户端发往服务端,可以用来:

  • 在没有其他控制包从客户端发送给服务端的时候,告知服务端客户端的存活状态。

  • 请求服务端响应,来确认服务端是否存活。

  • 确认网络连接的有效性。

这个包在Keep Alive处理中用到。

15.1 固定包头

Figure 3.33 – PINGREQ Packet fixed header

|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (12)  |Reserved
|           |1      |1      |0      |0      |0      |0      |0      |0
|byte 2     |Remaining Length (0)
|           |0      |0      |0      |0      |0      |0      |0      |0

剩余长度为0,既无可变包头,也无载荷

15.2 响应

服务端必须发送PINGRESP包响应PINGREQ包。

16、MQTT控制包之 PINGRESP

PING响应 PINGRESP包从服务端发送给客户端来响应PINGREQ包代表服务端是存活的

3.13.1 固定包头

|Figure 3.34 – PINGRESP Packet fixed header

|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (13)  |Reserved
|           |1      |1      |0      |0      |0      |0      |0      |0
|byte 2     |Remaining Length (0)
|           |0      |0      |0      |0      |0      |0      |0      |0

剩余长度为0,既无可变包头,也无载荷

17、MQTT控制包之DISCONNECT

断开连接通知 DISCONNECT包是客户端发给服务端的最后一个控制包表示客户端正在干净利索的断开连接

17.1 固定包头

|Figure 3.35 – DISCONNECT Packet fixed header

|Bit        |7      |6      |5      |4      |3      |2      |1      |0
|byte 1     |MQTT Control Packet type (14)  |Reserved
|           |1      |1      |0      |0      |0      |0      |0      |0
|byte 2     |Remaining Length (0)
|           |0      |0      |0      |0      |0      |0      |0      |0

剩余长度为0,既无可变包头,也无载荷

17.2 响应

客服端发送DISCONNECT包之后:

  • 必须关闭网络连接[MQTT-3.14.4-1]。
  • 不允许在这个网络连接上再发送任何控制包。

服务端收到DISCONNECT包:

  • 必须丢弃所有和当前连接有关的Will Message,不要发布。
  • 应该关闭网络连接,如果客户端还没有这么做。

六、MQTT 常见Q&A

Question Answer
MQTT 协议默认使用的端口号? 1883
没有Broker可以使用MQTT协议吗? 不能,必须搭建或者借用其他公共的Broker
多个客户端是否可以发布到同一Topic? 可以
是否可以知道发布Message的客户端的具体身份? 默认不行,除非在Topic或者Payload中主动标识
当发布的Topic无人订阅Broker 会怎么处理? 默认忽略掉,丢弃。
是否可以订阅一个可能无任何客户端发布的主题? 可以,但无意义。
Message是否存储在Broker上? 是的,但是只是临时性的,一旦被转发给所有订阅者,它们就会被丢弃,但Retained Messages 除外。
什么是Retained Messages? 发布Message时,可以让Broker存储上次发布的消息,若最后一条Message没被订阅者处理,Broker将在订阅者订阅时主动转发最近上一次的Message,MQTT只保留1条消息。
如何删除或删除Retained Messages? 用同一个Topic 发布一个空白(Payload 字节为0)的Retained Message
不同的QOS是否对Retained Messages影响? 没有

MQTT的应用,未完待续…

上一篇:令牌桶实现流量限速实例


下一篇:MQTT libary之发布和订阅