物联网之MQTT3.1.1和MQTT5协议 (17) 操作行为

前言

本文介绍了PUBACK报文,PUBREC 报文,PUBREL报文,PUBCOMP报文, AUTHB报文。并且还会介绍关于流控,错误处理,MQTT的订阅匹配语法和响应的QoS流程。

操作行为

状态存储(MQTT3.1.1)

为了提供服务质量保证,客户端和服务端有必要存储会话状态。在整个会话期间,客户端和服务端都必须存储会话状态 。会话必须至少持续和它的活跃网络连接同样长的时间。

服务端的保留消息不是会话状态的组成部分。服务端应该保留那种消息直到客户端删除它。

客户端和服务端实现的存储容量必然是有限的,还可能要受管理策略的限制,比如跨网络连接的会话状态的最大存储时间。已保存的会话状态丢失可能是某个管理操作造成的,例如对某个预定义条件的自动响应。它造成的后果就是会话终止。这些操作可能是资源限制或其他操作原因引发的。需要谨慎的评估客户端和服务端的存储容量,以确保存储空间够用。

客户端或服务端的软硬件故障都可能导致会话状态的丢失或损坏。

服务器和客户端操作正常可能意味着,已保存的状态丢失或损坏是管理操作或软硬件故障造成的。管理操作可能是对某个预定义条件的自动响应。这些操作可能是资源限制或其他操作原因引发的。例如,服务端可能会基于外部条件,决定不再将某个消息或某些消息分发给任何当前的或以后的客户端。

MQTT用户应该评估MQTT客户端和服务端实现的存储容量,确保能满足需求。

会话状态(MQTT 5)

为实现QoS等级1和QoS等级2协议流,客户端和服务端需要将状态与客户标识符相关联,这被称为会话状态。服务端还将订阅信息存储为会话状态的一部分。

会话可以跨越一系列的网络连接。它持续到最新的网络连接(Network Connections)加上会话过期间隔(Session Expiry Interval)。

客户端的会话状态包括:

  • 已发送给服务端,但是还没有完成确认的QoS等级1和QoS等级2的消息
  • 从服务端收到的,但是还没有完成确认的QoS等级2消息

服务端的会话状态包括:

  • 会话是否存在,即使会话状态其余部分为空
  • 客户端订阅信息,包括任何订阅标识符
  • 已发送给客户端,但是还没有完成确认的QoS等级1和QoS等级2的消息
  • 等待发送到客户端的QoS 1和QoS 2消息,等待发送到客户端的可选QoS 0消息。
  • 已从客户端收到但尚未完全确认的QoS 2消息.Will消息和Will Delay间隔
  • 如果会话当前未连接,会话结束时间和会话状态将被丢弃

保留消息不是会话状态的一部分,会话结束时不被删除。

存储会话状态

当网络连接打开时,客户端和服务端不能丢弃会话状态。当网络连接被关闭并且会话过期间隔已过时,服务端必须丢弃会话状态。

和MQTT 3.1.1在状态存储一节描述相差无几,但多了一个判断,即会话过期间隔时间。

非规范示例

例如,想要收集电表读数的用户可能会决定使用QoS 1等级的消息,因为他们不能接受数据在网络传输途中丢失,但是,他们可能认为客户端和服务端的数据可以存储在内存(易失性存储器)中,因为(他们觉得)电力供应是非常可靠的,不会有太大的数据丢失风险。

与之相反,停车计费支付应用的提供商可能决定任何情况下都不能让数据支付消息丢失,因此他们要求在通过网络传输之前,所有的数据必须写入到非易失性存储器中(如硬盘)。

网络连接

MQTT协议要求基础传输层能够提供有序的、可靠的、双向传输(从客户端到服务端和从服务端到客户端)字节流。此规范不要求任何指定的传输协议。客户端或服务端可以支持这里列出的任何传输协议,或者满足本节要求的任何其他传输协议。

MQTT v5.0,和MQTT v3.1.1使用的传输层协议是 [RFC0793] 定义的TCP/IP协议。下面的协议也支持:

TCP端口8883和1883已在IANA注册,分别用于MQTT的TLS和非TLS通信。

无连接的网络传输,如用户数据包协议 (UDP) 本身不适合,因为它们可能丢失或重新排列数据。

服务质量等级和协议流程

MQTT按照这里定义的服务质量 (QoS) 等级分发应用消息。分发协议是对称的,在下面的描述中,客户端和服务端既可以是发送者也可以是接收者。分发协议关注的是从单个发送者到单个接收者的应用消息。服务端分发应用消息给多个客户端时,每个客户端独立处理。分发给客户端的出站应用消息和入站应用消息的QoS等级可能是不同的。
下面的非规范流程图展示了可能的实现方法。

QoS 0:最多分发一次

消息的分发依赖于底层网络的能力。接收者不会发送响应,发送者也不会重试。消息可能送达一次也可能根本没送达。

对于QoS 0的分发协议,发送者

  • 必须发送QoS等于0,DUP等于0的PUBLISH报文

对于QoS 0的分发协议,接收者

  • 接受PUBLISH报文时同时接受消息的所有权
QoS 0协议流程图,非规范示例

在这里插入图片描述

QoS 1: 至少分发一次

服务质量确保消息至少送达一次。QoS 1的PUBLISH报文的可变报头中包含一个报文标识符,需要PUBACK报文确认。

对于QoS 1的分发协议,发送者

  • 每次发送新的应用消息都必须分配一个未使用的报文标识符。
  • 发送的PUBLISH报文必须包含报文标识符且QoS等于1,DUP等于0。
  • 必须将这个PUBLISH报文看作是 未确认的 ,直到从接收者那收到对应的PUBACK报文。

一旦发送端收到PUBACK报文,这个报文标识符就可以重用。

注意:允许发送端在等待确认时使用不同的报文标识符发送后续的PUBLISH报文。

对于QoS 1的分发协议,接收者

  • 响应的PUBACK报文必须包含一个报文标识符,这个标识符来自接收到的、已经接受所有权的PUBLISH报文。
  • 发送了PUBACK报文之后,接收者必须将任何包含相同报文标识符的入站PUBLISH报文当作一个新的消息,并忽略它的DUP标志的值。
    在这里插入图片描述

不要求接收者在发送PUBACK之前完整分发应用消息。原来的发送者收到PUBACK报文之后,应用消息的所有权就会转移给这个接收者。

QoS 2:仅分发一次

这是最高等级的服务质量,消息丢失和重复都是不可接受的。使用这个服务质量等级会有额外的开销。

QoS 2的消息可变报头中有报文标识符。QoS 2的PUBLISH报文的接收者使用一个两步确认过程来确认收到。

对于QoS 2的分发协议,发送者

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

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

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

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

    MQTT5额外要求是原因码小于0x80的PUBREC报文

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

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

  • 【MQTT5】如果PUBLISH报文已发送,不能应用消息过期属性

一旦发送者收到PUBCOMP报文,这个报文标识符就可以重用。

MQTT5要求是包含原因码大于0x80的PUBCOMP报文

注意:允许发送者在等待确认时使用不同的报文标识符发送后续的PUBLISH报文。

对于QoS 2的分发协议,接收者:

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

  • 【MQTT5】如果接收端发送了包含原因码大于等于0x80的PUBREC报文,它必须将后续包含相同报文标识符的PUBLISH报文当做是新的应用消息

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

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

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

    MQTT5认为是新的应用消息

  • 即使已应用消息到期,也必须继续QoS 2确认序列

在这里插入图片描述

不要求接收者在发送PUBREC或PUBCOMP之前完整分发应用消息。原来的发送者收到PUBREC报文之后,应用消息的所有权就会转移给这个接收者。

【MQTT5】

然而,接收端需要在接受所有权之前执行对所有可能导致转发失败(例如超出配额、权限等)的条件的检查。接收端在PUBREC中使用适当的原因码指示所有权接受成功或失败。

消息分发重试

【MQTT3.1.1】

客户端设置清理会话(CleanSession)标志为0重连时,客户端和服务端必须使用原始的报文标识符重发任何未确认的PUBLISH报文(如果QoS>0)和PUBREL报文。这是唯一要求客户端或服务端重发消息的情况。

控制报文的重发曾经需要克服某些陈旧TCP网络上的数据丢失问题。部署在那些环境中的MQTT 3.1.1实现可能仍然需要关注这个问题。

【MQTT5】

客户端以新开始(Clean Start)标志为0且会话存在的情况下重连时,客户端和服务端都必须使用原始报文标识符重新发送任何未被确认的PUBLISH报文(当QoS > 0)和PUBREL报文。这是唯一要求客户端或服务端重发消息的情况。客户端和服务端不能在其他任何时间重发消息

如果收到包含原因码大于等于0x80的PUBACK或PUBREC,则对应的PUBLISH报文被看作已确认,且不能被重传

消息收到

服务端接管入站应用消息的所有权时,它必须将消息添加到订阅匹配的客户端的会话状态中。

通常情况下,客户会收到响应其创建的订阅的消息。 客户端还可能收到与任何其明确订阅都不匹配的消息。 如果服务器自动为客户端分配了订阅,则会发生这种情况。 客户端还可以在进行UNSUBSCRIBE操作时接收消息。 客户端必须根据适用的QoS规则确认收到的任何发布数据报文,无论其是否选择处理其包含的应用消息。

消息排序

实现定义的协议流程时,客户端必须遵循下列规则:

  • 重发任何之前的PUBLISH报文时,必须按原始PUBLISH报文的发送顺序重发(适用于QoS 1和QoS 2消息)
  • 必须按照对应的PUBLISH报文的顺序发送PUBACK报文(QoS 1消息)
  • 必须按照对应的PUBLISH报文的顺序发送PUBREC报文(QoS 2消息)
  • 必须按照对应的PUBREC报文的顺序发送PUBREL报文(QoS 2消息)

默认情况下,服务端转发订阅的消息【在MQTT中认为是非共享订阅的消息】时,必须将每个主题都视为有序主题。它可以提供一个管理功能或其它机制,以允许将一个或多个主题当作是无序的。

【MQTT5】

一个有序主题(Ordered Topic)是一个主题,在这个主题中,客户端可以确定从同一个客户端接收的相同QoS等级的消息的顺序与他们发布的顺序一致。当服务端处理发布到有序主题的消息时,它必须按照消息从任何给定客户端接收的顺序发送PUBLISH报文给消费端(对于同一主题和QoS等级) 。这是上面列出的规则的补充。

上面列出的规则确保,使用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。【MQTT3.1.1】将传输窗口 (in-flight window)设为1意味着,在同一个主题上,即使发布者发送了一系列不同QoS等级的消息,它们的顺序也被保留。

主题名和主题过滤器

主题通配符

主题层级(topic level)分隔符用于将结构化引入主题名。如果存在分隔符,它将主题名分割为多个主题层级 topic level 。

订阅的主题过滤器可以包含特殊的通配符,允许客户端一次订阅多个主题。

主题过滤器中可以使用通配符,但是主题名不能使用通配符。

主题层级分隔符

斜杠('/' U+002F)用于分割主题的每个层级,为主题名提供一个分层结构。当客户端订阅指定的主题过滤器包含两种通配符时,主题层级分隔符就很有用了。主题层级分隔符可以出现在主题过滤器或主题名字的任何位置。相邻的主题层次分隔符表示一个零长度的主题层级。

多层通配符

数字标志('#' U+0023)是用于匹配主题中任意层级的通配符。多层通配符表示它的父级和任意数量的子层级。多层通配符必须位于它自己的层级或者跟在主题层级分隔符后面。不管哪种情况,它都必须是主题过滤器的最后一个字符。

例如,如果客户端订阅主题 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) 是只能用于单个主题层级匹配的通配符。

在主题过滤器的任意层级都可以使用单层通配符,包括第一个和最后一个层级。然而它必须占据过滤器的整个层级。可以在主题过滤器中的多个层级中使用它,也可以和多层通配符一起使用。

例如, sport/tennis/+ 匹配 sport/tennis/player1sport/tennis/player2 ,但是不匹配 sport/tennis/player1/ranking 。同时,由于单层通配符只能匹配一个层级, sport/+ 不匹配 sport 但是却匹配 sport/

  • + 是有效的。
  • +/tennis/# 是有效的。
  • sport+ 是无效的。
  • sport/+/player1 也是有效的。
  • /finance 匹配 +/+/+ ,但是不匹配 +

以$开头的主题

服务端不能将 $ 字符开头的主题名匹配通配符(#+)开头的主题过滤器。服务端应该阻止客户端使用这种主题名与其它客户端交换消息。服务端实现可以将 $开头的主题名用作其他目的。

  • $SYS/ 被广泛用作包含服务器特定信息或控制接口的主题的前缀。
  • 应用不能使用 $ 字符开头的主题。
  • 订阅 # 的客户端不会收到任何发布到以 $ 开头主题的消息。
  • 订阅 +/monitor/Clients 的客户端不会收到任何发布到 $SYS/monitor/Clients 的消息。
  • 订阅 $SYS/# 的客户端会收到发布到以$SYS/ 开头主题的消息。
  • 订阅 $SYS/monitor/+的客户端会收到发布到 $SYS/monitor/Clients 主题的消息。
  • 如果客户端想同时接受以 $SYS/开头主题的消息和不以$ 开头主题的消息,它需要同时订阅 #$SYS/#

主题语义和用法

主题名和主题过滤器必须符合下列规则:

  • 所有的主题名和主题过滤器必须至少包含一个字符
  • 主题名和主题过滤器是区分大小写的
  • 主题名和主题过滤器可以包含空格
  • 主题名或主题过滤器以前置或后置斜杠 /区分。
  • 只包含斜杠 /的主题名或主题过滤器是合法的。
  • 主题名和主题过滤器不能包含空字符 (Unicode U+0000)
  • 主题名和主题过滤器是UTF-8编码字符串,它们不能超过65535字节

除了不能超过UTF-编码字符串的长度限制之外,主题名或主题过滤器的层级数量没有其它限制。

匹配订阅时,服务端不能对主题名或主题过滤器执行任何规范化(normalization)处理,不能修改或替换任何未识别的字符。主题过滤器中的每个非通配符层级需要逐字符匹配主题名中对应的层级才算匹配成功。

使用UTF-8编码规则意味着,主题过滤器和主题名的比较可以通过比较编码后的UTF-8字节或解码后的Unicode字符。

  • “ACCOUNTS” 和 “Accounts” 是不同的主题名。
  • “Accounts payable” 是合法的主题名
  • “/finance” 和 “finance” 是不同的。

如果订阅的主题过滤器与消息的主题名匹配,应用消息会被发送给每一个匹配的客户端订阅。主题可能是管理员在服务端预先定义好的,也可能是服务端收到第一个订阅或使用那个主题名的应用消息时动态添加的。服务端也可以使用一个安全组件有选择地授权客户端使用某个主题资源。

订阅(MQTT 5)

MQTT提供两种订阅方式,共享和非共享

在MQTT 早期版本中,所有的订阅都是非共享的。

非共享订阅

非共享订阅只与创建它的会话相关联。每个订阅(Subscription)包含一个指示用于在此会话上分发消息的主题过滤器和订阅选项。服务端负责收集与过滤器相匹配的消息,并在此会话的连接上发送这些消息。

一个会话不能有多个包含相同主题过滤器的非共享订阅,因此主题过滤器可以用作标识此会话的订阅的关键词。

如果有多个客户端,每个客户端都拥有对某个相同主题的非共享订阅,则每个客户端都将获得在该主题上发布的应用消息的副本。这意味着非共享订阅不能被用于多个消费客户端的应用消息负载均衡,因为在这种情况下,每条消息都将被传递给每一个订阅的客户端。

共享订阅

共享订阅可以与多个订阅会话相关联。与非共享订阅一样,它包含一个主题过滤器和订阅选项。但是,与此主题过滤器相匹配的发布消息仅被发布到其中一个订阅会话。共享订阅在多个消费客户端并行共享处理发布消息时是很有用的。

使用特殊样式的主题过滤器来表示共享订阅。过滤器格式如下:

$share/{ShareName}/{filter}

  • $share是字符串字面量,用来把主题过滤器标记为共享订阅主题过滤器。
  • {ShareName}是字符串,不包含/+#
  • {filter}该字符串的剩余部分与非共享订阅中的主题过滤器具有相同的语法和语义。

共享订阅主题过滤器必须以$share/开始,且必须包含至少一个字符长度的共享名(ShareName) 。共享名不能包含字符"/","+“或”#",但必须跟在"/“字符后面。此”/"字符后面必须跟随一个主题过滤器 。

共享订阅在MQTT服务端的范围内定义,而不是在会话中定义。共享订阅的主题过滤器包含共享名,因此服务端可以有多个包含相同{过滤器}组件的共享订阅。通常,应用程序使用共享名表示共享同一个订阅的一组订阅会话。

示例

共享订阅$hare/consumer1/sport/tennis/+$share/consumer2/sport/tennis/+是不同的共享订阅,因此可以被关联到不同的会话组。它们都与非共享订阅主题sport/tennis/+相匹配。

如果一条消息被发布到匹配主题sport/tennis/+,则消息的副本仅发送给所有订阅$hare/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等级,与向订阅者发送消息相同。
  • 如果服务端正在向其选中的订阅客户端发送QoS等级2的消息,并且在分发完成之前网络中断,服务端必须在客户端重新连接时完成向该客户端的消息分发。如果客户端的会话在客户端重连之前终止,服务端不能把此消息发送给其他订阅的客户端
  • 如果服务端正在向其选中的订阅客户端发送QoS等级1的消息,并且服务端在收到此客户端的确认报文之前网络中断,服务端可以等客户端重新连接之后将消息重传给客户端。如果客户端的会话在客户端重连之前终止,服务端应该把此应用消息发送给与此共享订阅相关的另一个客户端。服务端可以在第一个客户端断开连接时就尝试将消息发送给另一个客户端。
  • 如果客户端对来自服务端的PUBLISH报文使用包含原因码大于等于0x80的PUBACK或PUBREC报文进行响应,服务端必须丢弃应用消息而不尝试将其发送给任何其他订阅者。
  • 允许客户端向已订阅的共享订阅第二次发送SUBSCRIBE请求。比如,它可以通过这样改变其订阅请求的QoS等级,或者因为它不确定以前的连接关闭之前订阅是否已完成。这不会增加共享订阅关联的会话个数,因此会话将在其第一次发送UNSUBSCRIBE之后脱离此共享订阅。
  • 每个共享订阅都是独立于其他共享订阅的。有可能两个共享订阅包含了重叠的过滤器。在这种情况下,与两个共享订阅都相匹配的消息都将被它们单独处理。如果某个客户端既有共享订阅也有非共享订阅,且某个消息与它们都相匹配,客户端将由于存在非共享订阅而接收此消息的副本,此消息的第二个副本将分发给此共享订阅的某个订阅者,因此可能导致两份副本都被发送给此客户端。

流控(MQTT 5)

客户端和服务端使用接收最大值来控制接收未被确认的PUBLISH报文数量。接收最大值创建了一个发送配额,用于限制可以在没收到PUBACK(QoS等级1)或PUBCOMP(QoS等级2)的情况下发送的QoS等级大于0的PUBLISH报文数量。PUBACK和PUBCOMP按照下述方式补充配额。

客户端或服务端必须将其初始发送配额设置为不超过接收最大值的非0值。

每当客户端或服务端发送了一个QoS等级大于0的PUBLISH报文,它就会减少发送配额。如果发送配额减为0,客户端或服务端不能再发送任何QoS等级大于0的PUBLISH报文。它可以继续发送QoS为0的PUBLISH报文,也可以选择暂停发送这些报文。即使配额为0,客户端和服务端也必须继续处理和响应其他MQTT控制报文。

发送配额增加1:

  • 每当收到一个PUBACK报文或PUBCOMP报文,不管PUBACK或PUBCOMP报文是否包含错误码。
  • 每次收到一个包含返回码大于等于0x80的PUBREC报文。

如果发送配额已到达初始发送配额,则不继续增加。在初始发送配额之上尝试增加配额可能是由建立新的网络连接后重新发送PUBREL数据包引起的。

发送配额和接收最大值的保留不跨越网络连接,每次建立新的网络连接时按照上面的描述进行初始化。它们不是会话状态的一部分。

请求/响应(MQTT 5)

有些应用程序或标准可能希望通过MQTT协议运行请求/响应交互。此版本MQTT协议包含三个可用于此目的的属性:

  • 响应主题
  • 对比数据
  • 请求响应信息
  • 响应信息

示例

客户端通过发布一个包含响应主题的应用消息来发送请求消息。请求消息可以包含对比数据属性。

基本请求响应(非规范)

请求/响应交互过程如下:

  1. MQTT客户端(请求方)向主题发布请求消息。请求消息是具有响应主题的应用消息。
  2. 另一个MQTT客户端(响应方)订阅了与请求消息发布时使用的主题名相匹配的主题过滤器。结果,它收到请求消息。可能有多个响应方订阅了此主题名,也可能没有响应方。
  3. 响应方根据请求消息采取适当的操作,然后往请求消息中携带的响应主题属性中的主题名发布响应消息。
  4. 典型用法,请求放订阅了响应主题,从而接收到响应信息。但是,其他某些客户端可能会订阅响应主题,因此它们也将接收和处理响应消息。与请求消息一样,可能有多个客户端订阅了响应消息的发送主题,也可能没有。

如果请求消息包含对比数据属性,则响应方将此属性拷贝到响应消息中,由响应消息的接收端用来将响应消息与原始请求相关联。响应消息不包含响应主题属性。

MQTT服务端转发请求消息中的响应主题和对比数据属性,和响应消息中的对比数据属性。服务端像处理其他应用程序消息一样处理请求消息和响应消息。

请求放通常在发布请求消息之前订阅响应主题。如果响应消息发送时没有任何订阅者订阅了响应主题,则响应消息将不会传递给任何客户端。

请求消息和响应消息可以具有任何QoS等级,并且响应方可以使用具有非0会话过期间隔的会话。通常使用QoS等级0发送请求消息,并且只有在应答者正连接时才发送请求消息。但这不是必须的。

响应者可以使用共享订阅来允许响应客户端池。注意,使用共享订阅时,不保证消息在客户端之间的分发顺序。

请求方有责任确保它具有发布消息到请求消息的主题、并订阅响应主题属性中主题名的必要权限。响应方有责任确保它具有订阅请求主题和发布到响应主题的权限。虽然主题授权不属于MQTT规范中,但建议服务端实施此类授权。

确定响应主题值(非规范)

请求方可以通过包括本地配置在内的任何方式来确定作为他们的响应主题的主题名。为避免不同请求方之间的冲突,由请求方客户端使用的响应主题最好对于该客户端是唯一的。由于请求方和响应方通常都需要对这些主题进行授权,因此使用随机主题名称将会对授权造成挑战。

为了解决此问题,MQTT规范中在CONNACK报文中定义了一个名为响应信息的属性。服务端可以使用此属性指导客户端如何选择使用的响应主题。此机制对于服务端和客户端都是可选的。连接时,客户端通过设置CONNECT报文中的请求响应信息属性来请求服务端发送响应信息。这会导致服务端在CONNACK报文中插入响应信息属性(UTF-8编码的字符串)。

MQTT规范中不定义响应信息的内容,但它可以被用来传递主题树的全局唯一部分,该部分至少在其会话的整个生命周期内保留给该客户端。使用这种机制,可以在服务端而不是每个客户端中完成该属性的配置。

服务端重定向

服务端可以通过发送包含原因码为0x9C(使用其他服务端)或0x9D(服务端已移动)的CONNACK或DISCONNECT报文请求客户端使用另一台服务端。服务端发送这些原因码时可以包含一个服务端参考列表属性,用以说明客户端应该使用的服务端位置。

原因码0x9C (使用其他服务端) 指定客户端应该临时切换到另一台服务端。另一台服务端可能是客户端已知的,也可能是由服务端参考所指定的。

原因码0x9D (服务端已移动)指定客户端应该永久切换到另一台服务端。另一台服务端可能是客户端已知的,也可能是由服务端参考所指定的。

服务端参考列表是一个UTF-8编码字符串,其值是一个由空格分隔开的参考列表列表。MQTT5不指定服务端参考的格式。

推荐每个参考包含名称及可选的端口号。如果名称包含冒号,则名称字符串可以由方括号括起来(“[“和“]”)。由方括号括起来的名称不能包含右方括号(“]”)字符,用于表示使用冒号分隔符的IPv6 地址。这是一个简化版的URI授权。

服务端参考列表中的名字通常代表主机名、DNS名、SRV名或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报文,并且必须关闭网络连接。

认证方法是客户端和服务端关于认证数据中的数据和CONNECT报文中其他字段的含义,以及客户端和服务端完成认证需要交换和处理的协议。

认证方法通常为SASL(Simple Authentication and Security Layer)机制,使用一个注册过的名称便于信息交换。然而,认证方法不限于使用已注册的SASL机制。

如果客户端选择的认证方法指定客户端先发送数据,客户端应该在CONNECT报文中包含认证数据属性。此属性可被用来提供认证方法指定的数据,认证数据的内容由认证方法定义。

如果服务端需要额外的信息来完成认证,它可以向客户端发送AUTH报文,此报文必须包含原因码0x18(继续认证)。如果认证方法需要服务端向客户端发送认证相关的数据,这些数据在认证数据(Authentication Data)中发送。

客户端通过发送另一个AUTH报文响应来自服务端的AUTH报文,此报文必须包含原因码0x18(继续认证)。如果认证方法要求客户端向服务端发送认证相关的数据,这些数据在认证数据(Authentication Data)中发送。

客户端和服务端按需交换AUTH报文,直到服务端通过发送包含原因码为0的CONNACK报文接受认证为止。如果接受认证需要向客户端发送数据,这些数据在认证数据中发送。

客户端可以在处理过程中随时关闭连接。它可以在关闭之前发送DISCONNECT报文。服务端可以在处理过程中随时拒绝认证。它可以发送包含原因码大于等于0x80的CONNACK报文 ,并且必须关闭网络连接。

如果初始CONNECT报文包含认证方法属性,则所有的AUTH报文和成功的CONNACK报文必须包含与CONNECT报文中相同的认证方法属性。

增强认证的实现对于客户端和服务端来说都是可选的。如果客户端在CONNECT报文中没有包含认证方法,则服务端不能发送AUTH报文,且不能在CONNACK报文中发送认证方法 。如果客户端在CONNECT报文中没有包含认证方法,则客户端不能向服务端发送AUTH报文 。

如果客户端在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数据报文(重新认证)来做到这一点。 客户端必须将验证方法设置为与最初用于验证网络连接的验证方法相同的值。 如果身份验证方法首先需要客户端数据,则此AUTH数据报文将包含身份验证数据的第一块作为验证数据。

服务器通过向客户端发送AUTH数据报文来响应此重新身份验证请求,原因代码为0x00(成功)以指示重新身份验证已完成,或者原因代码为0x18(继续身份验证)以指示需要更多身份验证数据。 客户端可以通过发送原因码为0x18的AUTH数据报文(继续身份验证)来响应其他身份验证数据。 与原始身份验证一样,此流程继续进行,直到重新身份验证完成或重新身份验证失败为止。

如果重新认证失败,客户端或服务端应该发送包含适当原因码的DISCONNECT报文。并且必须关闭网络连接。

如果重新认证失败,客户端或服务端应该发送包含适当原因码的DISCONNECT报文。并且必须关闭网络连接。

服务端可以通过拒绝重新认证来限制客户端在重新认证中尝试的更改范围。例如,如果服务端不允许更改用户名,它可以使任何尝试更改用户名的重新认证都失败。

MQTT5的错误处理

无效报文和协议错误(MQTT 5)

客户端或服务端对其收到的MQTT控制报文的检查严格程度依赖:

  • 客户端或服务端实现的大小
  • 实现支持的性能
  • 接收端对发送端发送的MQTT控制报文的信任程度
  • 接收端对用于分发MQTT控制报文的网络的信任程度
  • 继续处理错误报文的的后果

如果发送端遵守此规范,它将不会发送无效报文或导致协议错误。然而,如果客户端在收到CONNACK报文之前发送MQTT控制报文,它可能会因为错误的估计了服务端的性能而导致协议错误。

无效报文和协议错误使用的原因码包括:

  • 0x81 无效报文
  • 0x82 协议错误
  • 0x93 超过接收最大值
  • 0x95 报文过大
  • 0x9A 不支持保留
  • 0x9B 不支持的QoS等级
  • 0x9E 不支持共享订阅
  • 0xA1 不支持订阅标识符
  • 0xA2 不支持通配符订阅

当客户端检测到无效报文或协议错误,并且给出了相应的原因码时,它应该关闭网络连接。在AUTH报文出错的情况下它可以在关闭网络连接之前发送包含原因码的DISCONNECT报文。在其他报文出错的情况下它应该在关闭网络连接之前发送包含原因码的DISCONNECT报文。使用原因码0x81(错误报文)或0x82(协议错误),除非包含更具体的原因码。

当服务端检测到无效报文或协议错误,并且MQTT规范中中给出了相应的原因码时,它必须关闭网络连接。在CONNECT报文出错的情况下它可以在关闭网络连接之前发送包含原因码的CONNACK报文。在其他报文出错的情况下它应该在关闭网络连接之前发送包含原因码的DISCONNECT报文。使用原因码0x81(无效报文)或0x82(协议错误),除非包含更具体的原因码。对其他会话没有影响。

如果服务端或客户端省略了检查MQTT控制报文的某些特性,它可能无法检测到某个错误,因此可能会导致数据被损坏。

其他错误(MQTT 5)

发送端无法预料到无效报文和协议错误以外的错误,因为它可能有某些没有告知发送端的约束。客户端或服务端可能在接收时遇到短暂的错误,比如内存不足,导致无法成功的处理某个MQTT控制报文。

包含原因码大于等于0x80的确认报文PUBACK,PUBREC,PUBREL,PUBCOMP,SUBACK,UNSUBACK表明收到了某个报文标识符的报文出错。这不会影响其他会话或此会话上的其他报文。

CONNACK报文和DISCONNECT报文允许使用大于等于0x80的原因码以指示网络连接将被关闭。如果某个大于等于0x80的原因码被指定,无论是否发送CONNACK报文或DISCONNECT报文,必须关闭网络连接。发送这些原因码不会影响任何其他会话。

如果控制报文包含多个错误,接收端可以按照任意顺序对报文进行验证,并对发现的任何错误采取适当的行为。

MQTT3.1.1的错误处理

除非另有说明,如果服务端或客户端遇到了协议违规的行为,它必须关闭传输这个协议违规控制报文的网络连接 。
客户端或服务端实现可能会遇到瞬时错误(Transient Error)(例如内部缓冲区满了的情况)导致无法成功处理MQTT报文。
如果客户端或服务端处理入站控制报文时遇到了瞬时错误,它必须关闭传输那个控制报文的网络连接 。如果服务端发现了瞬时错误,它不应该断开连接或者执行任何对其它客户端有影响的操作。

发布了189 篇原创文章 · 获赞 675 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/YuYunTan/article/details/102519576