- 第一章 - 介绍
- 第二章 – MQTT控制报文格式
- 第三章 – MQTT控制报文
- 第四章 – 操作行为
- 第五章 – 安全(非规范)
- 第六章 – 使用WebSocket作为网络层
- 第七章 – 一致性
- 附录B - 强制性规范声明(非规范)
- 附录C - MQTT v5.0新特性总结(非规范)
为实现QoS等级1和QoS等级2协议流,客户端和服务端需要将状态与客户标识符相关联,这被称为会话状态。服务端还将订阅信息存储为会话状态的一部分。
会话可以跨越一系列的网络连接。它持续到最新的网络连接(Network Connections)加上会话过期间隔(Session Expiry Interval)。
客户端的会话状态包括:
- 已发送给服务端,但是还没有完成确认的QoS等级1和QoS等级2的消息。
- 从服务端收到的,但是还没有完成确认的QoS等级2消息。
服务端的会话状态包括:
- 会话是否存在,即使会话状态其余部分为空。
- 客户端订阅信息,包括任何订阅标识符。
- 已发送给客户端,但是还没有完成确认的QoS等级1和QoS等级2的消息。
- 等待传输给客户端的QoS等级0(可选),QoS等级1和QoS等级2的消息。
- 从客户端收到的,但是还没有完成确认的QoS等级2消息。遗嘱小子和遗嘱延时间隔。
- 如果会话当前未连接,会话结束时间和会话状态将被丢弃。
保留消息不是会话状态的一部分,会话结束时不被删除。
当网络连接打开时,客户端和服务端不能丢弃会话状态 [MQTT-4.1.0-1]。当网络连接被关闭并且会话过期间隔已过时,服务端必须丢弃会话状态 [MQTT-4.1.0-2]。
非规范评注
客户端和服务端实现的存储容量必然是有限的,还可能要受管理策略的限制。已存储的会话状态可能因为管理操作(比如某个预定义条件的自动响应)而被丢弃。它造成的后果就是会话终止。这些操作可能是因为资源受限或其他操作原因引发的。硬件或软件故障可能导致客户端或服务端存储的会话状态丢失或损坏。需要谨慎的评估客户端和服务端的存储能力,以确保存储空间充足。
例如,想要收集电表读数的用户可能会决定使用QoS等级1的消息,因为他们不能接受数据在网络传输途中丢失,但是,他们可能认为客户端和服务端的数据可以存储在内存(易失性存储器)中,因为(他们觉得)电力供应是非常可靠的,不会有太大的数据丢失风险。
与之相反,停车计费支付应用的提供商可能决定任何情况下都不能让数据支付消息丢失,因此他们要求在通过网络传输之前将所有的数据写入到非易失性存储器中(如硬盘)。
MQTT协议要求基础传输层能够提供有序的、可靠的、双向传输(从客户端到服务端和从服务端到客户端)字节流。此规范不要求任何指定的传输协议。客户端或服务端可以支持这里列出的任何传输协议,或者满足本节要求的任何其他传输协议。
客户端或服务端必须支持使用一个或多个提供有序的、可靠的、双向传输(从客户端到服务端和从服务端到客户端)字节流传输的底层传输协议 [MQTT-4.2-1]。
非规范评注
MQTT 3.1使用的传输层协议是 [RFC793] 定义的TCP/IP协议。下面的协议也支持:
非规范评注
TCP端口8883和1883已在IANA注册,分别用于MQTT的TLS和非TLS通信。
非规范评注
无连接的网络传输,如用户数据包协议 (UDP) 本身不适合,因为它们可能丢失或重新排列数据。
MQTT按照后面章节定义的服务质量(QoS)等级分发应用消息。分发协议是对称的,在下面的描述中,客户端和服务端既可以是发送端也可以是接收端。分发协议关注的是从单个发送者到单个接收者的应用消息。服务端分发应用消息给多个客户端时,每个客户端独立处理。分发给客户端的出站应用消息和入站应用消息的QoS等级可能是不同的。
消息的分发依赖于底层网络的能力。接收端不会发送响应,发送端也不会重试。消息可能送达一次也可能根本没送达。
对于QoS等级0的分发协议,发送端
- 必须发送QoS等于0,DUP等于0的PUBLISH报文 [MQTT-4.3.1-1]。
对于QoS等级0的分发协议,接收端
- 接受PUBLISH报文时同时接受消息的所有权。
发送端动作 | 控制报文 | 接收端动作 |
---|---|---|
PUBLISH报文QoS 0, DUP=0 | ||
----------> | ||
分发应用消息给适当的后续接收者(们) |
服务质量等级1确保消息至少送达一次。QoS等级1的PUBLISH报文的可变报头中包含一个报文标识符,需要PUBACK 报文确认。2.2.1节提供了有关报文标识符的更多信息。
对于QoS等级1的分发协议,发送端
- 每次发送新的应用消息都必须分配一个未使用的报文标识符 [MQTT-4.3.2-1]。
- 发送的PUBLISH报文必须包含报文标识符且QoS等于1,DUP等于0 [MQTT-4.3.2-2]。
- 必须将这个PUBLISH报文看作是未确认的 ,直到从接收端那收到对应的PUBACK报文。 4.4节有一个关于未确认消息的讨论 [MQTT-4.3.2-3]。
一旦发送端收到PUBACK报文,这个报文标识符就可以重用。
注意:允许发送端在等待确认时使用不同的报文标识符发送后续的PUBLISH报文。
对于QoS等级1的分发协议,接收端
- 响应的PUBACK报文必须包含一个报文标识符,这个标识符来自接收到的、已经接受所有权的PUBLISH报文 [MQTT-4.3.2-4]。
- 发送了PUBACK报文之后,接收端必须将任何包含相同报文标识符的入站PUBLISH报文当做一个新的消息,并忽略它的DUP标志的值 [MQTT-4.3.2-5]。
发送端动作 | 控制报文 | 接收端动作 |
---|---|---|
存储消息 | ||
发送PUBLISH报文QoS=1,DUP=0,带报文标识符 | ----------> | |
开始应用消息的后续分发1 | ||
<---------- | 发送PUBACK报文,带报文标识符 | |
丢弃消息 |
1不要求接收端在发送 PUBACK 之前完整分发应用消息。原来的发送端收到 PUBACK 报文之后,应用消息的所有权就会转移给这个接收端。
这是最高等级的服务质量,消息丢失和重复都是不可接受的。使用这个服务质量等级会有额外的开销。
QoS等2消息可变报头中有报文标识符。2.2.1节 提供了有关报文标识符的更多信息。QoS等级2的PUBLISH报文的接收端使用一个两部确认过程来确认收到。
对于QoS等级2的分发协议,发送端
- 必须给要发送的新应用消息分配一个未使用的报文标识符 [MQTT-4.3.3-1]。
- 发送端PUBLISH报文必须包含报文标识符且报文的QoS等于2,DUP等于0 [MQTT-4.3.3-2]。
- 必须将这个PUBLISH报文看作是未确认的,直到从接收端那收到对应的PUBREC报文 [MQTT-4.3.3-3]。4.4节有一个关于未确认消息的讨论。
- 收到发送端发送的包含原因码小于0x80的PUBREC报文后必须发送一个PUBREL报文。PUBREL报文必须包含与原始PUBLISH报文相同的报文标识符 [MQTT-4.3.3-4]。
- 必须将这个PUBREL报文看作是未确认的 ,直到从接收端那收到对应的PUBCOMP报文 [MQTT-4.3.3-5]。
- 一旦发送了对应的PUBREL报文就不能重发这个PUBLISH报文 [MQTT-4.3.3-6]。
- 如果PUBLISH报文已发送,不能应用消息过期属性 [MQTT-4.3.3-7]。
一旦发送端收到包含原因码大于0x80的PUBCOMP报文,这个报文标识符就可以重用。
注意:允许发送端在等待确认时使用不同的报文标识符发送后续的PUBLISH报文,受制于4.9节描述的流量控制。
对于QoS等级2的分发协议,接收端
- 响应的PUBREC报文必须包含报文标识符,这个标识符来自接收到的、已经接受所有权的PUBLISH报文 [MQTT-4.3.3-8]。
- 如果接收端发送了包含原因码大于等于0x80的PUBREC报文,它必须将后续包含相同报文标识符的PUBLISH报文当做是新的应用消息 [MQTT-4.3.3-9]。
- 在收到对应的PUBREL报文之前,接收端必须发送PUBREC报文确认任何后续的具有相同报文标识符的PUBLISH报文。在这种情况下,它不能重复分发消息给任何后续的接收者 [MQTT-4.3.3-10]。
- 必须发送包含与PUBREL相同报文标识符的PUBCOMP报文作为对PUBREL报文的响应 [MQTT-4.3.3-11]。
- 发送PUBCOMP报文之后,接收端必须将后续包含相同报文标识符的PUBLISH报文当做是新的应用消息 [MQTT-4.3.3-12]。
- 必须继续QoS等级2确认序列,即使它已经应用了消息过期属性 [MQTT-4.3.3-13]。
客户端以新开始(Clean Start)标志为0且会话存在的情况下重连时,客户端和服务端都必须使用原始报文标识符重新发送任何未被确认的PUBLISH报文(当QoS > 0)和PUBREL报文。这是唯一要求客户端或服务端重发消息的情况。客户端和服务端不能在其他任何时间重发消息 [MQTT-4.4.0-1]。
如果收到包含原因码大于等于0x80的PUBACK或PUBREC,则对应的PUBLISH报文被看作已确认,且不能被重传 [MQTT-4.4.0-2]。
发送端动作 | 控制报文 | 接收端动作 |
---|---|---|
存储消息 | ||
发送PUBLISH报文QoS=2,DUP=0,带报文标识符 | ||
----------> | ||
存储报文标识符,然后启动应用消息的向前分发1 | ||
发送PUBREC报文,带报文标识符和原因码 | ||
<---------- | ||
丢弃消息,存储PUBREC中的报文标识符 | ||
发送PUBREL报文,带报文标识符 | ||
----------> | ||
丢弃报文标识符 | ||
发送PUBCOMP报文,带报文标识符 | ||
<---------- | ||
丢弃已保存的状态 |
1 不要求接收端在发送PUBREC和PUBCOMP之前完整分发应用消息。原始发送端收到PUBREC报文之后,应用消息的所有权就会转移给这个接收端。然而,接收端需要在接受所有权之前执行对所有可能导致转发失败(例如超出配额、权限等)的条件的检查。接收端在PUBREC中使用适当的原因码指示所有权接受成功或失败。
当服务端接受入站应用消息的所有权时,它必须将消息添加到订阅匹配的客户端的会话状态中 [MQTT-4.5.0-1]。匹配规则定义见 4.7节 。
正常情况下,客户端收到的消息是对他们创建的订阅的响应。客户端也可能收到不是与它的订阅精确匹配的消息。如果服务端自动给客户端分配了一个订阅,可能发生这种情况。 UNSUBSCRIBE 操作正在被处理时也可能收到消息。客户端必须按照可用的服务质量(QoS)规则确认它收到的任何PUBLISH报文,不管它是否选择处理其包含的应用消息 [MQTT-4.5.0-2]。
实现4.3节定义的协议流程时,客户端必须遵循下列规则
- 重发任何之前的PUBLISH报文时,必须按原始PUBLISH报文的发送顺序重发(适用于QoS等级1和QoS等级2消息)[MQTT-4.6.0-1]。
- 必须按照对应的PUBLISH报文的顺序发送PUBACK报文(QoS等级1消息)[MQTT-4.6.0-2]。
- 必须按照对应的PUBLISH报文的顺序发送PUBREC报文(QoS等级2消息)[MQTT-4.6.0-3]。
- 必须按照对应的PUBREC报文的顺序发送PUBREL报文(QoS等级2消息)[MQTT-4.6.0-4]。
一个有序主题(Ordered Topic)是一个主题,在这个主题中,客户端可以确定从同一个客户端接收的相同QoS等级的消息的顺序与他们发布的顺序一致。当服务端处理发布到有序主题的消息时,它必须按照消息从任何给定客户端接收的顺序发送PUBLISH报文给消费端(对于同一主题和QoS等级) [MQTT-4.6.0-5]。这是上面列出的规则的补充。
服务端处理发送给有序主题的消息时,必须按照上面的规则将消息分发给每个订阅者。此外,它必须按照从客户端收到的顺序发送PUBLISH报文给消费者(对相同的主题和QoS)[MQTT-4.6.0-6]。
默认情况下,服务端转发非共享订阅的消息时,必须将每个主题都视为有序主题 [MQTT-4.6.0-6]。服务端可以提供管理或其他机制来允许一个或多个主题不被当作有序主题。
非规范评注
上面列出的规则确保,使用QoS等级1发布和订阅的消息流,订阅者按照消息发布时的顺序收到每条消息的最终副本,但是消息可能会重复,这可能导致在它的后继消息之后收到某个已经收到消息的重发版本。例如,发布者按顺序1,2,3,4发送消息,订阅者收到的顺序可能是1,2,3,2,3,4。
如果客户端和服务端能保证任何时刻最多有一条消息在 传输中(in-flight)(在某条消息被确认前不发送后面的那条消息),那么,不会有QoS等级1的消息会在它的任何后续消息之后收到。例如,订阅者收到的顺序可能是 1,2,3,3,4,而不是 1,2,3,2,3,4。关于如何使用Receive Maximum的详细信息,参考4.9节流控。
主题层级(topic level)分隔符用于将结构化引入主题名。如果存在分隔符,它将主题名分割为多个主题层级 topic level 。
订阅的主题过滤器可以包含特殊的通配符,允许客户端一次订阅多个主题。
主题过滤器中可以使用通配符,但是主题名不能使用通配符 [MQTT-4.7.0-1]。
斜杠(“/” U+002F)用于分割主题的每个层级,为主题名提供一个分层结构。当客户端订阅指定的主题过滤器包含两种通配符时,主题层级分隔符就很有用了。主题层级分隔符可以出现在主题过滤器或主题名字的任何位置。相邻的主题层次分隔符表示一个零长度的主题层级。
数字符号(“#” U+0023)是用于匹配主题中任意层级的通配符。多层通配符表示它的父级和任意数量的子层级。多层通配符必须单独指定,或者跟在主题层级分隔符后面。不管哪种情况,它都必须是主题过滤器的最后一个字符 [MQTT-4.7.1-1]。
非规范评注
例如,如果客户端订阅主题 “sport/tennis/player1/#”,它会收到使用下列主题名发布的消息:
- “sport/tennis/player1”
- “sport/tennis/player1/ranking”
- “sport/tennis/player1/score/wimbledon”
非规范评注
- “sport/#”也匹配单独的 “sport” 主题名,因为#包括它的父级。
- “#”是有效的,会收到所有的应用消息。
- “sport/tennis/#”也是有效的。
- “sport/tennis#”是无效的。
- “sport/tennis/#/ranking”是无效的。
加号(“+” U+002B) 是只能用于单个主题层级匹配的通配符。
在主题过滤器的任意层级都可以使用单层通配符,包括第一个和最后一个层级。在使用它时,它必须占据过滤器的整个层级 [MQTT-4.7.1-2]。可以在主题过滤器中的多个层级中使用它,也可以和多层通配符一起使用。
非规范评注
例如,“sport/tennis/+”匹配“sport/tennis/player1”和“sport/tennis/player2”,但是不匹配“sport/tennis/player1/ranking”。同时,由于单层通配符只能匹配一个层级,“sport/+”不匹配“sport”但是却匹配 “sport/”。
- “+” 是有效的。
- “+/tennis/#” 是有效的。
- “sport+” 是无效的。
- “sport/+/player1” 也是有效的。
- “/finance” 匹配 “+/+” 和 “/+” ,但是不匹配 “+”。
服务端不能将$字符开头的主题名匹配通配符(#或+)开头的主题过滤器 [MQTT-4.7.2-1]。服务端应该阻止客户端使用这种主题名与其它客户端交换消息。服务端实现可以将$开头的主题名用作其他目的。
非规范评注
- $SYS/被广泛用作包含服务器特定信息或控制接口的主题的前缀。
- 应用不能使用$字符开头的主题。
非规范评注
- 订阅“#”的客户端不会收到任何发布到以“$”开头主题的消息。
- 订阅“+/monitor/Clients”的客户端不会收到任何发布到“$SYS/monitor/Clients”的消息。
- 订阅“$SYS/#”的客户端会收到发布到以“$SYS/”开头主题的消息。
- 订阅“$SYS/monitor/+” 的客户端会收到发布到“$SYS/monitor/Clients”主题的消息。
- 如果客户端想同时接受以“$SYS/”开头主题的消息和不以$开头主题的消息,它需要同时订阅“#”和“$SYS/#”。
下列规则应用于主题名和主题过滤器:
- 所有的主题名和主题过滤器必须至少包含一个字符 [MQTT-4.7.3-1]。
- 主题名和主题过滤器是大小写敏感的。
- 主题名和主题过滤器可以包含空格字符。
- 主题名或主题过滤器以前置或后置斜杠“/”区分。
- 只包含斜杠“/”的主题名或主题过滤器是合法的。
- 主题名和主题过滤器不能包含空字符(Unicode U+0000) [Unicode] [MQTT-4.7.3-2]。
- 主题名和主题过滤器是UTF-8编码字符串,它们不能超过65535字节 [MQTT-4.7.3-3]。见1.5.4节。
除了不能超过UTF-8编码字符串的长度限制之外,主题名或主题过滤器的层级数量没有其它限制。
匹配订阅时,服务端不能对主题名或主题过滤器执行任何规范化(normalization)处理,不能修改或替换任何未识别的字符 [MQTT-4.7.3-4]。主题过滤器中的每个非通配符层级需要逐字符匹配主题名中对应的层级才算匹配成功。
非规范评注
使用UTF-8编码规则意味着,主题过滤器和主题名的比较可以通过比较编码后的UTF-8字节或解码后的Unicode字符。
非规范评注
- “ACCOUNTS”和“Accounts”是不同的主题名。
- “Accounts payable”是合法的主题名
- “/finance”和“finance”是不同的。
如果订阅的主题过滤器与消息的主题名匹配,应用消息会被发送给每一个匹配的客户端订阅。主题资源可以是管理员在服务端预先定义好的,也可以是服务端收到第一个订阅或使用那个主题名的应用消息时动态添加的。服务端也可以使用一个安全组件有选择地授权客户端使用某个主题资源。
MQTT提供两种订阅方式,共享和非共享。
非规范评注
在早期的MQTT版本中,所有的订阅都是非共享的。
非共享订阅只与创建它的会话相关联。每个订阅(Subscription)包含一个指示用于在此会话上分发消息的主题过滤器和订阅选项。服务端负责收集与过滤器相匹配的消息,并在此会话的连接上发送这些消息。
一个会话不能有多个包含相同主题过滤器的非共享订阅,因此主题过滤器可以用作标识此会话的订阅的关键词。
如果有多个客户端,每个客户端都拥有对某个相同主题的非共享订阅,则每个客户端都将获得在该主题上发布的应用消息的副本。这意味着非共享订阅不能被用于多个消费客户端的应用消息负载均衡,因为在这种情况下,每条消息都将被传递给每一个订阅的客户端。
共享订阅可以与多个订阅会话相关联。与非共享订阅一样,它包含一个主题过滤器和订阅选项。但是,与此主题过滤器相匹配的发布消息仅被发布到其中一个订阅会话。共享订阅在多个消费客户端并行共享处理发布消息时是很有用的。
使用特殊样式的主题过滤器来表示共享订阅。过滤器格式如下:
$share/{ShareName}/{filter}
- $share是字符串字面量,用来把主题过滤器标记为共享订阅主题过滤器。
- {ShareName}是字符串,不包含“/”,“+”或“#”。
- {filter}该字符串的剩余部分与非共享订阅中的主题过滤器具有相同的语法和语义。参考4.7节。
共享订阅主题过滤器必须以$share/开始,且必须包含至少一个字符长度的共享名(ShareName) [MQTT-4.8.2-1]。共享名不能包含字符“/”,“+”或“#”,但必须跟在“/”字符后面。此“/”字符后面必须跟随一个主题过滤器 [MQTT-4.8.2-2],如4.7节所述。
非规范评注
共享订阅在MQTT服务端的范围内定义,而不是在会话中定义。共享订阅的主题过滤器包含共享名,因此服务端可以有多个包含相同{过滤器}组件的共享订阅。通常,应用程序使用共享名表示共享同一个订阅的一组订阅会话。 示例:
- 共享订阅“$share/consumer1/sport/tennis/+”和“$share/consumer2/sport/tennis/+”是不同的共享订阅,因此可以被关联到不同的会话组。它们都与非共享订阅主题“sport/tennis/+”相匹配。
如果一条消息被发布到匹配主题“sport/tennis/+”,则消息的副本仅发送给所有订阅“$share/consumer1/sport/tennis/+”的会话中的一个会话,也仅发送给所有订阅“$share/consumer2/sport/tennis/+”的会话中的一个会话。更多的副本将发送给所有对“sport/tennis/+”进行非共享订阅的客户端。- 共享订阅“$share/consumer1//finance”匹配非共享订阅主题“/finance”。
注意,“$share/consumer1//finance”和“$share/consumer1/sport/tennis/+”是不同的共享订阅,尽管它们有相同的共享名。它们可能在某种程度上是相关的,但拥有相同的共享名并不意味着它们之间有某种关系。
通过SUBSCRIBE请求中的共享订阅主题过滤器创建共享订阅。只有一个会话订阅了某个共享订阅时,共享订阅行为如同非共享订阅,除了:
-
匹配发布消息时,不考虑"$share"和{ShareName}部分。
-
第一次订阅时,保留消息不发送给此会话。其他匹配的发布消息将发送给此会话。
一旦某个共享订阅存在,其他会话就有可能订阅了相同的共享订阅主题过滤器。新的会话作为额外的订阅者关联到此共享订阅。保留消息不发送给此新的订阅者。后续每条与此共享订阅相匹配的应用消息被发送到该共享订阅关联的其中一个会话。
会话可以通过发送包含某共享订阅主题过滤器的UNSUBSCRIBE报文来显式的将其从共享订阅中分离。会话终止时,也将从共享订阅中分离。
共享订阅持续到至少有一个与其相关的会话(即,会话已经对此共享订阅主题过滤器发布了成功的SUBSCRIBE请求,且尚未完成相应的UNSUBSCRIBE)。当初始创建此共享订阅的会话取消订阅时,除非没有其他的相关会话,否则共享订阅仍然存在。共享订阅在没有被任何会话订阅时结束,且任何相关的未分发的消息都被删除。
共享订阅注释
-
如果有不止一个会话订阅了某个共享订阅,服务端在消息的基础上自由的选择使用哪个会话,以及使用什么标准来进行该选择。
-
允许不同的订阅客户端在其SUBSCRIBE报文中请求不同的QoS等级。服务端决定授予每个客户端的最大QoS等级,并且允许向不同的订阅者授予不同的最大QoS等级。向客户端发送应用消息时,服务端必须考虑授予客户端的QoS等级 [MQTT-4.8.2-3],与向订阅者发送消息相同。
-
如果服务端正在向其选中的订阅客户端发送QoS等级2的消息,并且在分发完成之前网络中断,服务端必须在客户端重新连接时完成向该客户端的消息分发 [MQTT-4.8.2-4],如4.3.3节 所述。如果客户端的会话在客户端重连之前终止,服务端不能把此消息发送给其他订阅的客户端 [MQTT-4.8.2-5]。
-
如果服务端正在向其选中的订阅客户端发送QoS等级1的消息,并且服务端在收到此客户端的确认报文之前网络中断,服务端可以等客户端重新连接之后将消息重传给客户端。如果客户端的会话在客户端重连之前终止,服务端应该把此应用消息发送给与此共享订阅相关的另一个客户端。服务端可以在第一个客户端断开连接时就尝试将消息发送给另一个客户端。
-
如果客户端对来自服务端的PUBLISH报文使用包含原因码大于等于0x80的PUBACK或PUBREC报文进行响应,服务端必须丢弃应用消息而不尝试将其发送给任何其他订阅者 [MQTT-4.8.2-6]。
-
允许客户端向已订阅的共享订阅第二次发送SUBSCRIBE请求。比如,它可以通过这样改变其订阅请求的QoS等级,或者因为它不确定以前的连接关闭之前订阅是否已完成。这不会增加共享订阅关联的会话个数,因此会话将在其第一次发送UNSUBSCRIBE之后脱离此共享订阅。
-
每个共享订阅都是独立于其他共享订阅的。有可能两个共享订阅包含了重叠的过滤器。在这种情况下,与两个共享订阅都相匹配的消息都将被它们单独处理。如果某个客户端既有共享订阅也有非共享订阅,且某个消息与它们都相匹配,客户端将由于存在非共享订阅而接收此消息的副本,此消息的第二个副本将分发给此共享订阅的某个订阅者,因此可能导致两份副本都被发送给此客户端。
客户端和服务端使用接收最大值来控制接收未被确认的PUBLISH报文数量,如3.1.2.11.4节和3.2.2.3.2节所述。接收最大值创建了一个发送配额,用于限制可以在没收到PUBACK(QoS等级1)或PUBCOMP(QoS等级2)的情况下发送的QoS等级大于0的PUBLISH报文数量。PUBACK和PUBCOMP按照下述方式补充配额。
客户端或服务端必须将其初始发送配额设置为不超过接收最大值的非0值 [MQTT-4.9.0-1]。
每当客户端或服务端发送了一个QoS等级大于0的PUBLISH报文,它就会减少发送配额。如果发送配额减为0,客户端或服务端不能再发送任何QoS等级大于0的PUBLISH报文 [MQTT-4.9.0-2]。它可以继续发送QoS为0的PUBLISH报文,也可以选择暂停发送这些报文。即使配额为0,客户端和服务端也必须继续处理和响应其他MQTT控制报文 [MQTT-4.9.0-3]。
发送配额增加1:
- 每当收到一个PUBACK报文或PUBCOMP报文,不管PUBACK或PUBCOMP报文是否包含错误码。
- 每次收到一个包含返回码大于等于0x80的PUBREC报文。
如果发送配额已到达初始发送配额,则不继续增加。在初始发送配额之上尝试增加配额可能是由建立新的网络连接后重新发送PUBREL数据包引起的。
关于客户端和服务端在超出最大接收值的允许的情况下发送PUBLISH报文的描述,参考3.3.4节。
发送配额和接收最大值的保留不跨越网络连接,每次建立新的网络连接时按照上面的描述进行初始化。它们不是会话状态的一部分。
有些应用程序或标准可能希望通过MQTT协议运行请求/响应交互。此版本MQTT协议包含三个可用于此目的的属性:
- 响应主题,在3.3.2.3.5节中描述
- 对比数据,在3.3.2.3.6节中描述
- 请求响应信息,在3.1.2.11.7节中描述
- 响应信息,在3.2.2.3.14节中描述 以下非规范部分描述了如何使用这些属性。
客户端通过发布一个包含响应主题的应用消息来发送请求消息,如3.3.2.3.5节所述。请求消息可以包含对比数据属性,如3.3.2.3.6节所述。
请求/响应交互过程如下:
- MQTT客户端(请求方)向主题发布请求消息。请求消息是具有响应主题的应用消息。
- 另一个MQTT客户端(响应方)订阅了与请求消息发布时使用的主题名相匹配的主题过滤器。结果,它收到请求消息。可能有多个响应方订阅了此主题名,也可能没有响应方。
- 响应方根据请求消息采取适当的操作,然后往请求消息中携带的响应主题属性中的主题名发布响应消息。
- 典型用法,请求放订阅了响应主题,从而接收到响应信息。但是,其他某些客户端可能会订阅响应主题,因此它们也将接收和处理响应消息。与请求消息一样,可能有多个客户端订阅了响应消息的发送主题,也可能没有。
如果请求消息包含对比数据属性,则响应方将此属性拷贝到响应消息中,由响应消息的接收端用来将响应消息与原始请求相关联。响应消息不包含响应主题属性。
MQTT服务端转发请求消息中的响应主题和对比数据属性,和响应消息中的对比数据属性。服务端像处理其他应用程序消息一样处理请求消息和响应消息。
请求放通常在发布请求消息之前订阅响应主题。如果响应消息发送时没有任何订阅者订阅了响应主题,则响应消息将不会传递给任何客户端。
请求消息和响应消息可以具有任何QoS等级,并且响应方可以使用具有非0会话过期间隔的会话。通常使用QoS等级0发送请求消息,并且只有在应答者正连接时才发送请求消息。但这不是必须的。
响应者可以使用共享订阅来允许响应客户端池。注意,使用共享订阅时,不保证消息在客户端之间的分发顺序。
请求方有责任确保它具有发布消息到请求消息的主题、并订阅响应主题属性中主题名的必要权限。响应方有责任确保它具有订阅请求主题和发布到响应主题的权限。虽然主题授权不属于本规范,但建议服务端实施此类授权。
请求方可以通过包括本地配置在内的任何方式来确定作为他们的响应主题的主题名。为避免不同请求方之间的冲突,由请求方客户端使用的响应主题最好对于该客户端是唯一的。由于请求方和响应方通常都需要对这些主题进行授权,因此使用随机主题名称将会对授权造成挑战。
为了解决此问题,本规范在CONNACK报文中定义了一个名为响应信息的属性。服务端可以使用此属性指导客户端如何选择使用的响应主题。此机制对于服务端和客户端都是可选的。连接时,客户端通过设置CONNECT报文中的请求响应信息属性来请求服务端发送响应信息。这会导致服务端在CONNACK报文中插入响应信息属性(UTF-8编码的字符串)。
本规范不定义响应信息的内容,但它可以被用来传递主题树的全局唯一部分,该部分至少在其会话的整个生命周期内保留给该客户端。使用这种机制,可以在服务端而不是每个客户端中完成该属性的配置。
有关响应信息的定义,参考3.1.2.11.7节。
服务端可以通过发送包含原因码为0x9C((临时)使用其他服务端)或0x9D(服务端已(永久)移动)的CONNACK或DISCONNECT报文请求客户端使用另一台服务端,如4.13节 所述。服务端发送这些原因码时可以包含一个服务端参考属性,用以说明客户端应该使用的服务端位置。
原因码0x9C ((临时)使用其他服务端) 指定客户端应该临时切换到另一台服务端。另一台服务端可能是客户端已知的,也可能是由服务端参考所指定的。
原因码0x9D (服务端已(永久)移动)指定客户端应该永久切换到另一台服务端。另一台服务端可能是客户端已知的,也可能是由服务端参考所指定的。
服务端参考是一个UTF-8编码字符串,其值是一个由空格分隔开的参考列表。本规范不指定服务端参考的格式。
非规范评注
推荐每个参考包含名称及可选的端口号。如果名称包含冒号,则名称字符串可以由方括号括起来(“[“和“]”)。由方括号括起来的名称不能包含右方括号(“]”)字符,用于表示使用冒号分隔符的IPv6地址。这是一个简化版的URI授权,如[RFC3986]所述。
非规范评注
服务端参考中的名字通常代表主机名、DNS名[RFC1035]、SRV名RFC2782或IP地址。跟随冒号分隔符的通常是十进制端口号。如果端口信息来自于DNS(比如包含SRV)或者使用默认端口,则主机名后无需跟随端口号。
非规范评注
如果给出了多个服务端参考,则期望客户端选择其中一个。
非规范评注
服务端参考示例如下:
myserver.xyz.org
myserver.xyz.org:8883
10.10.151.22:8883 [fe80::9610:3eff:fe1c]:1883
允许服务端不发送服务端参考,允许客户端忽略服务端参考。此特性可用于负载均衡、服务端重定位和服务端预置服务端。
MQTT CONNECT报文使用用户名和密码字段支持基本的网络连接认证。这些字段虽然称为简单密码认证,但可以被用来承载其他形式的认证,例如把密码作为令牌(Token)传递。
增强认证包含质询/响应风格的认证,从而扩展了基本认证。它可能涉及在CONNECT报文之后、CONNACK报文之前的客户端和服务端之间AUTH报文交换。
服务端通过在CONNECT报文中添加认证方法字段来启动增强认证。此字段指定使用的认证方法。如果服务端不支持客户端提供的认证方法,它可以发送一个包含原因码0x8C(无效的认证方法)或0x87(未授权)的CONNACK报文,如4.13节所述,并且必须关闭网络连接 [MQTT-4.12.0-1]。
认证方法是客户端和服务端关于认证数据中的数据和CONNECT报文中其他字段的含义,以及客户端和服务端完成认证需要交换和处理的协议。
非规范评注
认证方法通常为SASL(Simple Authentication and Security Layer)机制,使用一个注册过的名称便于信息交换。然而,认证方法不限于使用已注册的SASL机制。
如果客户端选择的认证方法指定客户端先发送数据,客户端应该在CONNECT报文中包含认证数据属性。此属性可被用来提供认证方法指定的数据,认证数据的内容由认证方法定义。
如果服务端需要额外的信息来完成认证,它可以向客户端发送AUTH报文,此报文必须包含原因码0x18(继续认证) [MQTT-4.12.0-2]。如果认证方法需要服务端向客户端发送认证相关的数据,这些数据在认证数据(Authentication Data)中发送。
客户端通过发送另一个AUTH报文响应来自服务端的AUTH报文,此报文必须包含原因码0x18(继续认证) [MQTT-4.12.0-3]。如果认证方法要求客户端向服务端发送认证相关的数据,这些数据在认证数据(Authentication Data)中发送。
客户端和服务端按需交换AUTH报文,直到服务端通过发送包含原因码为0的CONNACK报文接受认证为止。如果接受认证需要向客户端发送数据,这些数据在认证数据中发送。
客户端可以在处理过程中随时关闭连接。它可以在关闭之前发送DISCONNECT报文。服务端可以在处理过程中随时拒绝认证。它可以发送包含原因码大于等于0x80的CONNACK报文 ,如4.13节所述,并且必须关闭网络连接 [MQTT-4.12.0-4]。
如果初始CONNECT报文包含认证方法属性,则所有的AUTH报文和成功的CONNACK报文必须包含与CONNECT报文中相同的认证方法属性。 [MQTT-4.12.0-5]。
增强认证的实现对于客户端和服务端来说都是可选的。如果客户端在CONNECT报文中没有包含认证方法,则服务端不能发送AUTH报文,且不能在CONNACK报文中发送认证方法 [MQTT-4.12.0-6]。如果客户端在CONNECT报文中没有包含认证方法,则客户端不能向服务端发送AUTH报文 [MQTT-4.12.0-7]。
如果客户端在CONNECT报文中没有包含认证方法,服务端应该使用CONNECT报文中的信息、TLS会话和网络连接进行认证。
SCRAM认证非规范示例
- 客户端到服务端:CONNECT认证方法="SCRAM-SHA-1",认证数据=client-first-data
- 服务端到客户端:AUTH原因码=0x18,认证方法="SCRAM-SHA-1",认证数据=server-first-data
- 客户端到服务端:AUTH原因码=0x18,认证方法="SCRAM-SHA-1",认证数据=client-final-data
- 服务端到客户端:CONNACK原因码=0,认证方法="SCRAM-SHA-1",认证数据=server-final-data
Kerberos认证非规范示例
- 客户端到服务端:CONNECT认证方法="GS2-KRB5"
- 服务端到客户端:AUTH原因码=0x18,认证方法="GS2-KRB5"
- 客户端到服务端:AUTH原因码=0x18,认证方法="GS2-KRB5",认证数据=initial context token
- 服务端到客户端:AUTH原因码=0x18,认证方法="GS2-KRB5",认证数据=reply context token
- 客户端到服务端:AUTH原因码=0x18,认证方法="GS2-KRB5"
- 服务端到客户端:CONNACK原因码=0,认证方法="GS2-KRB5",认证数据=outcome of authentication
如果客户端在CONNECT报文中提供了认证方法,它可以在收到CONNACK报文之后的任何时间通过发送包含原因码0x19(重新认证)的AUTH报文发起重新认证。客户端必须将认证方法设置为与最初验证网络连接时的认证方法一致 [MQTT-4.12.1-1]。如果认证方法需要客户端先发送数据,则此AUTH报文包含第一片认证数据。
服务端通过向客户端发送AUTH报文来响应此重新认证请求,包含原因码为0x00(成功)的AUTH报文指示重新认证完成,包含原因码为0x18(继续认证)的AUTH报文指示需要更多的认证数据。客户端可以通过发送包含原因码0x18(继续认证)的AUTH报文来响应附加的认证数据。此流程与原始身份验证一样,直到重新认证完成或重新认证失败。
如果重新认证失败,客户端或服务端应该发送包含适当原因码的DISCONNECT报文,如4.13节所述。并且必须关闭网络连接 [MQTT-4.12.1-2]。
在重新认证的过程中,客户端和服务端的其他报文流可以继续使用之前的认证。
非规范评注
服务端可以通过拒绝重新认证来限制客户端在重新认证中尝试的更改范围。例如,如果服务端不允许更改用户名,它可以使任何尝试更改用户名的重新认证都失败。
无效报文(Malformed Packet)和协议错误(Protocol Error)的定义见1.2节术语。这些错误案例的部分术语贯穿本规范。客户端或服务端对其收到的MQTT控制报文的检查严格程度依赖:
- 客户端或服务端实现的大小。
- 实现支持的性能。
- 接收端对发送端发送的MQTT控制报文的信任程度。
- 接收端对用于分发MQTT控制报文的网络的信任程度。
- 继续处理错误报文的的后果。
如果发送端遵守此规范,它将不会发送无效报文或导致协议错误。然而,如果客户端在收到CONNACK报文之前发送MQTT控制报文,它可能会因为错误的估计了服务端的性能而导致协议错误。参考3.1.4节CONNECT 行为。
无效报文和协议错误使用的原因码包括:
- 0x81 无效报文
- 0x82 协议错误
- 0x93 超过接收最大值
- 0x95 报文过大
- 0x9A 不支持保留
- 0x9B 不支持的QoS等级
- 0x9E 不支持共享订阅
- 0xA1 不支持订阅标识符
- 0xA2 不支持通配符订阅
当客户端检测到无效报文或协议错误,并且本规范中给出了相应的原因码时,它应该关闭网络连接。在AUTH报文出错的情况下它可以在关闭网络连接之前发送包含原因码的DISCONNECT报文。在其他报文出错的情况下它应该在关闭网络连接之前发送包含原因码的DISCONNECT报文。使用原因码0x81(错误报文)或0x82(协议错误),除非包含3.14.2.1断开原因码 中定义的更具体的原因码。
当服务端检测到无效报文或协议错误,并且本规范中给出了相应的原因码时,它必须关闭网络连接 [MQTT-4.13.1-1]。在CONNECT报文出错的情况下它可以在关闭网络连接之前发送包含原因码的CONNACK报文。在其他报文出错的情况下它应该在关闭网络连接之前发送包含原因码的DISCONNECT报文。使用原因码0x81(无效报文)或0x82(协议错误),除非包含3.2.2.2节 - 连接原因码 或3.14.2.1节 – 断开原因码 中定义的更具体的原因码。对其他会话没有影响。
如果服务端或客户端省略了检查MQTT控制报文的某些特性,它可能无法检测到某个错误,因此可能会导致数据被损坏。
发送端无法预料到无效报文和协议错误以外的错误,因为它可能有某些没有告知发送端的约束。客户端或服务端可能在接收时遇到短暂的错误,比如内存不足,导致无法成功的处理某个MQTT控制报文。
包含原因码大于等于0x80的确认报文PUBACK,PUBREC,PUBREL,PUBCOMP,SUBACK,UNSUBACK表明收到了某个报文标识符的报文出错。这不会影响其他会话或此会话上的其他报文。
CONNACK报文和DISCONNECT报文允许使用大于等于0x80的原因码以指示网络连接将被关闭。如果某个大于等于0x80的原因码被指定,无论是否发送CONNACK报文或DISCONNECT报文,必须关闭网络连接 [MQTT-4.13.2-1]。发送这些原因码不会影响任何其他会话。
如果控制报文包含多个错误,接收端可以按照任意顺序对报文进行验证,并对发现的任何错误采取适当的行为。