MQTT3.1.1解析

MQTT 协议其实有用了好久，最近正好有机会对整个协议系统的熟悉以下。主要参考了 MQTT3.1.1 协议中英文的介绍文档。

mqtt-v3.1.1-errata01-os-complete.pdf

第一章 - MQTT 介绍・MQTT 协议中文版

应用消息 Application Message MQTT 协议通过网络传输应用数据。应用消息通过 MQTT 传输时，它们有关联的服务质量（QoS）和主题（Topic）。

客户端 Client：发布消息，订阅请求。

服务器 Server：接收、发布消息，处理订阅。

订阅包含一个主题过滤器（Topic Filter）和一个最大的服务质量（QoS）等级。

控制报文格式

MQTT 协议通过交换预定义的 MQTT 控制报文来通信。

控制报文主要包括：固定报头、可变报头（部分包含）和有效载荷（部分包含）。

控制报文类型

名字 | 值 | 报文流动方向 | 描述
Reserved | 0 | 禁止 | 保留
CONNECT | 1 | 客户端到服务端 | 客户端请求连接服务端
CONNACK | 2 | 服务端到客户端 | 连接报文确认
PUBLISH | 3 | 两个方向都允许 | 发布消息
PUBACK | 4 | 两个方向都允许 | QoS 1 消息发布收到确认
PUBREC | 5 | 两个方向都允许 | 发布收到（保证交付第一步）
PUBREL | 6 | 两个方向都允许 | 发布释放（保证交付第二步）
PUBCOMP | 7 | 两个方向都允许 | QoS 2 消息发布完成（保证交互第三步）
SUBSCRIBE | 8 | 客户端到服务端 | 客户端订阅请求
SUBACK | 9 | 服务端到客户端 | 订阅请求报文确认
UNSUBSCRIBE | 10 | 客户端到服务端 | 客户端取消订阅请求
UNSUBACK | 11 | 服务端到客户端 | 取消订阅报文确认
PINGREQ | 12 | 客户端到服务端 | 心跳请求
PINGRESP | 13 | 服务端到客户端 | 心跳响应
DISCONNECT | 14 | 客户端到服务端 | 客户端断开连接
Reserved | 15 | 禁止 | 保留

固定报头

固定报头为两个字节。

第一个字节：高四位为控制报文类型，而低四位为对应控制报文类型的标志位

第二个字节（剩余长度）：表示当前报文剩余部分的字节数，包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。

可变报头

位于固定报头与负载之间。很多控制报文的可变报头部分包含一个两字节的报文标识符字段。

有效载荷

位于报文最后。

MQTT 运行流程

客户端连接

客户端 向 服务端 发送 connect 报文

包含 客户端标识符（必须）、用户名、密码、协议级别以及遗嘱信息等

服务端 向 客户端 发送 CONNACK 确认连接请求

订阅主题

客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅（支持通配符 ）。SUBSCRIBE 报文也（为每个订阅）指定了最大的 QoS 等级，服务端根据这个发送应用消息给客户端。

SUBACK 用来进行订阅 确认。SUBACK报文会为 SUBSCRIBE报文中的每个主题过滤器返回一个授予的 QoS 等级（这个等级可能等于或小于客户端请求的最大 QoS，但不会高于它）。

PUBLISH 发送消息

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

服务端使用 PUBLISH 报文发送应用消息给每一个订阅匹配的客户端。

可变报头中包含了 主题名 和 报文标识符，有效载荷中包含将要被发布的消息。

QOS 为 0 时，无确认流程。

当 QOS 为 1 时，需要通过 PUBACK 进行发布确认

当 QOS 为 2 时，首先是通过 PUBREC 进行发布收到确认，之后使用 PUBREL 进行发布释放确认，最后使用 PUBCOMP 进行发布完成确认

取消订阅

使用 UNSUBSCRIBE 取消订阅

使用 UNSUBACK 确认收到 UNSUBSCRIBE 报文。

断开连接

客户端发送 DISCONNECT 报文，断开与服务端连接。

控制报文

CONNECT – 连接服务端

客户端到服务端的网络连接建立后，客户端发送给服务端的第一个报文必须是 CONNECT 报文

在一个网络连接上，客户端只能发送一次 CONNECT 报文。

有效载荷包含一个或多个编码的字段。包括客户端的唯一标识符，Will 主题，Will 消息，用户名和密码。

固定报头

第一字节： 报文类型 0001 低四位作为保留位

剩余长度等于可变报头的长度（10 字节）加上有效载荷的长度。

可变报头

CONNECT 报文的可变报头按下列次序包含四个字段：协议名（Protocol Name），协议级别（Protocol Level），连接标志（Connect Flags）和保持连接（Keep Alive）。

协议名：表示 MQTT 的编码

协议级别：表示协议版本

连接标志 Connect Flags

清理会话 Clean Session为

为 0 时：服务端必须基于当前会话（使用客户端标识符识别）的状态恢复与客户端的通信。

为 1 时：断开之后，旧的会话会被丢弃。

要确保不丢失连接断开期间的消息，需要使用 QoS 1 或 QoS 2 级别，同时将清理会话标志设置为 0。

** 遗嘱标志：** 服务端是否需要发布遗嘱消息

** 遗嘱 QOS：** 遗嘱消息 QOS 等级

** 遗嘱保留：** 是否将遗嘱消息作为保留消息发布

** 用户名标志：** 有效载荷中是否包含用户名

** 密码标志：** 有效载荷中是否包含密码

保持连接

保持连接（Keep Alive）是一个以秒为单位的时间间隔。

指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻，两者之间允许空闲的最大时间间隔。期间如果没有任何其它的控制报文可以发送，客户端必须发送一个 PINGREQ 报文

如果保持连接的值非零，并且服务端在一点五倍的保持连接时间内没有收到客户端的控制报文，它必须断开客户端的网络连接，认为网络连接已断开。

客户端发送了 PINGREQ 报文之后，如果在合理的时间内仍没有收到 PINGRESP 报文，它应该关闭到服务端的网络连接。

保持连接的值为零表示关闭保持连接功能。

有效载荷

CONNECT 报文的有效载荷（payload）包含一个或多个以长度为前缀的字段，可变报头中的标志决定是否包含这些字段。如果包含的话，必须按这个顺序出现：客户端标识符，遗嘱主题，遗嘱消息，用户名，密码。

客户端标识符

服务端使用客户端标识符 (ClientId) 识别客户端。连接服务端的每个客户端都有唯一的客户端标识符（ClientId）。客户端和服务端都必须使用 ClientId 识别两者之间的 MQTT 会话相关的状态。

客户端标识符 (ClientId) 必须存在而且必须是 CONNECT 报文有效载荷的第一个字段。

遗嘱主题

如果遗嘱标志被设置为 1，有效载荷中的下一个字段是遗嘱主题（Will Topic）。 之后是遗嘱消息，根据是否含有标志包括了用户名和密码。

响应

服务器可以在同一个 TCP 端口或其他网络端点上支持多种协议 。

服务端必须发送返回码为零的 CONNACK 报文作为 CONNECT 报文的确认响应 。但客户端可以在收到 CONNACK 之前发送控制报文 。

CONNACK – 确认连接请求

服务端发送 CONNACK 报文响应从客户端收到的 CONNECT 报文。服务端发送给客户端的第一个报文必 须是 CONNACK 。

固定报头

可变报头

连接确认标志

第 0 (SP) 位 是当前会话（Session Present）标志。 其他（１－７）位应为０

如果服务端收到清理会话（CleanSession）标志为 1 的连接，除了将 CONNACK 报文中的返回码设置为 0 之外，还必须将 CONNACK 报文中的当前会话设置（Session Present）标志为 0

当服务端收到一个 CleanSession 为 0 的连接请求时，需要根据是否已经保存了该客户端（通过 ClientId 识别）的会话信息来决定 CONNACK 响应中的当前会话标志。如果之前有保存过这个客户端的会话信息，那么在 CONNACK 里就把当前会话标志设为 1；如果没有保存过，则设为 0。另外，不管哪种情况，都要确保 CONNACK 里的返回码是 0。

连接返回码

一个无符号值，0 表示正常连接；非 0 值则表明连接被拒绝

无有效载荷

Publish - 发布消息

固定报头

重发标志 DUP: 0 第一次请求发送，1 表示重发报文.

发送（出站）的 PUBLISH 报文与收到（入站）的 PUBLISH 报文中的 DUP 标志是独立设置的，它的值必须 单独的根据发送（出站）的 PUBLISH 报文是否是一个重发来确定 .

服务质量等级 QOS

保留标志 RETAIN

如果客户端发给服务端的 PUBLISH 报文的保留（RETAIN）标志被设置为 1，服务端必须存储这个应用消 息和它的服务质量等级（QoS），以便它可以被分发给未来的主题名匹配的订阅者

一个 新的订阅建立时，对每个匹配的主题名，如果存在最近保留的消息，它必须被发送给这个订阅者

如果服务端收到一条保留（RETAIN）标志为 1 的 QoS 0 消息，它必须丢弃之前为那个主题保留 的任何消息。它应该将这个新的 QoS 0 消息当作那个主题的新保留消息，但是任何时候都可以选择丢弃它 — 如果这种情况发生了，那个主题将没有保留消息

服务端发送PUBLISH 报文给客户端时，如果消息是作为客户端一个新订阅的结果发送，它必须将 ** 报文的保 留标志设为 1 **

剩余长度字段

可变报头的长度加上有效载荷的长度

可变报头

包含主题名和报文标识符。

主题名

可变报头的第一个字段

主题名不能包含通配符

报文标识符

QOS 为 1 或 2 时存在

有效载荷

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

响应

接收者与发布者的 QOS 等级应相同

动作

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

服务端使用 PUBLISH 报文发送应用消息给每一个订阅匹配的客户端。

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

服务端之后可以按照订阅的 QoS 等级，分发消息的副本给每一个匹配的订阅者。

PUBACK –发布确认

QoS 1 等级的 PUBLISH 报文的响应。

固定报头

MQTT 报文控制类型为 4

剩余长度字段为 2

可变报头

包含等待确认的 PUBLISH 报文的报文标识符。

无有效载荷

PUBREC – 发布收到（QoS 2，第一步）

QoS 等级 2 的 PUBLISH 报文的响应。 它是 QoS 2 等级协议交换的第二个报文。

固定报头
MQTT 报文控制类型为 5
剩余长度字段为 2

可变报头

包含等待确认的 PUBLISH 报文的报文标识符。

无有效载荷

PUBREL – 发布释放（QoS 2，第二步）

对 PUBREC 报文的响应。它是 QoS 2 等级协议交换的第三个报文。

固定报头

MQTT 报文控制类型为 5

剩余长度字段为 2

可变报头

与等待确认的 PUBREC 报文相同的报文标识符。

无有效载荷

PUBCOMP – 发布完成（QoS 2，第三步）

对 PUBREL 报文的响应。它是 QoS 2 等级协议交换的第四个也是最后一个报文。

固定报头
MQTT 报文控制类型为 7
剩余长度字段为 2
可变报头

与等待确认的 PUBREC 报文相同的报文标识符。
无有效载荷

SUBSCRIBE - 订阅主题

客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅。SUBSCRIBE 报文也 **（为每个订阅）指定了最大的 QoS 等级 **，服务端根据这个发送应用消息给客户端。

固定报头
MQTT 报文控制类型为 5
剩余长度字段为 可变报头 + 有效载荷 的长度

可变报头

可变报头包含客户端标识符。

有效载荷

** 主题过滤器 和 QoS 等级字段组合 **

表示客户端想要订阅的主题。 服务端应该 ** 支持 包含通配符（4.7.1 节定义的）的主题过滤器。** 如果服务端选择不支持包含通配符的主题过滤器，必须拒绝 任何包含通配符过滤器的订阅请求 。

每一个过滤器后面跟着一个字节，这个字节被叫做 服 务质量要求（Requested QoS）。它给出了服务端向客户端发送应用消息所允许的最大 QoS 等级。 有效载荷必须包含至少一对主题过滤器 和 QoS 等级字段组合。

没有有效载荷的 SUBSCRIBE 报文是违反协议的 。

响应

服务端收到客户端发送的一个 SUBSCRIBE 报文时，必须使用 SUBACK 报文响应，SUBACK 报文必须和等待确认的 SUBSCRIBE 报文有相同的报文标识符

新订阅的主题过滤器和之前订阅的相同，但是它的最大 QoS 值可以不 同。与这个主题过滤器匹配的任何现存的保留消息必须被重发，但是发布流程不能中断 。

如果服务端收到包含多个主题过滤器的 SUBSCRIBE 报文，它必须如同收到了一系列的多个 SUBSCRIBE 报文一样处理那个，除了需要将它们的响应合并到一个单独的 SUBACK 报文发送 SUBACK 响应可以合并

SUBACK 报文对每一对主题过滤器 和 QoS 等级都必须包含一个返回码。

服务端可以授予比 订阅者要求的低一些的 QoS 等级。为响应订阅而发出的消息的有效载荷的 QoS 必须是原始发布消息的 QoS 和服务端授予的 QoS 两者中的最小值。

** 确定消息分发时可能的最大 QoS 等级是发布者的责任，而订阅者可以 要求服务端降低 QoS 到更适合它的等级。 **

SUBACK – 订阅确认

用于确认它已收到并且正在处理 SUBSCRIBE 报文。 包含一个返回码清单，它们指定了 SUBSCRIBE 请求的每个订阅被授予的最大 QoS 等级。

固定报头

MQTT 报文控制类型为 5

剩余长度字段为 可变报头 + 有效载荷 的长度

可变报头

包含等待确认的 SUBSCRIBE 报文的报文标识符。

有效载荷

包含一个返回码清单。每个返回码对应等待确认的 SUBSCRIBE 报文中的一个主题过滤器。返回 码的顺序必须和 SUBSCRIBE 报文中主题过滤器的顺序相同

允许的返回码值：

0x00 - 最大 QoS 0

0x01 - 成功 – 最大 QoS 1

0x02 - 成功 – 最大 QoS 2

0x80 - Failure 失败

UNSUBSCRIBE –取消订阅

用于取消订阅主题

固定报头

控制报文类型 10

保留位 0，0，1，0

** 剩余长度字段 **可变报头 + 有效载荷 的长度

可变报头

有效载荷

包含客户端想要取消订阅的主体过考虑列表。

Response 响应

如果服务端删除了一个订阅：

停止分发任何新消息给这个客户端

完成分发任何已经开始往客户端发送的 QoS 1 和 QoS 2 的消息

继续发送任何现存的准备分发给客户端的缓存消息。

服务端必须发送 UNSUBACK 报文响应客户端的 UNSUBSCRIBE 请求。

UNSUBACK – 取消订阅确认

用于确认收到 UNSUBSCRIBE 报文。

固定报头

控制报文类型 11

剩余长度 2

可变报头

有效载荷

无有效载荷

PINGREQ – 心跳请求

告知服务端客户端还活着。

请求服务端发送 响应确认它还活着。

使用网络以确认网络连接没有断开。

固定报头

控制报文类型 12

剩余长度 0

无 可变报头，有效载荷

响应

服务端必须发送 PINGRESP 报文响应客户端的 PINGREQ 报文

PINGRESP – 心跳响应

发送 PINGRESP 报文响应客户端的 PINGREQ 报文。表示服务端还活着。

固定报头

控制报文类型 13

剩余长度 0
无 可变报头，有效载荷

DISCONNECT –断开连接

控制报文类型 14

保留位 0

剩余长度 0 无 可变报头，有效载荷

Response

客户端发送 DISCONNECT 报文之后：

必须关闭网络连接

不能通过那个网络连接再发送任何控制报文

服务端收到 到 DISCONNECT 报文时：

必须丢弃任何与当前连接关联的未发布的遗嘱消息

应该关闭网络连接，如果客户端 还没有这么做。

操作行为

MQTT 3.1 使用的传输层协议是 [RFC793] 定义的 TCP/IP 协议。同事也支持 TLS、 WebSocket 协议

服务质量等级和协议流程

QoS 0: 最多分发一次

消息的分发依赖于底层网络的能力。接收者不会发送响应，发送者也不会重试。

QoS 1: 至少分发一次

QoS 1 的 PUBLISH 报文的可变报头中包含一个报文标识符，需要 PUBACK 报文确认。

发送者：

每次发送新的应用消息都必须分配一个未使用的报文标识符。

发送的 PUBLISH 报文必须包含报文标识符且 QoS 等于 1，DUP 等于 0。

必须将这个 PUBLISH 报文看作是 未确认的 ，直到从接收者那收到对应的 PUBACK 报文。

接收者 ：

响应的 PUBACK 报文必须包含一个报文标识符，这个标识符来自接收到的、已经接受所有权的 PUBLISH 报文。

发送了 PUBACK 报文之后，接收者必须将任何包含相同报文标识符的入站 PUBLISH 报文当作一 个新的消息，并忽略它的 DUP 标志的值。

QoS 2: 仅分发一次

消息丢失和重复都是不可接受的。

发送者：

必须给要发送的新应用消息分配一个未使用的报文标识符。

发送的 PUBLISH 报文必须包含报文标识符且报文的 QoS 等于 2,，DUP 等于 0。

** 必须将这个 PUBLISH 报文看作是 未确认的 ，直到从接收者那收到对应的 PUBREC 报文。 **

收到 PUBREC 报文后必须发送一个 PUBREL 报文。PUBREL 报文必须包含与原始 PUBLISH 报文 相同的报文标识符。

** 必须将这个 PUBREL 报文看作是 未确认的 ，直到从接收者那收到对应的 PUBCOMP 报文。 **

** 一旦发送了对应的 PUBREL 报文就不能重发这个 PUBLISH 报文。**

响应者：

响应的 PUBREC 报文必须包含报文标识符，这个标识符来自接收到的、已经接受所有权的 PUBLISH 报文。

在收到对应的 PUBREL 报文之前，接收者必须发送 PUBREC 报文确认任何后续的具有相同标识符 的 PUBLISH 报文。 ** 在这种情况下，它不能重复分发消息给任何后续的接收者。 **

** ** 响应 PUBREL 报文的 PUBCOMP 报文必须包含与 PUBREL 报文相同的标识符。

发送 PUBCOMP 报文之后，接收者必须将包含相同报文标识符的任何后续 PUBLISH 报文当作一 个新的发布。

消息分发重试

客户端和服务端必须使用原始的报文标识符重发 任何未确认的 PUBLISH 报文（如果 QoS>0）和 PUBREL 报文 。 这是唯一要求客户端或 服务端重发消息的情况。

主题名和主体过滤器

主题通配符

斜杠（‘/’ U+002F）用于分割主题的每个层级，为主题名提供一个分层结构。

多层通配符

数字标志（‘#’ U+0023）是用于匹配主题中任意层级的通配符。

单层通配符