MQTT 协议学习:005-发布消息 与 对应报文 (PUBLISH、PUBACK、PUBREC、PUBREL)

--- title: protocol-app-mqtt-5-publish date: 2020-02-05 11:23:51 categories: tags: - mqtt - protocol ---

背景

当有订阅者订阅了有关的主题以后,通过发布消息的消息的动作,可以让订阅者收到对应主题的消息。

根据不同的QoS 等级,通信的动作也略有不同。

PUBLISH – 发布消息 报文

PUBLISH控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。

  • 客户端使用PUBLISH报文发送应用消息给服务端,目的是分发到其它订阅匹配的客户端。
  • 服务端使用PUBLISH报文发送应用消息给每一个订阅匹配的客户端。

客户端使用带通配符的主题过滤器请求订阅时,客户端的订阅可能会重复,因此发布的消息可能会匹配多个过滤器。对于这种情况,服务端必须将消息分发给所有订阅匹配的QoS等级最高的客户端。服务端之后可以按照订阅的QoS等级,分发消息的副本给每一个匹配的订阅者。

收到一个PUBLISH报文时,接收者的动作取决于QoS等级。

如果服务端实现不授权某个客户端发布PUBLISH报文,它没有办法通知那个客户端。它必须按照正常的QoS规则发送一个正面的确认,或者关闭网络连接。

PUBLISH 固定头

Bit 7 6 5 4 3 2 1 0
byte 1 MQTT控制报文类型 (0x3) DUP QoS RETAIN
0 0 1 1 X X X X
byte 2... 剩余长度

下面我们直接看各个报文类型标志位

重发标志 DUP

保证消息可靠传输,默认为0,只占用一个字节,表示第一次发送。不能用于检测消息重复发送等。
只适用于客户端或服务器端尝试重发PUBLISH, PUBREL, SUBSCRIBE 或 UNSUBSCRIBE消息,注意需要满足以下条件:
1)当QoS > 0
2)消息需要回复确认

位置:byte1[3]

如果DUP标志被设置为0:表示这是客户端或服务端第一次请求发送这个PUBLISH报文。对于QoS 0的消息,DUP标志必须设置为0。

如果DUP标志被设置为1:表示这可能是一个早前报文请求的重发。

客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1。

服务端发送PUBLISH报文给订阅者时,收到(入站)的PUBLISH报文的DUP标志的值不会被传播。发送(出站)的PUBLISH报文与收到(入站)的PUBLISH报文中的DUP标志是独立设置的,它的值必须单独的根据发送(出站)的PUBLISH报文是否是一个重发来确定。

非规范评注

接收者收到一个DUP标志为1的控制报文时,不能假设它看到了一个这个报文之前的一个副本。
需要特别指出的是,DUP标志关注的是控制报文本身,与它包含的应用消息无关。当使用QoS 1时,客户端可能会收到一个DUP标志为0的PUBLISH报文,这个报文包含一个它之前收到过的应用消息的副本,但是用的是不同的报文标识符。

服务质量等级 QoS

这个字段表示应用消息分发的服务质量等级保证。
关于 QoS 等级 与 流程可以参考 :《Qos等级 与 会话》

位置:byte1[2:1] 。

QoS值 Bit 2 Bit 1 描述
0 0 0 最多分发一次
1 0 1 至少分发一次
2 1 0 只分发一次
- 1 1 保留位

PUBLISH报文不能将QoS所有的位设置为1。如果服务端或客户端收到QoS所有位都为1的PUBLISH报文,它必须关闭网络连接。

当QoS设置为1时,客户端或服务器发布消息时,需要得到对方的确认(PUBACK),如果一段时间后没收到PUBACK,那么会再次发送当前消息,并将DUP字段标记为1。

保留标志 RETAIN

用来设定一条消息是否为保留消息;如果是,那么 服务端必须存储这个应用消息和它的服务质量等级(QoS),以便它可以被分发给未来的主题名匹配的订阅者。一个新的订阅建立时,对每个匹配的主题名,如果存在最近保留的消息,它必须被发送给这个订阅者。
参考:《Retained(保留消息) 和LWT(最后遗嘱)》

位置:byte1[0] 。

如果服务端收到一条保留(RETAIN)标志为1的QoS 0消息,它必须丢弃之前为那个主题保留的任何消息。它应该将这个新的QoS 0消息当作那个主题的新保留消息,但是任何时候都可以选择丢弃它 — 如果这种情况发生了,那个主题将没有保留消息。有关存储状态的更多信息见 4.1节。

服务端发送PUBLISH报文给客户端时,如果消息是作为客户端一个新订阅的结果发送,它必须将报文的保留标志设为1 [MQTT-3.3.1-8]。当一个PUBLISH报文发送给客户端是因为匹配一个已建立的订阅时,服务端必须将保留标志设为0,不管它收到的这个消息中保留标志的值是多少。

保留标志为1且有效载荷为零字节的PUBLISH报文会被服务端当作正常消息处理,它会被发送给订阅主题匹配的客户端。此外,同一个主题下任何现存的保留消息必须被移除,因此这个主题之后的任何订阅者都不会收到一个保留消息。当作正常 意思是现存的客户端收到的消息中保留标志未被设置。服务端不能存储零字节的保留消息。

如果客户端发给服务端的PUBLISH报文的保留标识为0,服务端不能存储这个消息也不能移除或替换任何现存的保留消息。

通俗来讲:之前发的消息A是带有保留标识位(值1)的,如果此后又发了消息B(没有带保留标志位的),那么保留消息还是A(而不是B)。

对于发布者不定期发送状态消息这个场景,保留消息 常用在 新的订阅者希望会收到某主题最近的状态。

PUBLISH 的 可变头

可变报头按顺序包含主题名(Topic Name)报文标识符(Packet Identifier)

主题名 Topic Name

主题名(Topic Name)用于识别有效载荷数据应该被发布到哪一个信息通道。

主题名必须是PUBLISH报文可变报头的第一个字段。

二进制位 7-0
byte 1 字符串长度的最高有效字节(MSB)
byte 2 字符串长度的最低有效字节(LSB)
byte 3 … 如果长度大于0,这里是UTF-8编码的字符数据。

PUBLISH报文中的主题名不能包含通配符。

服务端发送给订阅客户端的PUBLISH报文的主题名必须匹配该订阅的主题过滤器。

报文标识符 Packet Identifier

只有当QoS等级是1或2时,报文标识符(Packet Identifier)字段才能出现在PUBLISH报文中。
报文标识符用来区分报文,特别是在重发的报文中用来标识是否是同一个报文,并在需要应答的场景中用于确定是对哪个发送报文的应答。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里(占用2个字节)。这些报文是:
PUBLISH(QoS > 0时)PUBACKPUBRECPUBRELPUBCOMPSUBSCRIBE, SUBACKUNSUBSCRIBEUNSUBACK

Bit 7 - 0
byte 1 报文标识符 MSB
byte 2 报文标识符 LSB

Packet ID默认是从1(0x01)开始并自增,最大为255(0xff)。

SUBSCRIBEUNSUBSCRIBEPUBLISH(QoS大于0)控制报文必须包含一个非零的16位报文标识符(Packet Identifier)。

  • 客户端每次发送一个新的这些类型的报文时都必须分配一个当前未使用的报文标识符
  • 如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符

当客户端处理完这个报文对应的确认(ACK, CMP)后,这个报文标识符就释放可重用。

例如:QoS 1的PUBLISH对应的是PUBACK,QoS 2的PUBLISH对应的是PUBCOMP,与SUBSCRIBE或UNSUBSCRIBE对应的分别是SUBACKUNSUBACK

发送一个QoS 0的PUBLISH报文时,相同的条件也适用于服务端。

QoS等于0的PUBLISH报文不能包含报文标识符。

PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的报文标识符。类似地,SUBACKUNSUBACK必须包含在对应的SUBSCRIBE和UNSUBSCRIBE报文中使用的报文标识符。

PUBLISH 的 有效载荷

有效载荷包含将被发布的应用消息,即:数据的内容和格式是应用特定的。

包含零长度有效载荷的PUBLISH报文是合法的。

有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度。

实际上,应该说是 固定报头中的剩余长度字段的值 = 可变报头的长度 + 有效荷载
因为只有确定了可变头与有效荷载的长度,才可以计算固定报头中的剩余长度的值。

PUBLISH报文的 响应

PUBLISH 报文的接收者必须按照根据PUBLISH报文中的QoS等级发送响应,见下面表格的描述。

表格 3.4 – PUBLISH报文的预期响应
服务质量等级 预期响应
QoS 0 无响应
QoS 1 PUBACK报文
QoS 2 PUBREC报文

PUBLISH 响应

不同的Qos 等级会导致不同的通信流程。

关于 QoS 等级 与 流程可以参考 :《Qos等级 与 会话》
PUBLISH报文 中的 Packet Identifier 是什么,下面 的 Packet Identifier便是什么。

PUBACK - 发布确认报文 (QoS 1)

PUBACK 报文 比较简单,它是对QoS 1等级的PUBLISH报文的响应。

PUBACK 报文的 组成 (没有 有效载荷) = 一个固定头(0x40 0x02) + Packet Identifier (from PUBLISH's Packet Identifier)。

PUBREC – 发布收到报文 (QoS 2,第一步)

PUBREC报文是对QoS等级2的PUBLISH报文的响应。

PUBREC 报文的 组成 (没有 有效载荷) = 一个固定头(0x50 0x02) + Packet Identifier (from PUBLISH's Packet Identifier)。

PUBREL – 发布释放(QoS 2,第二步)

PUBREL报文是对PUBREC报文的响应。

PUBREL 报文的 组成 (没有 有效载荷) = 一个固定头(0x52 0x02) + Packet Identifier (from PUBLISH's Packet Identifier)。

PUBREL控制报文固定报头的第3,2,1,0位是保留位,必须被设置为0,0,1,0。

PUBCOMP – 发布完成(QoS 2,第三步)

PUBCOMP报文是对PUBREL报文的响应。

PUBCOMP 报文的 组成 (没有 有效载荷) = 一个固定头(0x70 0x02) + Packet Identifier (from PUBLISH's Packet Identifier)。

posted @ 2020-02-05 11:23  schips  阅读(4362)  评论(0编辑  收藏  举报