MQTT 5.0
参考文档:
- http://docs.oasis-open.org/mqtt/mqtt/v5.0/csprd02/mqtt-v5.0-csprd02.html
- https://github.com/mcxiaoke/mqtt
摘要
MQTT 是物联网 (IoT) 最常用的消息传递协议。 MQTT 代表 MQ 遥测传输。该协议是一组规则,定义了物联网设备如何通过互联网发布和订阅数据。 MQTT 用于物联网和工业物联网 (IIoT) 设备之间的消息传递和数据交换,例如嵌入式设备、传感器、工业 PLC 等。该协议是事件驱动的,并使用发布/订阅 (Pub/Sub) 模式连接设备。发送者(Publisher)和接收者(Subscriber)通过 Topics 进行通信,相互解耦。它们之间的连接由 MQTT 代理处理。 MQTT 代理过滤所有传入消息并将它们正确分发给订阅者。它的设计思想是轻巧、开放、简单、规范,因此易于实现。这些特点使得它对很多场景来说都是很好的选择,包括受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT),这些场景要求很小的代码封装或者网络带宽非常昂贵。
本协议运行在TCP/IP,或其它提供了有序、可靠、双向连接的网络连接上。它有以下特点:
- 使用发布/订阅消息模式,提供了一对多的消息分发和应用之间的解耦。
- 消息传输不需要知道负载内容。
- 提供三种等级的服务质量(QoS):① “最多一次”,尽操作环境所能提供的最大努力分发消息。消息可能会丢失。例如,这个等级可用于环境传感器数据,单次的数据丢失没关系,因为不久之后会再次发送。② “至少一次”,保证消息可以到达,但是可能会重复。③ “仅一次”,保证消息只到达一次。例如,这个等级可用在一个计费系统中,这里如果消息重复或丢失会导致不正确的收费。
- 很小的传输消耗和协议数据交换,最大限度减少网络流量。
- 异常连接断开发生时,能通知到相关各方。
1、概述 Introduction
1.0、知识产权政策 IPR Policy
本委员会规范公开审查草案是在 OASIS 知识产权政策的非断言模式下制定的,该模式是技术委员会成立时选择的模式。
有关是否已披露任何可能对实施本规范至关重要的专利的信息,以及任何专利许可条款的提供,请参阅 TC 网页 (https://www.oasis-open.org) 的知识产权部分。组织/委员会/mqtt/ipr.php)。
1.1、MQTT规范的组织 Organization of the MQTT specification
规范分为七章:
- 第1章 - 简介
- 第2章 - MQTT 控制包格式
- 第3章 - MQTT 控制数据包
- 第4章 - 操作行为
- 第5章 - 使用 WebSocket 作为网络传输
- 第6章 - 一致性目标
1.2、术语 Terminology
本规范中用到的关键字 必须 MUST,不能 MUST NOT,要求 REQUIRED,将会 SHALL,不会 SHALL NOT,应该 SHOULD,不应该 SHOULD NOT,推荐 RECOMMENDED,可以 MAY,可选 OPTIONAL 都是按照 IETF RFC 2119 [RFC2119] 中的描述解释。
1.2.1、网络连接 NetWork Connection
MQTT使用的底层传输协议基础设施。
- 客户端使用它连接服务端。
- 它提供有序的、可靠的、双向字节流传输。
1.2.2、应用消息 Application Message
MQTT协议通过网络传输应用数据。应用消息通过MQTT传输时,它们有关联的服务质量(QoS)和主题(Topic)。
1.2.3、客户端 Client
使用MQTT的程序或设备。客户端总是通过网络连接到服务端。它可以
- 发布应用消息给其它相关的客户端。
- 订阅以请求接受相关的应用消息。
- 取消订阅以移除接受应用消息的请求。
- 从服务端断开连接。
1.2.4、服务端 Server
一个程序或设备,作为发送消息的客户端和请求订阅的客户端之间的中介。服务端
- 接受来自客户端的网络连接。
- 接受客户端发布的应用消息。
- 处理客户端的订阅和取消订阅请求。
- 转发应用消息给符合条件的已订阅客户端。
1.2.5、订阅 Subscription
订阅包含一个主题过滤器(Topic Filter)和一个最大的服务质量(QoS)等级。订阅与单个会话(Session)关联。会话可以包含多于一个的订阅。会话的每个订阅都有一个不同的主题过滤器。
1.2.6、共享订阅 Shared Subscription
共享订阅包括主题过滤器和最大 QoS。共享订阅可以与多个会话相关联,以允许更广泛的消息交换模式。与共享订阅匹配的应用程序消息仅发送给与这些会话之一关联的客户端。一个会话可以订阅多个共享订阅,并且可以同时包含共享订阅和非共享订阅。
1.2.7、通配符订阅 Wildcard Subscription
通配符订阅是一种主题过滤器包含一个或多个通配符的订阅。这允许订阅匹配多个主题名称。
1.2.8、主题名称 Topic Name
附加在应用消息上的一个标签,服务端已知且与订阅匹配。服务端发送应用消息的一个副本给每一个匹配的客户端订阅。
1.2.9、主题过滤器 Topic Filter
订阅中包含的一个表达式,用于表示相关的一个或多个主题。主题过滤器可以使用通配符。
1.2.10、会话 Session
客户端和服务端之间的状态交互。一些会话持续时长与网络连接一样,其他会话可以跨越客户端和服务器之间的多个连续网络连接。
1.2.11、MQTT控制报文 MQTT Control Packet
通过网络连接发送的信息数据包。MQTT规范定义了十五种不同类型的控制报文,其中一个(PUBLISH报文)用于传输应用消息。
1.2.12、遗嘱消息 Will Message
在网络连接未正常关闭的情况下,服务器在网络连接关闭后发布的应用消息。
1.3、规范性引用文件 Normative references
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997,
http://www.rfc-editor.org/info/rfc2119
[RFC3629]
Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November 2003,
http://www.rfc-editor.org/info/rfc3629
[RFC6455]
Fette, I. and A. Melnikov, "The WebSocket Protocol", RFC 6455, DOI 10.17487/RFC6455, December 2011,
http://www.rfc-editor.org/info/rfc6455
[Unicode]
The Unicode Consortium. The Unicode Standard,
http://www.unicode.org/versions/latest/
1.4、非规范性引用文件 Non-normative references
[RFC0793]
Postel, J., "Transmission Control Protocol", STD 7, RFC 793, DOI 10.17487/RFC0793, September 1981, http://www.rfc-editor.org/info/rfc793
[RFC5246]
Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, DOI 10.17487/RFC5246, August 2008,
http://www.rfc-editor.org/info/rfc5246
[AES]
Advanced Encryption Standard (AES) (FIPS PUB 197).
https://csrc.nist.gov/csrc/media/publications/fips/197/final/documents/fips-197.pdf
[CHACHA20]
ChaCha20 and Poly1305 for IETF Protocols
https://tools.ietf.org/html/rfc7539
[FIPS1402]
Security Requirements for Cryptographic Modules (FIPS PUB 140-2)
https://csrc.nist.gov/csrc/media/publications/fips/140/2/final/documents/fips1402.pdf
[IEEE 802.1AR]
IEEE Standard for Local and metropolitan area networks - Secure Device Identity
http://standards.ieee.org/findstds/standard/802.1AR-2009.html
[ISO29192]
ISO/IEC 29192-1:2012 Information technology -- Security techniques -- Lightweight cryptography -- Part 1: General
https://www.iso.org/standard/56425.html
[MQTT NIST]
MQTT supplemental publication, MQTT and the NIST Framework for Improving Critical Infrastructure Cybersecurity
http://docs.oasis-open.org/mqtt/mqtt-nist-cybersecurity/v1.0/mqtt-nist-cybersecurity-v1.0.html
[MQTTV311]
MQTT V3.1.1 Protocol Specification
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
[ISO20922]
MQTT V3.1.1 ISO Standard (ISO/IEC 20922:2016)
https://www.iso.org/standard/69466.html
[NISTCSF]
Improving Critical Infrastructure Cybersecurity Executive Order 13636
https://www.nist.gov/sites/default/files/documents/itl/preliminary-cybersecurity-framework.pdf
[NIST7628]
NISTIR 7628 Guidelines for Smart Grid Cyber Security Catalogue
https://www.nist.gov/sites/default/files/documents/smartgrid/nistir-7628_total.pdf
[NSAB]
NSA Suite B Cryptography
http://www.nsa.gov/ia/programs/suiteb_cryptography/
[PCIDSS]
PCI-DSS Payment Card Industry Data Security Standard
https://www.pcisecuritystandards.org/pci_security/
[RFC1928]
Leech, M., Ganis, M., Lee, Y., Kuris, R., Koblas, D., and L. Jones, "SOCKS Protocol Version 5", RFC 1928, DOI 10.17487/RFC1928, March 1996,
http://www.rfc-editor.org/info/rfc1928
[RFC4511]
Sermersheim, J., Ed., "Lightweight Directory Access Protocol (LDAP): The Protocol", RFC 4511, DOI 10.17487/RFC4511, June 2006,
http://www.rfc-editor.org/info/rfc4511
[RFC5280]
Cooper, D., Santesson, S., Farrell, S., Boeyen, S., Housley, R., and W. Polk, "Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, May 2008,
http://www.rfc-editor.org/info/rfc5280
[RFC6066]
Eastlake 3rd, D., "Transport Layer Security (TLS) Extensions: Extension Definitions", RFC 6066, DOI 10.17487/RFC6066, January 2011,
http://www.rfc-editor.org/info/rfc6066
[RFC6749]
Hardt, D., Ed., "The OAuth 2.0 Authorization Framework", RFC 6749, DOI 10.17487/RFC6749, October 2012,
http://www.rfc-editor.org/info/rfc6749
[RFC6960]
Santesson, S., Myers, M., Ankney, R., Malpani, A., Galperin, S., and C. Adams, "X.509 Internet Public Key Infrastructure Online Certificate Status Protocol - OCSP", RFC 6960, DOI 10.17487/RFC6960, June 2013,
http://www.rfc-editor.org/info/rfc6960
[SARBANES]
Sarbanes-Oxley Act of 2002.
http://www.gpo.gov/fdsys/pkg/PLAW-107publ204/html/PLAW-107publ204.htm
[USEUPRIVSH]
U.S.-EU Privacy Shield Framework
[RFC3986]
Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005,
http://www.rfc-editor.org/info/rfc3986
[RFC1035]
Mockapetris, P., "Domain names - implementation and specification", STD 13, RFC 1035, DOI 10.17487/RFC1035, November 1987,
http://www.rfc-editor.org/info/rfc1035
[RFC2782]
Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR for specifying the location of services (DNS SRV)", RFC 2782, DOI 10.17487/RFC2782, February 2000,
http://www.rfc-editor.org/info/rfc2782
1.5、数据表示 Data representations
1.5.1、二进制位 Bits
字节中的位从0到7。第7位是最高有效位,第0位是最低有效位。
1.5.2、二字节整数 Two Byte Integer
两个字节整数数据值是大端顺序的 16 位无符号整数:高位字节在低位字节之前。这意味着 16 位字在网络上显示为最高有效字节 (MSB),然后是最低有效字节 (LSB)。
1.5.4、四字节整数 Four Byte Integer
四字节整数数据值是大端顺序的 32 位无符号整数:高位字节在连续的低位字节之前。这意味着 32 位字在网络上显示为最高有效字节 (MSB),然后是下一个最高有效字节 (MSB),然后是下一个最高有效字节 (MSB),然后是最低有效字节 (LSB) ).
1.5.4、UTF-8编码字符串 UTF-8 encoded strings
后面会描述的控制报文中的文本字段编码为UTF-8格式的字符串。UTF-8 [RFC3629] 是一个高效的Unicode字符编码格式,为了支持基于文本的通信,它对ASCII字符的编码做了优化。
每一个字符串都有一个两字节的长度字段作为前缀,它给出这个字符串UTF-8编码的字节数,它们在图例 1.1 UTF-8编码字符串的结构 中描述。因此可以传送的UTF-8编码的字符串大小有一个限制,不能超过 65535字节。
除非另有说明,所有的UTF-8编码字符串的长度都必须在0到65535字节这个范围内。
图例 1.1 UTF-8编码字符串的结构 Structure of UTF-8 encoded strings
二进制位 | 7-0 |
---|---|
byte 1 | 字符串长度的最高有效字节(MSB) |
byte 2 | 字符串长度的最低有效字节(LSB) |
byte 3 …. | 如果长度大于0,这里是UTF-8编码的字符数据。 |
UTF-8编码字符串中的字符数据必须是按照Unicode规范 [Unicode] 定义的和在RFC3629 [RFC3629] 中重申的有效的UTF-8格式。特别需要指出的是,这些数据不能包含字符码在U+D800和U+DFFF之间的数据。如果服务端或客户端收到了一个包含无效UTF-8字符的控制报文,它必须关闭网络连接 [MQTT-1.5.3-1]。
UTF-8编码的字符串不能包含空字符U+0000。如果客户端或服务端收到了一个包含U+0000的控制报文,它必须关闭网络连接 [MQTT-1.5.3-2]。
数据中不应该包含下面这些Unicode代码点的编码。如果一个接收者(服务端或客户端)收到了包含下列任意字符的控制报文,它可以关闭网络连接:
- U+0001和U+001F之间的控制字符。
- U+007F和U+009F之间的控制字符。
- Unicode规范定义的非字符代码点(例如U+0FFFF)。
- Unicode规范定义的保留字符(例如U+0FFFF)。
UTF-8编码序列0XEF 0xBB 0xBF总是被解释为U+FEFF(零宽度非换行空白字符),无论它出现在字符串的什么位置,报文接收者都不能跳过或者剥离它 [MQTT-1.5.3-3]。
非规范示例 Non normative example
例如,字符串 A𪛔 是一个拉丁字母A后面跟着一个代码点U+2A6D4(它表示一个中日韩统一表意文字扩展B中的字符),这个字符串编码如下:
图例 1.2 UTF-8编码字符串非规范示例 UTF-8 encoded string non normative example
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
byte 1 | 字符串长度 MSB (0x00) | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 2 | 字符串长度 LSB (0x05) | |||||||
0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | |
byte 3 | ‘A’ (0x41) | |||||||
0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | |
byte 4 | (0xF0) | |||||||
1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | |
byte 5 | (0xAA) | |||||||
1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | |
byte 6 | (0x9B) | |||||||
1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | |
byte 7 | (0x94) | |||||||
1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
1.5.5、Variable Byte Integer 可变字节整数
可变字节整数使用一种编码方案进行编码,该方案使用单个字节来表示最大 127 的值。更大的值按如下方式处理。每个字节的最低七位对数据进行编码,最高有效位用于指示表示中是否有后续字节。因此,每个字节编码 128 个值和一个“继续位”。可变字节整数字段中的最大字节数是四。编码值必须使用表示该值所需的最少字节数 [MQTT-1.5.5-1]。这显示在表 1‑1 可变字节整数的大小中。
字节 | 最小值 | 最大值 |
1 | 0 (0x00) | 127 (0x7F) |
2 | 128 (0x80, 0x01) | 16383 (0xFF, 0x7F) |
3 | 16384 (0x80, 0x80, 0x01) | 2097151 (0xFF, 0xFF, 0x7F) |
4 | 2097152 (0x80, 0x80, 0x80, 0x01) | 268435455 (0xFF, 0xFF, 0xFF, 0x7F) |
非规范评注:例如,十进制数64会被编码为一个字节,数值是64,十六进制表示为0x40,。十进制数字321(=65+2*128)被编码为两个字节,最低有效位在前。第一个字节是 65+128=193。注意最高位为1表示后面至少还有一个字节。第二个字节是2。
非规范评注:这允许应用发送最大256MB(268,435,455)大小的控制报文。这个数值在报文中的表示是:0xFF,0xFF,0xFF,0x7F。
非规范评注 Non-normative comment:
将非负整数(X)编码成可变字节整数编码方案的算法如下:
do
encodedByte = X MOD 128
X = X DIV 128
// 如果有更多的数据要编码,设置这个字节的最高位
if (X > 0)
encodedByte = encodedByte OR 128
endif
'output' encodedByte
while (X > 0)
其中 MOD 是模运算符(C 中的 %),DIV 是整数除法(C 中的 /),OR 是按位或(C 中的 |)。
解码可变字节整数类型的算法如下:
multiplier = 1
value = 0
do
encodedByte = 'next byte from stream'
value += (encodedByte AND 127) * multiplier
if (multiplier > 128 * 128 * 128)
throw Error(Malformed Variable Byte Integer) // 格式错误的可变字节整数
multiplier *= 128
while ((encodedByte AND 128) != 0) while ((encodedByte AND 128) != 0)
其中AND是按位与运算符(& 在 C 中)。当该算法终止时,值包含可变字节整数值。
1.5.6、二进制数据 Binary Data
二进制数据由表示数据字节数的两字节整数长度,后跟该字节数来表示。因此,二进制数据的长度限制在 0 到 65,535 字节的范围内。
1.5.7、UTF-8 字符串对 UTF-8 String Pair
UTF-8 字符串对由两个 UTF-8 编码字符串组成。此数据类型用于保存名称-值对。第一个字符串用作名称,第二个字符串包含值。
两个字符串都必须符合 UTF-8 编码字符串 [MQTT-1.5.7-1] 的要求。如果接收方(客户端或服务器)收到不符合这些要求的字符串对,则它是格式错误的数据包。有关处理错误的信息,请参阅第 4.13 节。
1.6、安全 Security
MQTT 客户端和服务器实现应该提供身份验证、授权和安全通信选项,例如第 5 章中讨论的选项。强烈建议与关键基础设施、个人身份信息或其他个人或敏感信息有关的应用程序使用这些安全功能。
1.7、编辑约定
本规范中以黄色突出显示的文本标识一致性声明。每个一致性声明都被分配了一个格式为 [MQTT-x.x.x-y] 的引用,其中 x.x.x 是部分编号,y 是该部分内的声明计数器。
1.8、变更历史 Change history
1.8.1、MQTT v3.1.1
MQTT v3.1.1 是第一个 OASIS 标准版本的 MQTT [MQTTV311]。
MQTT v3.1.1 也被标准化为 ISO/IEC 20922:2016 [ISO20922]。
1.8.2、MQTT v5.0
- MQTT v5.0 为 MQTT 添加了大量新功能,同时保留了大部分核心内容。主要功能目标是:
- 可扩展性和大型系统的增强;
- 改进了错误报告;
- 形式化常见模式,包括能力发现和请求响应;
- 包括用户属性在内的扩展机制;
- 性能改进和对小客户的支持。
2、MQTT控制报文结构 Structure of an MQTT Control Packet
MQTT 协议通过以定义的方式交换一系列 MQTT 控制数据包来运行。本节描述这些数据包的格式。
MQTT控制数据包最多包含三个部分,始终按以下顺序组成,如下所示:
固定报头。存在于所有MQTT控制数据包中 |
可变报头。存在于某些MQTT控制数据包中 |
有效载荷。存在于某些MQTT控制数据包中 |
2.1、固定报头 Fixed Header
每个MQTT控制数据包包含一个固定的报头,如下所示。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
字节1 | MQTT控制报文的类型 | 用于指定控制报文类型的标志位 | ||||||
字节2 ... | 剩余长度 |
2.1.1、MQTT 控制报文的类型
位置:字节1,位7-4。该值表示为4位无符号值,如下所示。
名字 | 值 | 报文流动方向 | 描述 |
---|---|---|---|
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 | 禁止 | 保留 |
2.1.2、标志 Flags
固定报头第1个字节的剩余的4位 [3-0]包含每个MQTT控制报文类型特定的标志。任何标记为“保留”的标志位,都是保留给以后使用的,必须设置为下面表格中列出的值 [MQTT-2.2.2-1]。如果收到非法的标志,接收者必须关闭网络连接。有关错误处理的详细信息见 4.8节 [MQTT-2.2.2-2]。
控制报文 | 固定报头标志 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|
CONNECT | Reserved | 0 | 0 | 0 | 0 |
CONNACK | Reserved | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | DUP | QoS | QoS2 | RETAIN |
PUBACK | Reserved | 0 | 0 | 0 | 0 |
PUBREC | Reserved | 0 | 0 | 0 | 0 |
PUBREL | Reserved | 0 | 0 | 1 | 0 |
PUBCOMP | Reserved | 0 | 0 | 0 | 0 |
SUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
SUBACK | Reserved | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
UNSUBACK | Reserved | 0 | 0 | 0 | 0 |
PINGREQ | Reserved | 0 | 0 | 0 | 0 |
PINGRESP | Reserved | 0 | 0 | 0 | 0 |
DISCONNECT | Reserved | 0 | 0 | 0 | 0 |
- DUP:=控制报文的重复分发标志。
- QoS:=PUBLISH报文的服务质量等级。
- RETAIN:=PUBLISH报文的保留标志。
PUBLISH控制报文中的DUP, QoS和RETAIN标志的描述见 3.3.1节。
2.1.3、剩余长度 Remaining Length
位置:从第2个字节开始。
剩余长度字段使用一个变长度编码方案(即1.5.5节描述的可变字节整数)。低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码128个数值和一个延续位(continuation bit)。剩余长度字段最大4个字节。具体描述见1.1.5节。
2.2、可变报头 Variable header
某些MQTT控制报文包含一个可变报头部分。它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里。
2.2.1、报文标识符 Packet Identifier
Bit | 7 - 0 |
---|---|
byte 1 | 报文标识符 MSB |
byte 2 | 报文标识符 LSB |
很多控制报文的可变报头部分包含一个两字节的报文标识符字段。这些报文是 PUBLISH(QoS > 0时), PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE, SUBACK,UNSUBSCRIBE,UNSUBACK。
SUBSCRIBE,UNSUBSCRIBE和PUBLISH(QoS大于0)控制报文必须包含一个非零的16位报文标识符(Packet Identifier)[MQTT-2.3.1-1]。客户端每次发送一个新的这些类型的报文时都必须分配一个当前未使用的报文标识符 [MQTT-2.3.1-2]。如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。当客户端处理完这个报文对应的确认后,这个报文标识符就释放可重用。QoS 1的PUBLISH对应的是PUBACK,QoS 2的PUBLISH对应的是PUBCOMP,与SUBSCRIBE或UNSUBSCRIBE对应的分别是SUBACK或UNSUBACK [MQTT-2.3.1-3]。发送一个QoS 0的PUBLISH报文时,相同的条件也适用于服务端 [MQTT-2.3.1-4]。
QoS等于0的PUBLISH报文不能包含报文标识符 [MQTT-2.3.1-5]。
PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的报文标识符 [MQTT-2.3.1-6]。类似地,SUBACK和UNSUBACK必须包含在对应的SUBSCRIBE和UNSUBSCRIBE报文中使用的报文标识符 [MQTT-2.3.1-7]。
需要报文标识符的控制报文在下表列出。
控制报文 | 报文标识符字段 |
---|---|
CONNECT | 不需要 |
CONNACK | 不需要 |
PUBLISH | 需要(如果QoS > 0) |
PUBACK | 需要 |
PUBREC | 需要 |
PUBREL | 需要 |
PUBCOMP | 需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
如果 QoS 值设置为 0 [MQTT-2.2.1-2],则 PUBLISH 数据包不得包含数据包标识符。
每次客户端发送新的 SUBSCRIBE、UNSUBSCRIBE 或 PUBLISH(其中 QoS > 0)MQTT 控制数据包时,它必须为其分配一个当前未使用的非零数据包标识符 [MQTT-2.2.1-3]。
每次服务器发送一个新的 PUBLISH(QoS > 0)MQTT 控制数据包时,它必须为其分配一个当前未使用的非零数据包标识符 [MQTT-2.2.1-4]。
在发送方处理了相应的确认数据包后,数据包标识符可以重新使用,定义如下。在 QoS 1 PUBLISH 的情况下,这是相应的 PUBACK;在 QoS 2 PUBLISH 的情况下,它是 PUBCOMP 或 PUBREC,原因代码为 128 或更大。对于 SUBSCRIBE 或 UNSUBSCRIBE,它是相应的 SUBACK 或 UNSUBACK。
与 PUBLISH、SUBSCRIBE 和 UNSUBSCRIBE 数据包一起使用的数据包标识符形成了一个单一的、统一的标识符集,分别用于会话中的客户端和服务器。一个数据包标识符在任何时候都不能被多个命令使用。
PUBACK、PUBREC、PUBREL 或 PUBCOMP 数据包必须包含与最初发送的 PUBLISH 数据包相同的数据包标识符 [MQTT-2.2.1-5]。 SUBACK 和 UNSUBACK 必须包含分别在相应的 SUBSCRIBE 和 UNSUBSCRIBE 数据包中使用的数据包标识符 [MQTT-2.2.1-6]。
客户端和服务器彼此独立分配数据包标识符。因此,客户端-服务器对可以使用相同的数据包标识符参与并发消息交换。
非规范评注:
客户端有可能发送一个带有数据包标识符 0x1234 的 PUBLISH 数据包,然后在它收到针对它发送的 PUBLISH 数据包的 PUBACK 之前,从它的服务器接收一个不同的带有数据包标识符 0x1234 的 PUBLISH 数据包:
Client | Server |
PUBLISH 数据包标识符=0x1234 ——> | |
<—— PUBLISH 数据包标识符=0x1234 | |
PUBACK 数据包标识符=0x1234 ——> | |
<—— PUBACK 数据包标识符=0x1234 |
2.2.2、Properties 属性
CONNECT、CONNACK、PUBLISH、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBACK、DISCONNECT 和 AUTH 数据包的可变报头中的最后一个字段是一个属性集。在 CONNECT 数据包中,Will Properties 字段中还有一组可选的 Properties 和 Payload。
属性集由属性长度(Property Length)后跟属性(Properties)组成。
2.2.2.1、属性长度 Property Length
属性长度被编码为可变字节整数(见1.5.5节)。 Property Length 不包括用于对自身进行编码的字节数,但包括 Properties 的长度。如果没有属性,则必须通过包含零属性长度来指示 [MQTT-2.2.2-1]。
2.2.2.2、属性 Property
属性由标识符(定义用法和数据类型)和后跟的值组成。标识符被编码为可变字节整数(见1.5.5节)。包含对其数据包类型无效的标识符或包含非指定数据类型的值的控制数据包是格式错误的数据包。如果收到,请使用原因代码为 0x81(格式错误的数据包)的 CONNACK 或 DISCONNECT 数据包。具有不同标识符的属性的顺序没有意义。
标识符 | 名称(用途) | 类型 | 数据包 / Will Properties | |
十进制 | 十六进制 | |||
1 | 0x01 | 有效载荷指示符 | 字节 | PUBLISH,Will Properties |
2 | 0x02 | 消息过期时间 | 四字节整数 | PUBLISH,Will Properties |
3 | 0x03 | 内容类型 | UTF-8编码字符串 | PUBLISH,Will Properties |
8 | 0x08 | 响应主题 | UTF-8编码字符串 | PUBLISH,Will Properties |
9 | 0x09 | 相关数据 | 二进制数据 | PUBLISH,Will Properties |
11 | 0x0B | 订阅标识符 | 可变字节整数 | PUBLISH,SUBSCRIBE |
17 | 0x11 | 会话到期时间 | 四字节整数 | CONNECT,CONNACK,DISCONNECT |
18 | 0x12 | 分配的客户端标识符 | UTF-8编码字符串 | CONNACK |
19 | 0x13 | 服务器保持活跃 | 二字节整数 | CONNACK |
21 | 0x15 | 身份验证方法 | UTF-8编码字符串 | CONNECT,CONNACK,AUTH |
22 | 0x16 | 认证数据 | 二进制数据 | CONNECT,CONNACK,AUTH |
23 | 0x17 | 请求问题信息 | 字节 | CONNECT |
24 | 0x18 | Will延迟间隔 | 四字节整数 | Will Properties |
25 | 0x19 | 请求响应信息 | 字节 | CONNECT |
26 | 0x1A | 响应信息 | UTF-8编码字符串 | CONNACK |
28 | 0x1C | 服务器引用 | UTF-8编码字符串 | CONNACK,DISCONNECT |
31 | 0x1F | 原因字符串 | UTF-8编码字符串 |
CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK, DISCONNECT, AUTH |
33 | 0x21 | 收到最大值 | 二字节整数 | CONNECT,CONNACK |
34 | 0x22 | 主题别名最大值 | 二字节整数 | CONNECT,CONNACK |
35 | 0x23 | 主题别名 | 二字节整数 | PUBLISH |
36 | 0x24 | 最大QoS | 字节 | CONNACK |
37 | 0x25 | 保留可用 | 字节 | CONNACK |
38 | 0x26 | 用户属性 | UTF-8编码字符串 | CONNECT,CONNACK,PUBLISH,Will properties, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE |
39 | 0x27 | 最大数据包大小 | 四字节整数 | CONNECT, CONNACK |
40 | 0x28 | 通配符订阅可用 | 字节 | CONNACK |
41 | 0x29 | 订阅标识符可用 | 字节 | CONNACK |
42 | 0x2A | 共享订阅可用 | 字节 | CONNACK |
尽管属性标识符定义为可变字节整数,但在此版本的规范中,所有属性标识符都是一个字节长度。
2.3、有效载荷 Payload
某些MQTT控制报文在报文的最后部分包含一个有效载荷,这将在第3章论述。对于PUBLISH来说有效载荷就是应用消息。
下表列出了需要有效载荷的控制报文。
控制报文 | 有效载荷 |
---|---|
CONNECT | 需要 |
CONNACK | 不需要 |
PUBLISH | 可选 |
PUBACK | 不需要 |
PUBREC | 不需要 |
PUBREL | 不需要 |
PUBCOMP | 不需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 不需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
2.4、原因码 Reason Code
原因码是一个字节的无符号值,用于指示操作的结果。小于0x80的原因码表示操作成功完成、表示成功的常规原因码为0。值为0x80或更大的原因码表示失败。
CONNACK、PUBACK、PUBREC、PUBREL、PUBCOMP、DISCONNECT 和 AUTH 控制包有一个单一的原因码作为可变报头的一部分。SUBACK 和 UNSUBACK 数据包在有效负载中包含一个或多个原因码的列表。
原因代码共享一组通用值,如下所示。
原因码 | 名称 | 数据包 | |
十进制 | 十六进制 | ||
0 | 0x00 | 成功 | CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK, AUTH |
0 | 0x00 | 正常断开 | DISCONNECT |
0 | 0x00 | 授予 QoS 0 | SUBACK |
1 | 0x01 | 授予 QoS 1 | SUBACK |
2 | 0x02 | 授予 QoS 2 | SUBACK |
4 | 0x04 | 带遗嘱消息的断开连接 | DISCONNECT |
16 | 0x10 | 没有匹配的订阅者 | PUBACK, PUBREC |
17 | 0x11 | 不存在任何订阅 | UNSUBACK |
24 | 0x18 | 继续身份验证 | AUTH |
25 | 0x19 | 重新认证 | AUTH |
128 | 0x80 | 未指定的错误 | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT |
129 | 0x81 | 格式错误的数据包 | CONNACK, DISCONNECT |
130 | 0x82 | 协议错误 | CONNACK, DISCONNECT |
131 | 0x83 | 具体实施错误 | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT |
132 | 0x84 | 不支持的协议版本 | CONNACK |
133 | 0x85 | 客户端标识符无效 | CONNACK |
134 | 0x86 | 用户名或密码错误 | CONNACK |
135 | 0x87 | 未经授权 | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT |
136 | 0x88 | 服务器不可用 | CONNACK |
137 | 0x89 | 服务器繁忙 | CONNACK, DISCONNECT |
138 | 0x8A | 已禁用 | CONNACK |
139 | 0x8B | 服务器关闭 | DISCONNECT |
140 | 0x8C | 错误的身份验证方法 | CONNACK, DISCONNECT |
141 | 0x8D | 保持活跃(Keep Alive)超时 | DISCONNECT |
142 | 0x8E | 被接管的会话 | DISCONNECT |
143 | 0x8F | 主题过滤器无效 | SUBACK, UNSUBACK, DISCONNECT |
144 | 0x90 | 主题名称无效 | CONNACK, PUBACK, PUBREC, DISCONNECT |
145 | 0x91 | 使用中的数据包标识符 | PUBACK, PUBREC, SUBACK, UNSUBACK |
146 | 0x92 | 未找到的数据包标识符 | PUBREL, PUBCOMP |
147 | 0x93 | 超出接收的最大值 | DISCONNECT |
148 | 0x94 | 主题别名无效 | DISCONNECT |
149 | 0x95 | 数据包太大 | CONNACK, DISCONNECT |
150 | 0x96 | 消息率过高 | DISCONNECT |
151 | 0x97 | 超出配额 | CONNACK, PUBACK, PUBREC, SUBACK, DISCONNECT |
152 | 0x98 | Administrative action | DISCONNECT |
153 | 0x99 | 有效负载格式无效 | CONNACK, PUBACK, PUBREC, DISCONNECT |
154 | 0x9A | 不支持保留 | CONNACK, DISCONNECT |
155 | 0x9B | 不支持QoS | CONNACK, DISCONNECT |
156 | 0x9C | 使用另一台服务器 | CONNACK, DISCONNECT |
157 | 0x9D | 服务器被移动 | CONNACK, DISCONNECT |
158 | 0x9E | 不支持共享订阅 | CONNACK, DISCONNECT |
159 | 0x9F | 超出连接速率 | CONNACK, DISCONNECT |
160 | 0xA0 | 最大连接时间 | DISCONNECT |
161 | 0xA1 | 不支持订阅标识符 | SUBACK, DISCONNECT |
162 | 0xA2 | 不支持通配符订阅 | SUBACK, DISCONNECT |
非规范评注:
对于原因代码 0x91(正在使用的数据包标识符),对此的响应是尝试修复状态,或者通过使用设置为 1 的 Clean Start 进行连接来重置会话状态,或者确定客户端或服务器实现是否有缺陷.
3、MQTT控制报文
3.1、CONNECT —— 连接请求 CONNECT - Connection Request
客户端到服务器建立网络连接后,从客户端发送到服务器的第一个数据包必须是 CONNECT 数据包 [MQTT-3.1.0-1]。客户端只能通过网络连接发送一次 CONNECT 数据包。服务器必须处理从客户端发送的第二个 CONNECT 数据包作为协议错误并关闭网络连接 [MQTT-3.1.0-2]。有关处理错误的信息,请参阅第 4.13 节。
有效载荷包含一个或多个编码字段,包括客户端唯一的客户标识符、Will主题、Will有效负载、用户名和密码。除了客户端标识之外,其它的字段都是可选的,基于标志位来决定可变报头中是否需要包含这些字段。
3.1.1、CONNECT 固定报头 CONNECT Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
字节1 | MQTT控制报文类型(1) | Reserved 保留位 | ||||||
字节2 ... | 剩余长度 |
剩余长度字段:剩余长度等于可变报头的长度(10字节)加上有效载荷的长度。剩余长度被编码为可变字节整数。
3.1.2、CONNECT 可变报头 CONNECT Variable Header
CONNECT 数据包的可变报头按以下顺序包含以下字段:协议名称(Protocol Name)、协议级别(Protocol Level)、连接标志(Connect Flags)、保持连接(Keep Alive)和属性(Properties)。
3.1.2.1、协议名称 Protocol Name
说明 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
协议名 | |||||||||
byte 1 | 长度 MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 2 | 长度 LSB (4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
byte 3 | ‘M’ | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
byte 4 | ‘Q’ | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
byte 5 | ‘T’ | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
byte 6 | ‘T’ | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
协议名称是一个 UTF-8 编码字符串,表示协议名称“MQTT”,大写字母如图所示。 MQTT 规范的未来版本不会更改字符串、其偏移量和长度。
如果协议名不正确服务端可以断开客户端的连接,也可以按照某些其它规范继续处理CONNECT报文。对于后一种情况,按照本规范,服务端不能继续处理CONNECT报文 [MQTT-3.1.2-1]。
支持多种协议的Server通过Protocol Name来判断数据是否为MQTT。协议名称必须是 UTF-8 字符串“MQTT”。如果服务器不想接受 CONNECT,并希望展示它是一个 MQTT 服务器,它可以发送一个 CONNACK 数据包,原因代码为 0x84(不支持的协议版本),然后它必须关闭网络连接 [MQTT-3.1 .2-1]。
非规范评注:数据包检测工具,例如防火墙,可以使用协议名称来识别MQTT流量。
3.1.2.2、协议级别 Protocol Level
说明 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
协议级别 | |||||||||
byte 7 | Level(5) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
客户端用8位的无符号值表示协议的修订版本。
对于3.1.1版协议,协议级别字段的值是4(0x04)。如果发现不支持的协议级别,服务端必须给发送一个返回码为0x01(不支持的协议级别)的CONNACK报文响应CONNECT报文,然后断开客户端的连接 [MQTT-3.1.2-2]。
当服务器支持的协议级别为5时,如果报文协议版本不是 5 并且服务器不想接受 CONNECT 数据包,服务器可以发送一个带有原因码 0x84(不支持的协议版本)的 CONNACK 数据包,然后必须关闭网络连接 [MQTT-3.1.2-2 ]。
3.1.2.3、连接标志 Connect Flags
连接标志字节包含一些用于指定MQTT连接行为的参数。它还指出有效载荷中的字段是否存在。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
用户名标志 | 密码标志 | Will Retain | Will QoS | Will Flag | Clean Start | 保留位 | ||
Level (5) | x | x | x | x | x | x | x | x |
服务端必须验证CONNECT控制报文的保留标志位(第0位)是否为0,如果不为0必须断开客户端连接 [MQTT-3.1.2-3]。
* Clean Start(干净启动):
位置:连接标志字节的第1位。该位指定 Connection 是开始一个新的 Session 还是现有 Session 的延续。
如果收到 CONNECT 数据包且 Clean Start 设置为 1,则客户端和服务器必须丢弃任何现有会话并启动新会话 [MQTT-3.1.2-4]。因此,如果 Clean Start 设置为 1,则 CONNACK 中的 Session Present 标志始终设置为 0。会话仅持续和网络连接同样长的时间。与这个会话关联的状态数据不能被任何之后的会话重用 [MQTT-3.1.2-6]。
如果收到 CONNECT 数据包,Clean Start 设置为 0,并且存在与客户端标识符关联的会话,则服务器必须根据现有会话 [MQTT-3.1.2-5] 的状态恢复与客户端的通信。如果收到一个 CONNECT 数据包,Clean Start 设置为 0,并且没有与客户端标识符关联的会话,则服务器必须创建一个新会话 [MQTT-3.1.2-6]。当连接断开后,客户端和服务端必须保存会话信息 [MQTT-3.1.2-4]。当 Clean Start 标志为0的会话连接断开之后,服务端必须将之后的QoS 1和QoS 2级别的消息保存为会话状态的一部分,如果这些消息匹配断开连接时客户端的任何订阅 [MQTT-3.1.2-5]。服务端也可以保存满足相同条件的QoS 0级别的消息。
客户端的会话状态包括:
- 已经发送给服务端,但是还没有完成确认的 QoS 1 和 QoS 2 级别的消息;
- 已从服务端接收,但是还没有完成确认的QoS 2级别的消息。
服务端的会话状态包括:
- 会话是否存在,即使会话状态的其它部分都是空;
- 客户端的订阅信息;
- 已经发送给客户端,但是还没有完成确认的 QoS 1 和 QoS 2 级别的消息;
- 即将传输给客户端的 QoS 1 和 QoS 2 级别的消息;
- 已从客户端接收,但是还没有完成确认的QoS 2级别的消息;
- 可选,准备发送给客户端的QoS 0级别的消息。
保留消息不是服务端会话状态的一部分,会话终止时不能删除保留消息 [MQTT-3.1.2.7]。
当 Clean Start 标志被设置为1时,客户端和服务端的状态删除不需要是原子操作。
非规范评注:
- 为了确保在发生故障时状态的一致性,客户端应该使用会话状态标志1重复请求连接,直到连接成功。
- 一般来说,客户端连接时总是将 Clean Start 标志设置为0或1,并且不交替使用两种值。这个选择取决于具体的应用。 Clean Start 标志设置为1的客户端不会收到旧的应用消息,而且在每次连接成功后都需要重新订阅任何相关的主题。 Clean Start 标志设置为0的客户端会收到所有在它连接断开期间发布的QoS 1和QoS 2级别的消息。因此,要确保不丢失连接断开期间的消息,需要使用QoS 1或 QoS 2级别,同时将 Clean Start 标志设置为0。
- Clean Start 标志0的客户端连接时,它请求服务端在连接断开后保留它的MQTT会话状态。如果打算在之后的某个时间点重连到这个服务端,客户端连接应该只使用 Clean Start 标志0。当客户端决定之后不再使用这个会话时,应该将 Clean Start 标志设置为1最后再连接一次,然后断开连接。
* Will Flag(遗嘱标志):
位置:连接标志字节的第 2 位。遗嘱标志(Will Flag)被设置为1,表示如果连接请求被接受了,遗嘱消息(Will Message)必须被存储在服务端并且与Session相关联。Will Message 由 CONNECT 的有效载荷(Payload)中的Will属性(Will Properties)、Will主题(Will Topic) 和 Will有效载荷(Will Payload) 字段组成。
除非服务器在收到原因代码为0x00(正常断开连接)的DISCONNECT数据包时删除了Will Message,或者在Will Delay Interval过期前为客户ID打开了一个新的网络连接,否则Will Message必须在网络连接随后关闭、Will Delay Interval过期或会话结束后发布[MQTT-3.1.2-8]。
发布Will消息的情况包括,但不限于:
- 服务器检测到一个I/O错误或网络故障。
- 客户端未能在Keep Alive时间内进行通信。
- 客户端没有首先发送一个原因代码为0x00(正常断开连接)的DISCONNECT数据包再关闭连接。
- 服务器在没有收到原因代码为0x00(正常断开连接)的DISCONNECT数据包的情况下关闭网络连接。
如果Will标志被设置为1,连接标志中的 Will QoS 和 Will Retain 字段会被服务端用到,同时Will属性、Will主题和Will有效载荷字段必须出现在有效载荷中[MQTT-3.1.2-9]。一旦Will消息被发布或服务器收到来自客户端的原因代码为0x00(正常断开连接)的 DISCONNECT 数据包,则必须将其从服务器中存储的会话状态中删除[MQTT-3.1.2-10]。
如果遗嘱标志被设置为0,连接标志中的 Will QoS 和 Will Retain 字段必须设置为0,并且有效载荷中不能包含 Will Message 字段 [MQTT-3.1.2-11]。并且网络连接断开时,不能发送遗嘱消息 [MQTT-3.1.2-12]。
服务器应在网络连接关闭和 Will Delay Interval 过后,或在会话结束时(以先发生者为准)及时发布 Will Message。在服务器关闭或故障的情况下,服务器可能会推迟发布 Will Message,直到随后重新启动。如果发生这种情况,在服务器发生故障和 Will Message发布的时间之间可能会有延迟。
非规范评注:客户端可以安排Will消息通知会话过期,方法是将Will延迟间隔设置为长于会话过期间隔,并发送原因代码为0x04的DISCONNECT(与Will消息断开连接)。
* Will QoS(遗嘱服务质量级别):
位置:连接标志的第4和第3位。这两个位指定了发布Will消息时要使用的服务质量(QoS)级别。
如果Will标志被设置为0,那么Will QoS必须被设置为0(0x00) [MQTT-3.1.2-11]。
如果遗嘱标志被设置为1,遗嘱QoS的值可以等于0(0x00),1(0x01),2(0x02)。它的值不能等于3 [MQTT-3.1.2-14],3 (0x03)的值是一个格式错误的数据包。
* Will Retain(遗嘱保留):
位置:连接标志的第5位。该位指定Will Message在发布时是否要保留。
如果Will Flag被设置为0,那么Will Retain必须被设置为0 [MQTT-3.1.2-13]。
如果Will Flag标志被设置为1:
- 如果Will Retain被设置为0,则服务器必须将Will消息作为非保留消息发布[MQTT-3.1.2-14]。
- 如果Will Retain被设置为1,则服务器必须将Will消息作为保留消息发布[MQTT-3.1.2-15]。
* User Name Flag(用户名称标志):
位置:连接标志的第7位。
如果用户名(User Name)标志被设置为0,有效载荷中不能包含用户名字段 [MQTT-3.1.2-18]。
如果用户名(User Name)标志被设置为1,有效载荷中必须包含用户名字段 [MQTT-3.1.2-19]。
* Password Flag(密码标志):
位置:连接标志的第6位。
如果密码(Password)标志被设置为0,有效载荷中不能包含密码字段 [MQTT-3.1.2-20]。
如果密码(Password)标志被设置为1,有效载荷中必须包含密码字段 [MQTT-3.1.2-21]。
非规范评注:5.0版本的协议允许发送没有用户名的密码,而MQTT v3.1.1则不允许(如果用户名标志被设置为0,密码标志也必须设置为0 [MQTT-3.1.2-22]。)。这反映了对密码以外的凭证的普遍使用。
3.1.2.4、Keep Alive 保持连接
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
byte 9 | 保持连接 Keep Alive MSB | |||||||
byte 10 | 保持连接 Keep Alive LSB |
Keep Alive是一个两字节的整数,是以秒为单位的时间间隔。它是允许在客户端完成发送一个MQTT控制包和开始发送下一个包之间的最大时间间隔。客户端有责任确保发送MQTT控制包的时间间隔不超过Keep Alive值。如果Keep Alive非零,并且在没有发送任何其他MQTT控制包的情况下,客户端必须发送一个PINGREQ包[MQTT-3.1.2-20]。
如果服务器在CONNACK数据包上返回一个服务器Keep Alive,则客户端必须使用该值,而不是它作为Keep Alive发送的值[MQTT-3.1.2-21]。
不管保持连接的值是多少,客户端任何时候都可以发送PINGREQ报文,并且使用PINGRESP报文判断网络和服务端的活动状态。
如果Keep Alive值为非零,并且服务器在1.5倍的Keep Alive时间段内没有收到来自客户端的MQTT控制包,那么它必须关闭与客户端的网络连接,就像网络失败一样[MQTT-3.1.2-22]。
如果客户端在发送PINGREQ后的合理时间内没有收到PINGRESP数据包,它应该关闭与服务器的网络连接。
Keep Alive值为0的效果是关闭Keep Alive机制。如果Keep Alive值为0,则客户端没有义务按照任何特定的时间表发送MQTT控制包。注意:不管保持连接的值是多少,任何时候,只要服务端认为客户端是不活跃或无响应的,可以断开客户端的连接。
非规范评注:
- 服务器可能有其他原因要断开客户端的连接,例如因为它正在关闭。设置Keep Alive并不能保证客户端会保持连接。
- Keep Alive的实际值是由应用程序决定的,通常是几分钟。最大值为65,535,即18小时12分15秒。
3.1.2.5、CONNECT 属性 CONNECT Preperties
* Property Length (属性长度):
CONNECT 数据包可变报头中属性的长度,以可变字节整数的形式编码。
* Session Expiry Interval (会话到期时间间隔):
字节值为 17 (0x11) 为会话到期间隔的标识符。后面是代表会话过期间隔的四字节整数,单位为秒。多次包含会话到期间隔是一个协议错误。如果没有会话到期间隔,则使用0值。
如果它被设置为0或不存在,那么会话在网络连接关闭时结束。
如果会话到期间隔是0xFFFFFFFF(UINT_MAX),则会话不会过期。
如果会话到期时间间隔大于0,则客户端和服务器必须在网络连接关闭后存储会话状态[MQTT-3.1.2-23]。
非规范评注:
- 客户端或服务器中的时钟可能在部分时间间隔内没有运行,例如因为客户端或服务器没有运行。这可能导致状态的删除被延迟。
- 当会话过期时,客户端和服务器不需要原子化地处理状态的删除。
非规范评注:
- 将Clean Start设置为1和Session Expiry Interval为0,相当于在MQTT规范版本3.1.1中将CleanSession设置为1。将Clean Start设置为0并且没有Session Expiry Interval,相当于在MQTT规范版本3.1.1中将CleanSession设置为0。
- 一个只想在连接时处理消息的客户端将把Clean Start设置为1,并把Session Expiry Interval设置为0。它将不会收到在它连接之前发布的应用消息,并且在每次连接时必须重新订阅它感兴趣的任何主题。
- 一个客户端可能使用一个提供间歇性连接的网络连接到一个服务器。这个客户端可以使用一个较短的会话失效间隔,这样它就可以在网络再次可用时重新连接,并继续进行可靠的消息传递。如果客户端不重新连接,让会话过期,那么应用消息就会丢失。
- 当客户端以较长的会话到期时间或根本没有会话到期时间进行连接时,它要求服务器在其断开连接后长时间保持其MQTT会话状态。如果客户打算在以后的某个时间点重新连接到服务器,那么他们只应该用长的会话有效期来连接。当客户端确定它对会话没有进一步的用途时,它应该在会话有效期设置为0时断开连接。
- 客户端应始终使用CONNACK中的Session Present标志来确定服务器是否有该客户端的Session状态。
- 客户端可以避免实现自己的Session expiry,而是依靠从服务器返回的Session Present标志来确定Session是否已经过期。如果客户机确实实现了它自己的会话过期,那么它需要将会话状态被删除的时间存储为其会话状态的一部分。
* Receive Maximum (接收最大值):
字节值为 33 (0x21) 为接收最大值的标识符。后面是代表接收最大值的两个字节的整数。多次包含接收最大值或其值为0,是一个协议错误。
客户端使用这个值来限制它愿意同时处理的QoS 1和QoS 2发布的数量。没有任何机制可以限制服务器可能试图发送的QoS 0发布。
接收最大值的值只适用于当前的网络连接。如果没有接收最大值,则其值默认为65,535。
* Maximum Packet Size (最大数据包大小):
字节值为 39 (0x27) 为最大数据包尺寸的标识符。后面是一个四字节的整数,代表客户愿意接受的最大数据包尺寸。如果最大数据包大小不存在,除了协议中的限制外,没有对数据包大小施加任何限制,因为剩余长度编码和协议头大小的结果。多次包含最大数据包大小,或将该值设置为零,是一种协议错误。
非规范评注:如果应用程序选择限制最大数据包大小,则有责任选择一个合适的最大数据包大小值。
数据包大小是指MQTT控制数据包中的总字节数,定义见第2.1.4节。客户端使用最大数据包大小来通知服务器,它将不处理超过该限制的数据包。服务器不得向客户端发送超过最大包大小的数据包[MQTT-3.1.2-24]。如果客户端收到的数据包的大小超过了这个限制,这就是一个协议错误,客户端会使用原因代码为0x95(数据包过大)的DISCONNECT,如4.13节所述。当数据包过大而无法发送时,服务器必须在不发送的情况下将其丢弃,然后像完成发送该应用消息一样行事[MQTT-3.1.2-25]。
在共享订阅的情况下,如果消息太大,无法发送给一个或多个客户端,但其他客户端可以接收它,那么服务器可以选择丢弃该消息而不将其发送给任何客户端,或者将该消息发送给可以接收它的某个客户端。
非规范评注:当一个数据包被丢弃而没有被发送时,服务器可以将被丢弃的数据包放在一个 "死信队列 "中或执行其他的诊断动作。这种动作不在本规范的范围内。
* Topic Alias Maximum (主题别名最大值):
字节值为 34 (0x22) 为主题别名最大值的标识符。后面是代表主题别名最大值的两个字节的整数。多次包含主题别名最大值是一种协议错误。如果没有主题别名最大值属性,默认值是0。此值指示了客户端将接受的作为服务器发送的主题别名的最高值。客户端使用此值来限制它愿意在此连接上持有的主题别名的数量。服务器不得在 PUBLISH 包中向客户端发送大于 Topic Alias Maximum 的 Topic Alias [MQTT-3.1.2-26]。值为0表示客户端在此连接上不接受任何主题别名。如果 Topic Alias Maximum 不存在或为零,则服务器必须不向客户端发送任何 Topic Aliases [MQTT-3.1.2-27]。
* Request Response Information (请求响应信息):
字节值为 25 (0x19) 为请求响应信息的标识符。后面是一个字节,其值为0或1。包含请求响应信息一次以上,或其值不是0或1,都是协议错误。如果没有请求响应信息,则使用0的值。客户端使用该值来请求服务器在CONNACK中返回响应信息。值为0表示服务器必须不返回响应信息[MQTT-3.1.2-28]。如果该值为1,则服务器可以在CONNACK数据包中返回响应信息。
非规范评注:服务器可以选择不在CONNACK中包括响应信息,即使客户要求这样做。
* Request Problem Information (请求问题信息):
字节值为23 (0x17) 为请求问题信息的标识符。后面是一个字节,其值为0或1。包含请求问题信息一次以上,或其值不是0或1,都是协议错误。如果没有请求问题信息,则使用1的值。客户端使用这个值来指示在失败的情况下是否发送原因字符串或用户属性。
如果请求问题信息的值为0,则服务器可以在CONNACK或DISCONNECT数据包上返回一个原因字符串或用户属性,但必须不在PUBLISH、CONNACK或DISCONNECT以外的任何数据包上发送原因字符串或用户属性[MQTT-3.1.2-29]。如果该值为0,并且客户端在PUBLISH、CONNACK或DISCONNECT以外的数据包中收到一个原因字符串或用户属性,它将使用一个带有原因代码0x82(协议错误)的DISCONNECT数据包,如第4.13节处理错误所述。
如果这个值是1,服务器可以在任何允许的数据包上返回一个原因字符串或用户属性。
* User Property (用户属性):
字节值为 38 (0x26) 为用户属性的标识符。后面是一个UTF-8字符串对。用户属性允许多次出现,以代表多个名称、值对。同一名称允许出现多次。
非规范评注:CONNECT数据包上的用户属性可用于将连接相关的属性从客户端发送到服务器。这些属性的含义没有被本规范所定义。
* Authentication Method (认证方法):
字节21 (0x15) 为认证方法的标识符。后面是一个UTF-8编码的字符串,包含用于扩展认证的认证方法名称。包含认证方法超过一次是一个协议错误。如果没有认证方法,则不执行扩展认证。请参考第4.12节。如果客户端在CONNECT中设置了认证方法,那么在收到CONNACK数据包之前,客户端必须不发送除AUTH或DISCONNECT数据包以外的任何数据包[MQTT-3.1.2-30]。
* Authentication Data (认证数据):
字节值为 22 (0x16) 为认证数据的标识符。后面是包含认证数据的二进制数据。如果没有认证方法,包括认证数据是一个协议错误。包含认证数据超过一次是协议错误。该数据的内容由认证方法定义。关于扩展认证的更多信息,请参考4.12节。
3.1.2.6、可变报头非规范示例 Variable Header non-normative example
字节 | 描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
协议名称 Protocol Name | |||||||||
byte 1 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 2 | Length LSB (4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
byte 3 | 'M' | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
byte 4 | 'Q' | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
byte 5 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
byte 6 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
协议版本/协议级别 Protocol Version/Protocol Level | |||||||||
byte 7 | Version (5) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
连接标志 Connect Flags | |||||||||
byte 8 |
User Name Flag (1) Password Flag (1) Will Retain (0) Will QoS (01) Will Flag (1) Clean Start (1) Reserved (0) |
1 | 1 | 0 | 0 | 1 | 1 | 1 | 0 |
保持连接 Keep Alive | |||||||||
byte 9 | Keep Alive MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 10 | Keep Alive LSB (10) | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
属性 Properties | |||||||||
byte 11 | Length (5) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
byte 12 | Session Expiry Interval identifier | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
byte 13 | Session Expiry Interval (10) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 14 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 16 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3.1.3、CONNECT 有效载荷 CONNECT Payload
CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。这些字段(如果存在的话),必须按这个顺序出现:客户端标识符,遗嘱属性,遗嘱主题,遗嘱有效载荷,用户名,密码 [MQTT-3.1.3-1]。
3.1.3.1、客户端标识符(ClientID) Client Identifier
客户端标识符(ClientID)用于识别服务器的客户端。每个连接到服务器的客户都有一个唯一的客户标识。客户端和服务器必须使用ClientID来识别它们所持有的与客户和服务器之间的这个MQTT会话有关的状态[MQTT-3.1.3-2]。有关会话状态的更多信息,请参考第4.1节。
ClientID必须存在,并且是CONNECT数据包有效载荷中的第一个字段 [MQTT-3.1.3-3]。
ClientID必须是第1.5.4节[MQTT-3.1.3-4]中定义的UTF-8编码的字符串。
服务器必须允许长度在1到23个UTF-8编码的字节之间的客户端ID,并且只包含以下字符:"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" [MQTT-3.1.3-5] 。
服务器可能允许包含超过23个编码字节的客户端ID。服务器可能允许客户端ID包含不包括在上述列表中的字符。
服务器可能允许客户端提供一个长度为0字节的客户端ID,但是如果它这样做,服务器必须将其视为一种特殊情况,并为该客户端分配一个唯一的客户端ID [mqtt-3.1.3-6]。然后,它必须处理CONNECT数据包,就像客户提供了那个唯一的ClientID一样,并且必须在CONNACK数据包中返回分配的客户标识符[MQTT-3.1.3-7]。
如果服务器拒绝了ClientID,它可能会用一个CONNACK响应的CONNECT报文,并使用Reason Code 0x85(无效的客户端标识符),然后它必须关闭网络连接 [MQTT-3.1.3-8]。
非规范评注:客户端实现可以提供一个方便的方法来生成一个随机的ClientID。使用该方法的客户端应注意避免创建长期存在的无主会话。
3.1.3.2、遗嘱属性 Will Properties
如果 Will 标志被设置为 1,Will Properties 是 Payload 的下一个字段。Will Properties 字段定义了发布 Will Message 时要与之一起发送的应用信息属性,以及定义何时发布 Will Message 的属性。Will Properties由属性长度和属性组成。
* 属性长度(Property Length):
Will Properties中属性的长度,以可变字节整数的形式编码。
* 遗嘱延迟间隔(Will Delay Interval):
字节值为 24 (0x18) 为遗嘱延迟间隔的标识符。后面是四个字节的整数,代表遗嘱延迟间隔,单位是秒。多次包含Will Delay Interval是一个协议错误。如果没有遗嘱延迟间隔,默认值为0,即遗嘱信息发布前没有延迟。
服务器会延迟发布客户的遗嘱信息,直到遗嘱延迟间隔过去或会话结束,以先发生者为准。如果在Will Delay Interval过去之前有一个新的网络连接到这个Session,则服务器不得发送Will 消息 [MQTT-3.1.3-9]。
非规范评注:
- 如果存在临时网络断开并且客户端在遗嘱消息被发布之前成功重新连接并继续其会话,则此功能的一种用途是避免发布遗嘱消息。
- 如果网络连接使用到服务器的现有网络连接的客户端标识符,则发送现有连接的 Will Message,除非新连接指定 Clean Start 为 0 并且 Will Delay 大于零。如果 Will Delay 为 0,则 Will Message 将在现有网络连接结束时发送,如果 Clean Start 为 1,则 Will Message 将在 Session 结束时发送。
* 有效载荷格式指示符 Payload Format Indicator:
字节值为1 (0x01) 为有效载荷格式指示符的标识符。后跟有效载荷格式指示符的值,可以是:
- 字节值为0 (0x00) 表示Will Message为未指定字节,相当于不发送Payload Format Indicator。
- 字节值为1 (0x01) 表示遗嘱消息是 UTF-8 编码字符数据。有效负载中的 UTF-8 数据必须是格式良好的 UTF-8,如 Unicode 规范 [Unicode] 所定义,并在 RFC 3629 [RFC3629] 中重述。
多次包含有效载荷格式指示符是一个协议错误。服务器可以验证 Will Message 是否具有指定的格式,如果不是,则发送一个 CONNACK,原因代码为 0x99(有效负载格式无效),如第 4.13 节所述。
* 消息过期间隔 Message Expiry Interval:
字节值为2 (0x02) 为消息过期间隔的标识符。后跟表示消息到期间隔的四字节整数。多次包含消息到期间隔是一个协议错误。
如果存在,则四字节值是遗嘱消息的生命周期(以秒为单位),并在服务器发布遗嘱消息时作为发布到期间隔发送。
如果不存在,则服务器发布遗嘱消息时不发送消息过期间隔。
* 内容类型 Content Type:
字节值为3 (0x03) 为内容类型的标识符。后跟 UTF-8 编码字符串,描述遗嘱消息的内容。多次包含内容类型属于协议错误。 Content Type 的值由发送和接收应用程序定义。
* 响应主题 Response Topic:
字节值为8 (0x08) 为响应主题的标识符。后跟 UTF-8 编码字符串,用作响应消息的主题名称。多次包含响应主题是协议错误。响应主题的存在将遗嘱消息标识为请求。
* 关联数据 Correlation Data:
字节值为9 (0x09) 为关联数据的标识符。后跟二进制数据。请求消息的发送方使用关联数据来识别响应消息在收到时是针对哪个请求的。多次包含关联数据属于协议错误。如果关联数据不存在,则请求者不需要任何关联数据。关联数据的值只对请求消息的发送者和响应消息的接收者有意义。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟一个 UTF-8 字符串对。允许用户属性出现多次以表示多个名称、值对。允许同一个名字出现多次。服务器必须在发布遗嘱消息时维护用户属性的顺序 [MQTT-3.1.3-10]。
非规范评注:此属性旨在提供一种传输应用层名称-值标记的方法,这些标记的含义和解释只有负责发送和接收它们的应用程序才知道。
3.1.3.3、遗嘱主题 Will Topic
如果 Will Flag 设置为 1,则 Will Topic 是 Payload 中的下一个字段。遗嘱主题必须是 UTF-8 编码字符串,如第 1.5.4 节 [MQTT-3.1.3-11] 中所定义。
3.1.3.4、遗嘱有效载荷 Will Payload
如果 Will Flag 设置为 1,则 Will Payload 是 Payload 中的下一个字段。遗嘱有效负载定义了要发布到遗嘱主题的应用程序消息有效负载,如第 3.1.2.5 节所述。该字段由二进制数据组成。
3.1.3.5、用户名 User Name
如果用户名标志设置为 1,则用户名是有效负载中的下一个字段。用户名必须是 UTF-8 编码字符串,如第 1.5.4 节 [MQTT-3.1.3-12] 中所定义。服务器可以使用它进行身份验证和授权。
3.1.3.6、密码 Password
如果密码标志设置为 1,则密码是有效负载中的下一个字段。密码字段是二进制数据。这个字段虽然叫做Password,但是可以用来携带任何凭证信息。
3.1.4、CONNECT 动作 CONNECT Actions
请注意,服务器可以在同一 TCP 端口或其他网络端点上支持多种协议(包括其他版本的 MQTT 协议)。如果服务器确定协议是 MQTT v5.0,则它会按如下方式验证连接尝试:
- 如果服务器在建立网络连接后的合理时间内没有收到 CONNECT 数据包,则服务器应该关闭网络连接。
- 服务器必须验证 CONNECT 数据包是否与第 3.1 节中描述的格式匹配,如果不匹配 [MQTT-3.1.4-1],则关闭网络连接。在关闭网络连接之前,服务器可以发送一个 CONNACK,其原因代码为 0x80 或更大,如第 4.13 节所述。
- 服务器可以检查连接数据包的内容是否满足任何进一步的限制,并应执行身份验证和授权检查。如果这些检查中的任何一个失败,则必须关闭网络连接[MQTT-3.1.4-2]。在关闭网络连接之前,它可能会以第3.2节和第4.13节中所述为0x80或更大的原因代码发送适当的Connack响应。
如果验证成功,服务器将执行以下步骤:
- 如果clientID表示已经连接到服务器的客户端,则服务器将脱连接数据包发送到现有客户端,其理由代码为0x8e(会话已接管),如第4.13节所述,并且必须关闭现有客户端的网络连接[ MQTT-3.1.4-3]。如果现有客户有一条遗嘱消息,则将按照第3.1.2.5节中的描述发布消息。
- 服务器必须执行第3.1.2.4节[MQTT-3.1.4-4]中描述的Clean Start处理。
- 服务器必须使用包含0x00(成功)原因代码[MQTT-3.1.4-5]的连接数据包确认连接数据包。
- 开始消息传递并保持监视。
非规范评注:
- 如果将现有网络连接的间隔延迟为0,并且有一条遗嘱消息,则将由于网络连接关闭而发送。如果现有网络连接的会话间隔为0,或者新的网络连接的干净启动设置为1,则如果现有网络连接具有遗嘱消息,则将发送其发送,因为原始会话在收购上结束。
- 建议使用服务器处理任何形式的业务关键数据,建议执行身份验证和授权检查。如果这些检查成功,则服务器通过以0x00(成功)代码发送Connack来做出响应。如果他们失败了,建议服务器根本不发送Connack,因为这可能会提醒潜在的攻击者对MQTT服务器的存在,并鼓励此类攻击者启动拒绝服务或密码确定攻击。
发送连接数据包后,允许客户端立即发送更多MQTT控制数据包;客户无需等待Connack数据包从服务器到达。如果服务器拒绝连接,则不得处理客户在连接数据包之后发送的任何数据,除了auth数据包[MQTT-3.1.4-6]。
非规范评注:
- 客户通常会等待Connack数据包,但是,如果客户端利用其在收到Connack之前发送MQTT控制数据包的自由,它可能会简化客户的实现,因为它不必监管连接的状态。客户端接受在服务器拒绝连接之前将不会处理从服务器接收Connack数据包之前发送的任何数据。
- 非规范性评论 在收到 CONNACK 之前发送 MQTT 控制数据包的客户端将不知道服务器约束以及是否正在使用任何现有会话。
- 如果客户端在身份验证完成之前发送过多数据,服务器可以限制从网络连接读取或关闭网络连接。建议将此作为避免拒绝服务攻击的一种方式。
3.2、CONNACK - 连接确认 CONNACK - acknowledgement
CONNACK 数据包是服务器响应从客户端接收到的 CONNECT 数据包而发送的数据包。在发送除 AUTH [MQTT-3.2.0-1] 之外的任何数据包之前,服务器必须发送带有 0x00(成功)原因代码的 CONNACK。服务器不得在网络连接中发送多个 CONNACK [MQTT-3.2.0-2]。
如果客户端在合理的时间内没有收到来自服务器的 CONNACK 数据包,客户端应该关闭网络连接。 “合理”的时间量取决于应用程序的类型和通信基础设施。
3.2.1、CONNACK 固定报头 CONNACK Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT报文类型 (2) | 保留位 Reserved | ||||||
0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | |
byte 2... | 剩余长度 Remaining Length |
剩余长度字段:这是编码为可变字节整数的可变报头的长度。
3.2.2、CONNACK 可变报头 CONNACK Variable Header
CONNACK 数据包的可变报头按顺序包含以下字段:连接确认标志、连接原因码和属性。编码属性的规则在 2.2.2 节中描述。
3.2.2.1、连接确认标志 Connect Acknowledge Flags
字节值为 1 (0x01) 是“连接确认标志”。第 7-1 位是保留位,必须设置为 0 [MQTT-3.2.2-1],第0位 是会话存在标志。
* 会话存在 Session Present
位置:连接确认标志的位 0。Session Present 标志通知客户端服务器是否正在使用来自此 ClientID 的先前连接的会话状态。这允许客户端和服务器具有一致的会话状态视图。
如果服务器接受一个 Clean Start 设置为1的连接,除了在 CONNACK 报文中设置 0x00(成功)原因码之外,服务器还必须在 CONNACK 数据包中将 session present 设置为 0 [MQTT-3.2.2-2]。
如果服务器接受 Clean Start 设置为0的连接并且服务器具有 ClientID 的会话状态,则它必须在CONNACK数据包中将 Session Present 设置为1,否则它必须在CONNACK数据包中将 Session Present 设置为0.在这两种情况下,它都必须在CONNACK数据包中设置一个 0x00(成功)原因码 [MQTT-3.2.2-3]。
如果Client从Server收到的Session Present值不符合预期,则Client进行如下处理:
- 如果客户端没有会话状态并且接收到会话存在设置为 1,则它必须关闭网络连接 [MQTT-3.2.2-4]。如果它希望使用新会话重新启动,客户端可以使用设置为 1 的 Clean Start 重新连接。
- 如果客户端确实有会话状态并且接收到会话存在设置为 0,则它必须在继续网络连接时丢弃其会话状态 [MQTT-3.2.2-5]。
如果服务器发送包含非零原因码的CONNACK报文,它必须将Session Present 设置为0 [MQTT-3.2.2-6]。
3.2.2.2、连接原因码 Connect Reason Code
字节值为 2 (0x02) 是可变报头中的连接原因码。连接原因码的值如下所示。如果服务器接收到格式正确的 CONNECT 数据包,但服务器无法完成连接,则服务器可以发送包含来自该表的适当连接原因码的 CONNACK 数据包。如果服务器发送包含 128 或更大的原因码的 CONNACK 数据包,则它必须关闭网络连接 [MQTT-3.2.2-7]。
值(十进制) | 值(十六进制) | 原因码名称 | 描述 |
0 | 0x00 | 成功 | 连接被接受 |
128 | 0x80 | 未指定的错误 | 服务器不希望透露失败的原因,或者其他原因码都不使用 |
129 | 0x81 | 格式错误的数据包 | 无法正确解析 CONNECT 报文中的数据 |
130 | 0x82 | 协议错误 | CONNECT 报文中的数据不符合此规范 |
131 | 0x83 | 具体实施错误 | CONNECT 有效但未被此服务器接受 |
132 | 0x84 | 不支持的协议版本 | 服务器不支持客户端请求的MQTT协议版本 |
133 | 0x85 | 客户端标识符无效 | 客户端标识符是有效的字符串,但服务器不允许 |
134 | 0x86 | 错误的用户名或密码 | 服务器不接受客户端指定的用户名或密码 |
135 | 0x87 | 未经授权 | 客户端无权连接 |
136 | 0x88 | 服务器无法使用 | MQTT服务器不可用 |
137 | 0x89 | 服务器繁忙 | 服务器正忙,稍后再试 |
138 | 0x8A | 禁止 | 该客户端已经被administrative action禁止,联系服务器管理员 |
140 | 0x8C | 错误的身份验证方法 | 验证方法不受支持或与当前使用的验证方法不匹配 |
144 | 0x90 | 主题名称无效 | 遗嘱主题名称没有格式错误,但未被此服务器接受 |
149 | 0x95 | 数据包太大 | CONNECT报文超过了最大允许大小 |
151 | 0x97 | 超出配额 | 已超出实施或管理强加的限制 |
153 | 0x99 | 负载格式无效 | 遗嘱有效载荷与指定的有效载荷格式指示符不匹配 |
154 | 0x9A | 不支持保留 | 服务器不支持保留消息,Will Retain 设置为1 |
155 | 0x9B | 不支持QoS | 服务器不支持Will QoS中设置的QoS |
156 | 0x9C | 使用另一台服务器 | 客户端应暂时使用另一台服务器 |
157 | 0x9D | 服务器被移动 | 客户端永久使用另一台服务器 |
159 | 0x9F | 超出连接速率 | 已超过连接速率限制 |
发送 CONNACK 数据包的服务器必须使用连接原因码值之一 T-3.2.2-8]。
非规范评注:
- 原因码 0x80(未指定错误)可用于服务器知道失败原因但不希望将其透露给客户端的情况,或者其他原因码值都不适用的情况。
- 如果在 CONNECT 上发现错误,服务器可以选择关闭网络连接而不发送 CONNACK 以增强安全性。例如,当在公共网络上并且连接未被授权时,指示这是一个 MQTT 服务器可能是不明智的。
3.2.2.3、CONNACK 属性 CONNACK Properties
* 属性长度 Property Length:
这是编码为可变字节整数的 CONNACK 数据包可变标头中属性的长度。
* 会话到期间隔 Session Expiry Interval:
字节值为17 (0x11)为会话到期间隔的标识符。后跟代表以秒为单位的会话到期间隔的四字节整数。多次包含会话到期间隔是一个协议错误。
如果会话到期间隔不存在,则使用 CONNECT 数据包中的值。服务器使用此属性通知客户端它使用的值不是客户端在 CONNACK 中发送的值。请参阅第 3.1.2.11.*节了解会话到期间隔的使用说明。
* 接收最大值 Receive Maximum:
字节值为33 (0x21)为接收最大值的标识符。后跟表示接收最大值的两字节整数。多次包含 Receive Maximum 值或它的值为 0 是协议错误。服务器使用此值来限制它愿意为客户端同时处理的 QoS 1 和 QoS 2 发布的数量。它不提供一种机制来限制客户端可能尝试发送的 QoS 0 发布。
如果接收最大值不存在,则其值默认为 65,535。
* 最大QoS Maximum QoS:
字节值为36 (0x24)为最大 QoS 的标识符。后跟值为 0 或 1 的字节。多次包含最大 QoS 或具有 0 或 1 以外的值是协议错误。如果不存在最大 QoS,则客户端使用最大 QoS 2。
如果服务器不支持 QoS 1 或 QoS 2 PUBLISH 数据包,它必须在 CONNACK 数据包中发送最大 QoS,指定它支持的最高 QoS [MQTT-3.2.2-9]。不支持 QoS 1 或 QoS 2 PUBLISH 数据包的服务器必须仍然接受包含请求 QoS 0、1 或 2 [MQTT-3.2.2-10] 的 SUBSCRIBE 数据包。
如果客户端从服务器接收到最大QoS,它不得发送QoS级别超过指定的最大QoS级别的PUBLISH报文 [MQTT-3.2.2-11]。如果服务器接收到 QoS 大于它指定的最大 QoS 的 PUBLISH 数据包,则这是一个协议错误。在这种情况下,使用带有原因码 0x9B(不支持的 QoS)的 DISCONNECT,如第 4.13 节处理错误中所述。
如果服务器接收到包含超出其能力的Will QoS的CONNECT报文,它必须拒绝连接。它应该使用带有原因码0x9B的CONNACK报文(不支持的QoS),并且必须关闭网络连接。
非规范评注:客户端不需要支持QoS 1或QoS 2发布数据包。如果是这种情况,则客户端仅将其发送给可以支持的值的任何订阅命令中的最大QoS字段限制。
* 保留可用 Retain Available:
字节值为37 (0x25) 为保留可用的标识符。后跟一个字节字段。如果存在,该字节声明服务器是否支持保留消息。值 0 表示不支持保留的消息。值 1 表示支持保留的消息。如果不存在,则支持保留消息。多次包含 Retain Available 或使用 0 或 1 以外的值是协议错误。
如果一个服务器收到一个 CONNECT 数据包,其中包含 Will Retain 设置为 1 的 Will Message,并且它不支持保留消息,则服务器必须拒绝连接请求。它应该发送带有原因代码 0x9A(不支持保留)的 CONNACK,然后它必须关闭网络连接 [MQTT-3.2.2-13]。
从服务器接收 Retain Available 的客户端不得发送 RETAIN 标志设置为 1 的 PUBLISH 数据包 [MQTT-3.2.2-14]。如果服务器收到这样的数据包,这是一个协议错误。服务器应该发送一个 DISCONNECT,原因代码为 0x9A(不支持保留),如 4.13 节所述。
* 最大报文大小 Maximum Packet Size:
字节值为39 (0x27) 为最大报文大小的标识符。后跟一个四字节整数,表示服务器愿意接受的最大报文大小。如果不存在最大报文大小,则由于剩余长度编码和协议标头大小,对超出协议限制的报文大小没有限制。
将最大报文大小多次包含或将值设置为0是一个协议错误。
报文大小是MQTT控制报文中字节的总数,如第2.1.4节所示。服务器使用最大报文大小来通知客户端,它将不会处理大小超过此限制的报文。
客户端不得向服务器发送超过最大报文大小的报文[MQTT-3.2.2-15]。如果服务器收到大小超过此限制的报文,则这是协议错误,服务器使用带有原因码为0x95(报文太大)的DISCONNECT报文,如第4.13节所述。
* 分配的客户端标识符 Assigned Client Idetifier:
字节值为18 (0x12) 为分配的客户端标识符的标识符。后跟 UTF-8 字符串,它是分配的客户端标识符。多次包含分配的客户端标识符是一个协议错误。
服务器分配的客户端标识符,因为在 CONNECT 数据包中发现长度为零的客户端标识符。
如果客户端使用零长度的客户端标识符进行连接,则服务器必须使用包含分配的客户端标识符的 CONNACK 进行响应。分配的客户端标识符必须是一个新的客户端标识符,不被当前服务器中的任何其他会话使用 [MQTT-3.2.2-16]。
* 主题别名最大值 Topic Alias Maximum:
字节值为34 (0x22)为该主题的标识符最大值。其次是代表主题别名最大值的两个字节整数。这是一个协议错误,将主题别名最大值多次多次包含。如果主题别名最大属性不存在,则默认值为0。
此值表示服务器将接受为客户端发送的主题别名的最高值。服务器使用此值来限制其愿意在此连接上保留的主题别名的数量。客户端不得将发布数据包中的主题别名发送给服务器的主题大于此值[MQTT-3.2.2-17]。值为0表示服务器在此连接上不接受任何主题别名。如果主题别名最大值或0,则客户端不得将任何主题别名发送到服务器[MQTT-3.2.2-18]。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的字节标识符。后跟表示与此响应关联的原因的 UTF-8 编码字符串。此原因字符串是为诊断而设计的人类可读字符串,不应由客户端解析。
服务器使用此值向客户端提供附加信息。如果服务器会增加 CONNACK 数据包的大小超过客户端指定的最大数据包大小,则服务器不得发送此属性 [MQTT-3.2.2-19]。多次包含原因字符串是协议错误。
非规范评注:在客户端中正确使用原因字符串包括在客户端代码抛出的异常中使用此信息,或将此字符串写入日志。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性标识符。后跟一个 UTF-8 字符串对。此属性可用于向客户端提供附加信息,包括诊断信息。如果服务器会增加 CONNACK 数据包的大小超过客户端 [MQTT-3.2.2-20] 指定的最大数据包大小,则服务器不得发送此属性。允许用户属性出现多次以表示多个名称、值对。允许同一个名字出现多次。
本规范未定义此属性的内容和含义。包含此属性的 CONNACK 的接收者可以忽略它。
* 通配符订阅可用 Wildcard Subscription Available:
字节值为40 (0x28) 为可用的通配符订阅标识符。后跟一个字节字段。如果存在,该字节声明服务器是否支持通配符订阅。值为 0 表示不支持通配符订阅。值为 1 表示支持通配符订阅。如果不存在,则支持通配符订阅。多次包含可用通配符订阅或发送 0 或 1 以外的值是协议错误。
如果服务器不支持通配符订阅并且它接收到包含通配符订阅的 SUBSCRIBE 数据包,则这是一个协议错误。服务器使用 DISCONNECT,原因码为 0xA2(不支持通配符订阅),如第 4.13 节所述。
如果服务器支持通配符订阅,它仍然可以拒绝包含通配符订阅的特定订阅请求。在这种情况下,服务器可以发送一个带有原因码 0xA2(不支持通配符订阅)的 SUBACK 控制包。
* 订阅标识符可用 Subscription Identifiers Available:
字节值为41 (0x29) 为可用订阅标识符的标识符。后跟一个字节字段。如果存在,则该字节声明服务器是否支持订阅标识符。值为0表示不支持订阅标识符。 1的值表示支持订阅标识符。如果不存在,则支持订阅标识符。这是一个协议错误,包括多次可用的订阅标识符,或发送0或1以外的其他值。
如果服务器收到包含订阅标识符的订阅数据包,并且不支持订阅标识符,则这是协议错误。该服务器使用第4.13节中所述的0xA1(不支持订阅标识符)的理由代码使用断开连接。
* 共享订阅可用 Shared Subscription Available:
字节值为42 (0x2A) 为共享订阅标识符可用。后跟一个字节字段。如果存在,该字节声明服务器是否支持共享订阅。值为 0 表示不支持共享订阅。值为 1 表示支持共享订阅。如果不存在,则支持共享订阅。多次包含可用的共享订阅或发送 0 或 1 以外的值是协议错误。
如果服务器收到包含共享订阅的 SUBSCRIBE 数据包并且它不支持共享订阅,则这是一个协议错误。服务器使用 DISCONNECT,原因代码为 0x9E(不支持共享订阅),如第 4.13 节所述。
* 服务器保持连接 Server Keep Alive:
字节值为19 (0x13) 为服务器保活标识符。后跟一个两字节整数,其中包含服务器分配的保持活动时间。如果服务器在 CONNACK 数据包上发送服务器保持活动,客户端必须使用此值而不是客户端在 CONNECT [MQTT-3.2.2-21] 上发送的保持活动值。如果服务器不发送服务器保活,服务器必须使用客户端在 CONNECT [MQTT-3.2.2-22] 上设置的保活值。多次包含服务器保持活动是一个协议错误。
非规范评注:Server Keep Alive 的主要用途是让 Server 通知 Client 它将比 Client 指定的 Keep Alive 更快地断开因不活动而导致的 Client。
* 响应信息 Response Information:
后跟 UTF-8 编码字符串,用作创建响应主题的基础。本规范未定义客户端从响应信息创建响应主题的方式。多次包含响应信息是协议错误。如果客户端发送值为 1 的请求响应信息,则服务器在 CONNACK 中发送响应信息是可选的。
非规范评注:它的一个常见用途是传递主题树的全局唯一部分,该部分至少在其会话的生命周期内为该客户端保留。这通常不能只是一个随机名称,因为请求客户端和响应客户端都需要获得授权才能使用它。通常将其用作特定客户端的主题树的根。对于返回此信息的服务器,通常需要正确配置。使用此机制允许此配置在服务器中完成一次,而不是在每个客户端中完成。
* 服务器引用 Server Reference:
字节值为28 (0x1C) 为服务器引用的标识符。后面跟着一个 UTF-8 编码的字符串,客户端可以使用它来识别要使用的另一个服务器。多次包含服务器引用是一个协议错误。
服务器在 CONNACK 或 DISCONNECT 数据包中使用服务器引用,原因代码为 0x9C(使用另一台服务器)或原因代码 0x9D(服务器已移动),如第 4.13 节所述。
* 认证方式 Authentication Method:
字节值为21 (0x15) 为身份验证方法的标识符。后跟包含身份验证方法名称的 UTF-8 编码字符串。多次包含身份验证方法是协议错误。有关扩展身份验证的更多信息,请参阅第 4.12 节。
* 身份验证数据 Authentication Data:
字节值为22 (0x16) 为身份验证数据的标识符。后跟包含身份验证数据的二进制数据。此数据的内容由身份验证方法和已交换的身份验证数据的状态定义。多次包含身份验证数据属于协议错误。有关扩展身份验证的更多信息,请参阅第 4.12 节。
3.2.3、CONNACK 有效载荷 CONNACK Payload
CONNACK 数据包没有有效载荷。
3.3、PUBLISH-发布消息 PUBLISH-Publish message
PUBLISH 数据包从客户端发送到服务器或从服务器发送到客户端以传输应用程序消息。
3.3.1、PUBLISH 固定报头 PUBLISH Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (3) | DUP flag | QoS level | RETAIN | ||||
0 | 0 | 1 | 1 | X | X | X | X | |
byte 2... | 剩余长度 Remaining Length |
3.3.1.1、DUP
位置:字节 1,位 3。
如果 DUP 标志设置为 0,则表明这是客户端或服务器第一次尝试发送此 PUBLISH 数据包。如果 DUP 标志设置为 1,则表明这可能是先前尝试发送数据包的重新交付。
当客户端或服务器尝试重新传送 PUBLISH 数据包时,DUP 标志必须设置为 1 [MQTT-3.3.1-1]。对于所有 QoS 0 消息 [MQTT-3.3.1-2],DUP 标志必须设置为 0。
当服务器将 PUBLISH 数据包发送给订阅者时,来自传入 PUBLISH 数据包的 DUP 标志的值不会传播。传出 PUBLISH 数据包中的 DUP 标志独立于传入 PUBLISH 数据包设置,其值必须由传出 PUBLISH 数据包是否重传单独确定 [MQTT-3.3.1-3]。
非规范评注:
- 包含设置为 1 的 DUP 标志的 MQTT 控制数据包的接收者不能假定它已经看到了该数据包的早期副本。
- 重要的是要注意 DUP 标志是指 MQTT 控制数据包本身,而不是它包含的应用程序消息。使用 QoS 1 时,客户端可能会收到 DUP 标志设置为 0 的 PUBLISH 数据包,其中包含重复的应用消息,但具有不同的数据包标识符。 2.2.1 节提供了有关数据包标识符的更多信息。
3.3.1.2、服务质量 QoS
位置:字节 1,位 2-1。
该字段指示应用消息传递的保证级别。 QoS 级别如下所示。
QoS 的值 | Bit 2 | Bit 1 | 描述 |
0 | 0 | 0 | 至多一次交付 |
1 | 0 | 1 | 至少一次交付 |
2 | 1 | 0 | 恰好一次交付 |
- | 1 | 1 | 保留(不得使用) |
如果服务器在其对客户端的 CONNACK 响应中包含最大 QoS,并且它接收到 QoS 大于此的 PUBLISH 数据包,则它使用带有原因代码 0x9B(不支持 QoS)的 DISCONNECT,如第 4.13 节处理错误中所述。
一个 PUBLISH 数据包不得将两个 QoS 位设置为 1 [MQTT-3.3.1-4]。如果服务器或客户端收到两个 QoS 位都设置为 1 的 PUBLISH 数据包,则它是格式错误的数据包。如第 4.13 节所述,将 DISCONNECT 与原因代码 0x81(格式错误的数据包)一起使用。
3.3.1.3、保留位 Retain
位置:字节 1,位 0。
如果客户端发送给服务器的 PUBLISH 数据包中的 RETAIN 标志设置为 1,则服务器必须替换该主题的任何现有保留消息并存储应用程序消息 [MQTT-3.3.1-5],以便它可以交付给其订阅与其主题名称匹配的未来订阅者。如果有效载荷包含零字节,服务器将正常处理它,但必须删除任何具有相同主题名称的保留消息,并且该主题的任何未来订阅者都不会收到保留消息 [MQTT-3.3.1-6]。有效载荷包含零字节的保留消息不得作为保留消息存储在服务器 [MQTT-3.3.1-7] 上。
如果客户端发送给服务器的 PUBLISH 数据包中的 RETAIN 标志为 0,则服务器不得将该消息存储为保留消息,并且不得删除或替换任何现有的保留消息 [MQTT-3.3.1-8]。
如果服务器在其对客户端的 CONNACK 响应中包含 Retain Available,其值设置为 0,并且它接收到 RETAIN 标志设置为 1 的 PUBLISH 数据包,则它使用 DISCONNECT 原因代码 0x9A(不支持 Retain),如所述在第 4.13 节中。
进行新的非共享订阅时,将按照“保留处理订阅选项”的指示,将每个匹配主题名称上最后保留的消息(如果有)发送给客户端。这些消息在RETAIN标志设置为1的情况下发送。发送哪些保留的消息由 Retain Handling Subscription Option 控制。订阅时:
- 如果 Retain Handling 设置为 0,则服务器必须将与订阅主题过滤器匹配的保留消息发送给客户端 [MQTT-3.3.1-9]。
- 如果 Retain Handling 设置为 1,则如果订阅尚不存在,则服务器必须将与订阅的主题过滤器匹配的所有保留消息发送给客户端,如果订阅确实存在,则服务器不得发送保留消息。 [MQTT-3.3.1-10]。
- 如果保留处理设置为 2,服务器不得发送保留消息 [MQTT-3.3.1-11]。
如果服务器收到一个 RETAIN 标志设置为 1 和 QoS 0 的 PUBLISH 数据包,它应该存储新的 QoS 0 消息作为该主题的新保留消息,但可以选择随时丢弃它。如果发生这种情况,该主题将不会保留消息。
如果某个主题的当前保留消息过期,则将其丢弃,并且该主题将没有保留消息。
服务器从已建立的连接转发的应用消息中的 RETAIN 标志的设置由 Retain As Published 订阅选项控制。有关订阅选项的定义,请参阅第 3.8.3.1 节。
- 如果 Retain As Published 订阅选项的值设置为 0,则在转发应用程序消息时,服务器必须将 RETAIN 标志设置为 0,无论在接收到的 PUBLISH 数据包中如何设置 RETAIN 标志 [MQTT-3.3.1-12 ].
- 如果 Retain As Published 订阅选项的值设置为 1,则服务器必须将 RETAIN 标志设置为等于接收到的 PUBLISH 数据包 [MQTT-3.3.1-13] 中的 RETAIN 标志。
非规范评注:保留消息在发布者不定期发送状态消息时很有用。新的非共享订阅者将收到最新状态。
3.3.1.4、剩余长度 Remaining Length
这是可变报头的长度加上有效载荷的长度,编码为可变字节整数。
3.3.2、PUBLISH 可变报头 PUBLISH Variable Header
PUBLISH 数据包的可变报头按顺序包含以下字段:主题名称(Topic Name)、数据包标识符(Packet Identifier)和属性(Properties)。编码属性的规则在 2.2.2 节中描述。
3.3.2.1、主题名称 Topic Name
主题名称标识有效负载数据发布到的信息通道。
主题名称必须作为 PUBLISH 数据包变量标头中的第一个字段出现。它必须是第 1.5.4 节 [MQTT-3.3.2-1] 中定义的 UTF-8 编码字符串。
PUBLISH 数据包中的主题名称不得包含通配符 [MQTT-3.3.2-2]。
根据第 4.7 节 [MQTT-3.3.2-3] 中定义的匹配过程,服务器发送给订阅客户端的 PUBLISH 数据包中的主题名称必须匹配订阅的主题过滤器。但是,由于允许服务器将主题名称映射到另一个名称,它可能与原始 PUBLISH 数据包中的主题名称不同。
为了减小 PUBLISH 数据包的大小,发送方可以使用主题别名。主题别名在 3.3.2.3.4 节中描述。如果主题名称的长度为零并且没有主题别名,则为协议错误。
3.3.2.2、数据包标识符 Packet Identifier
数据包标识符字段仅出现在 QoS 级别为 1 或 2 的 PUBLISH 数据包中。第 2.2.1 节提供了有关数据包标识符的更多信息。
3.3.2.3、PUBLISH 属性 PUBLISH Properties
* 属性长度 Property Length:
PUBLISH 数据包变量标头中属性的长度编码为可变字节整数。
* 有效载荷格式指示符 Payload Format Indicator:
字节值为1 (0x01) 为有效载荷格式指示符的标识符。后跟 Payload Format Indicator 的值,可以是:
- 字节值为0 (0x00) 表示Payload为未指定字节,相当于不发送Payload Format Indicator。
- 字节值为1 (0x01) 表示负载是 UTF-8 编码字符数据。有效负载中的 UTF-8 数据必须是格式良好的 UTF-8,如 Unicode 规范 [Unicode] 所定义,并在 RFC 3629 [RFC3629] 中重述。
服务器必须向所有接收应用消息的订阅者发送不变的有效载荷格式指示符 [MQTT-3.3.2-4]。接收方可以验证有效载荷是否具有指定的格式,如果不是,则发送 PUBACK、PUBREC 或 DISCONNECT,原因代码为 0x99(有效载荷格式无效),如第 4.13 节所述。
* 消息过期间隔 Message Expiry Interval:
字节值为2 (0x02) 为消息过期间隔的标识符。后跟表示消息到期间隔的四字节整数。
如果存在,四字节值是应用消息的生命周期(以秒为单位)。如果消息过期间隔已经过去并且服务器没有设法开始向匹配的订阅者继续传递,那么它必须删除该订阅者的消息副本[MQTT-3.3.2-5]。
如果不存在,则应用消息不会过期。
服务器发送给客户端的 PUBLISH 数据包必须包含一个消息过期间隔,该间隔设置为接收到的值减去应用消息在服务器中等待的时间 [MQTT-3.3.2-6]。有关存储状态的详细信息和限制,请参阅第 4.1 节。
* 主题别名 Topic Alias:
字节值为35 (0x23) 为主题别名的标识符。后跟表示主题别名值的两字节整数。多次包含主题别名值是一个协议错误。
主题别名是一个整数值,用于标识主题而不是使用主题名称。这减少了 PUBLISH 数据包的大小,并且当主题名称很长并且在网络连接中重复使用相同的主题名称时很有用。
发件人决定是否使用主题别名并选择值。它通过在 PUBLISH 数据包中包含一个非零长度的主题名称和一个主题别名来设置一个主题别名映射。接收方正常处理 PUBLISH,但还将指定的主题别名映射设置为该主题名称。
如果在接收方设置了主题别名映射,发送方可以发送包含该主题别名和零长度主题名称的 PUBLISH 数据包。接收者然后将传入的 PUBLISH 视为包含主题别名的主题名称。
发送者可以通过在同一网络连接中发送另一个具有相同主题别名值和不同的非零长度主题名称的 PUBLISH 来修改主题别名映射。
主题别名映射仅存在于网络连接中,并且仅在该网络连接的生命周期内有效。接收者不得将任何主题别名映射从一个网络连接转发到另一个 [MQTT-3.3.2-7]。
不允许主题别名为 0。发送方不得发送包含值为 0 的主题别名的 PUBLISH 数据包 [MQTT-3.3.2-8]。
客户端不得发送主题别名大于服务器在 CONNACK 数据包 [MQTT-3.3.2-9] 中返回的主题别名最大值的 PUBLISH 数据包。客户端必须接受所有大于 0 且小于或等于它在 CONNECT 数据包 [MQTT-3.3.2-10] 中发送的主题别名最大值的主题别名值。
服务器不得发送主题别名大于客户端在 CONNECT 数据包 [MQTT-3.3.2-11] 中发送的主题别名最大值的 PUBLISH 数据包。服务器必须接受所有大于 0 且小于或等于它在 CONNACK 数据包 [MQTT-3.3.2-12] 中返回的主题别名最大值的主题别名值。
客户端和服务器使用的主题别名映射相互独立。因此,当客户端向服务器发送包含主题别名值为 1 的 PUBLISH,而服务器向该客户端发送主题别名值为 1 的 PUBLISH 时,它们通常指的是不同的主题。
* 响应主题 Response Topic:
字节值为8 (0x08) 为响应主题的标识符。后跟 UTF-8 编码字符串,用作响应消息的主题名称。响应主题必须是 UTF-8 编码字符串,如第 1.5.4 节 [MQTT-3.3.2-13] 中所定义。响应主题不得包含通配符 [MQTT-3.3.2-14]。多次包含响应主题是协议错误。响应主题的存在将消息标识为请求。
服务器必须将未更改的响应主题发送给所有接收应用消息的订阅者 [MQTT-3.3.2-15]。
非规范评注:带有响应主题的应用消息的接收者通过使用响应主题作为 PUBLISH 的主题名称来发送响应。如果请求消息包含关联数据,则请求消息的接收方还应将此关联数据作为属性包含在响应消息的 PUBLISH 数据包中。
* 关联数据 Correlation Data:
字节值为9 (0x09) 为关联数据标识符。后跟二进制数据。请求消息的发送方使用关联数据来识别响应消息在收到时是针对哪个请求的。多次包含关联数据属于协议错误。如果关联数据不存在,则请求者不需要任何关联数据。
服务器必须将关联数据原样发送给所有接收应用消息的订阅者 [MQTT-3.3.2-16]。关联数据的值只对请求消息的发送者和响应消息的接收者有意义。
非规范评注:
- 包含响应主题和相关数据的应用消息的接收者通过使用响应主题作为 PUBLISH 的主题名称来发送响应。作为响应发布的一部分,客户还应发送未更改的关联数据。
- 如果关联数据包含的信息如果被响应请求的客户修改可能会导致应用程序失败,则应对其进行加密和/或散列处理以允许检测到任何更改。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟一个 UTF-8 字符串对。允许用户属性出现多次以表示多个名称、值对。允许同一个名字出现多次。
在将应用消息转发给客户端时,服务器必须在 PUBLISH 数据包中发送所有未更改的用户属性 [MQTT-3.3.2-17]。服务器在转发应用消息时必须维护用户属性的顺序 [MQTT-3.3.2-18]。
非规范评注:此属性旨在提供一种传输应用层名称-值标记的方法,这些标记的含义和解释只有负责发送和接收它们的应用程序才知道。
* 订阅标识符 Subscription Identifier:
字节值为11 (0x0B) 为订阅标识符的标识符。后跟表示订阅标识符的可变字节整数。
订阅标识符的值介于 1 到 268,435,455 之间。如果订阅标识符的值为 0,则为协议错误。如果发布是与多个订阅匹配的结果,则将包括多个订阅标识符,在这种情况下,它们的顺序并不重要。
* 内容类型 Content Type:
字节值为3 (0x03) 为内容类型的标识符。后跟 UTF-8 编码字符串,描述应用程序消息的内容。内容类型必须是 UTF-8 编码字符串,如第 1.5.4 节 [MQTT-3.3.2-19] 中所定义。多次包含内容类型属于协议错误。 Content Type 的值由发送和接收应用程序定义。
服务器必须将内容类型不变地发送给所有接收应用消息的订阅者 [MQTT-3.3.2-20]。
非规范评注:
- UTF-8 编码字符串可以使用 MIME 内容类型字符串来描述应用程序消息的内容。但是,由于发送和接收应用程序负责字符串的定义和解释,因此 MQTT 不执行字符串验证,除非确保它是有效的 UTF-8 编码字符串。
- 下图显示了一个 PUBLISH 数据包的示例,其中主题名称设置为“a/b”,数据包标识符设置为 10,并且没有任何属性。
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
主题名称 Topic Name | |||||||||
byte 1 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 2 | Length LSB (3)0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
byte 3 | 'a' (0x61) | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
byte 4 | '/' (0x2F) | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
byte 5 | 'b' (0x62) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
数据包标识符 Packet Identifier | |||||||||
byte 6 | 数据包标识符 MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 7 | 数据包标识符 LSB (10) | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
属性长度 Property Length | |||||||||
byte 8 | 无属性 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3.3.3、PUBLISH 有效载荷 PUBLISH Payload
有效负载包含正在发布的应用程序消息。数据的内容和格式是特定于应用程序的。 Payload 的长度可以通过从 Fixed Header 中的 Remaining Length 字段中减去 Variable Header 的长度来计算。包含零长度有效负载的 PUBLISH 数据包是有效的。
3.3.4、PUBLISH 操作 PUBLISH Actions
PUBLISH 数据包的接收者必须根据 PUBLISH 数据包 [MQTT-3.3.4-1] 中的 QoS 确定的数据包进行响应。
QoS 等级 | 预期回应 |
QoS 0 | 无 |
QoS 1 | PUBACK 数据包 |
QoS 2 | PUBREC 数据包 |
客户端使用 PUBLISH 数据包向服务器发送应用消息,以分发给具有匹配订阅的客户端。
服务器使用 PUBLISH 数据包向每个具有匹配订阅的客户端发送应用消息。 PUBLISH 数据包包含 SUBSCRIBE 数据包中携带的订阅标识符(如果有的话)。
当客户端使用包含通配符的主题过滤器进行订阅时,客户端的订阅可能会重叠,因此发布的消息可能会匹配多个过滤器。在这种情况下,服务器必须按照所有匹配订阅的最大 QoS [MQTT-3.3.4-2] 将消息传递给客户端。此外,服务器可以提供更多的消息副本,每个副本对应一个额外的匹配订阅,并且在每种情况下都尊重订阅的 QoS。
如果客户端收到 QoS 大于最大 QoS 的未经请求的应用程序消息(不是由订阅产生),它将使用原因代码为 0x9B(不支持 QoS)的 DISCONNECT 数据包,如第 4.13 节处理错误中所述。
如果客户端为任何重叠订阅指定了订阅标识符,则服务器必须在作为订阅结果发布的消息中发送这些订阅标识符 [MQTT-3.3.4-3]。如果服务器发送消息的单个副本,它必须在 PUBLISH 数据包中包含所有具有订阅标识符的匹配订阅的订阅标识符,它们的顺序不重要 [MQTT-3.3.4-4]。如果服务器发送多个 PUBLISH 数据包,它必须在每个数据包中发送匹配订阅的订阅标识符(如果它具有订阅标识符 [MQTT-3.3.4-5])。
客户可能进行了多个与发布匹配的订阅,并且它对其中多个订阅使用了相同的标识符。在这种情况下,PUBLISH 数据包将携带多个相同的订阅标识符。
PUBLISH 数据包包含任何订阅标识符,而不是在 SUBSCRIBE 数据包中接收到的导致它流动的订阅标识符,这是一个协议错误。从客户端发送到服务器的 PUBLISH 数据包不得包含订阅标识符 [MQTT-3.3.4-6]。
如果订阅是共享的,则只有来自正在接收消息的客户端的 SUBSCRIBE 数据包中存在的订阅标识符才会在 PUBLISH 数据包中返回。
接收方在收到 PUBLISH 数据包时的操作取决于 QoS 级别,如第 4.3 节所述。
如果 PUBLISH 数据包中包含 Topic Alias,则接收方对其进行如下处理:
- 主题别名值为 0 或大于最大主题别名是协议错误,接收方使用 DISCONNECT,原因代码为 0x94(主题别名无效),如第 4.13 节所述。
- 如果接收者已经为主题别名建立了映射,那么:① 如果数据包的主题名称长度为零,则接收方使用与主题别名相对应的主题名称对其进行处理;② 如果数据包包含非零长度的主题名称,接收方使用该主题名称处理数据包,并更新其主题别名到传入数据包主题名称的映射。
- 如果接收者还没有这个主题别名的映射:① 如果数据包的主题名称字段长度为零,则它是协议错误,并且接收方使用 DISCONNECT,原因代码为 0x82(协议错误),如第 4.13 节所述。② 如果数据包包含长度非零的主题名称,则接收方使用该主题名称处理数据包,并将其主题别名的映射设置为来自传入数据包的主题名称。
非规范评注:如果服务器向不支持本规范提供的属性或其他特性的不同协议级别(例如 MQTT V3.1.1)的客户端分发应用消息,则应用消息中的某些信息可能会丢失,并且依赖于此信息的应用程序可能无法正常工作。
客户端不得发送超过 Receive Maximum QoS 1 和 QoS 2 的 PUBLISH 数据包,因为它没有从服务器接收到 PUBACK、PUBCOMP 或 PUBREC,原因代码为 128 或更大 [MQTT-3.3.4-7]。如果它接收到超过 Receive Maximum QoS 1 和 QoS 2 PUBLISH 数据包,而它没有发送 PUBACK 或 PUBCOMP 作为响应,服务器将使用原因代码为 0x93(超出接收最大值)的 DISCONNECT 数据包,如第 4.13 节处理错误中所述。有关流量控制的更多信息,请参阅第 4.9 节。
客户端不得延迟发送除 PUBLISH 数据包之外的任何数据包,因为已发送 Receive Maximum PUBLISH 数据包但未收到对它们的确认 [MQTT-3.3.4-8]。 Receive Maximum 的值仅适用于当前网络连接。
非规范评注:
- 客户端可能会选择在没有收到确认的情况下向服务器发送少于 Receive Maximum 的消息,即使它可以发送的消息多于此数量。
- Client 在暂停发送 QoS 1 和 QoS 2 PUBLISH 数据包时,可能会选择暂停发送 QoS 0 PUBLISH 数据包。
- 如果客户端在收到 CONNACK 数据包之前发送 QoS 1 或 QoS 2 PUBLISH 数据包,则它有被断开连接的风险,因为它发送的数量超过了 Receive Maximum 发布。
服务器不得发送超过 Receive Maximum QoS 1 和 QoS 2 的 PUBLISH 数据包,因为它没有从客户端接收到 PUBACK、PUBCOMP 或 PUBREC,原因代码为 128 或更大 [MQTT-3.3.4-9]。如果它接收到超过 Receive Maximum QoS 1 和 QoS 2 PUBLISH 数据包,而它没有发送 PUBACK 或 PUBCOMP 作为响应,客户端使用原因代码为 0x93(超出接收最大值)的 DISCONNECT,如第 4.13 节处理错误中所述。有关流量控制的更多信息,请参阅第 4.9 节。
服务器不得延迟发送除 PUBLISH 数据包以外的任何数据包,因为已发送 Receive Maximum PUBLISH 数据包但未收到对它们的确认 [MQTT-3.3.4-10]。
非规范评注:
- 服务器可能会选择在没有收到确认的情况下向客户端发送少于 Receive Maximum 的消息,即使它可以发送的消息多于此数量。
- Server在暂停发送QoS 1和QoS 2 PUBLISH报文时,可能会选择暂停发送QoS 0 PUBLISH报文。
3.4、PUBACK-发布确认 PUBACK-Publish acknowledgement
PUBACK 数据包是对具有 QoS 1 的 PUBLISH 数据包的响应。
3.4.1、PUBACK 固定报头 PUBACK Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT报文类型 (4) | 保留位 Reserved | ||||||
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 2... | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
剩余长度字段:这是可变标头的长度,编码为可变字节整数。
3.4.2、PUBACK 可变报头 PUBACK Variable Header
PUBACK 数据包的可变报头按顺序包含以下字段:来自正在确认的 PUBLISH 数据包的数据包标识符、PUBACK 原因码、属性长度和属性。编码属性的规则在 2.2.2 节中描述。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | 报文标识符 MSB | |||||||
byte 2 | 报文标识符 LSB | |||||||
byte 3 | PUBACK 原因码(PUBACK Reason Code) | |||||||
byte 4 | 属性长度 (Property Length) |
3.4.2.1、PUBACK 原因码 PUBACK Reason Code
可变报头中的字节3是PUBACK原因码。如果剩余长度为 2,则没有原因码并使用值 0x00(成功)。
值(十进制) | 值(十六进制) | 原因码名称 | 描述 |
0 | 0x00 | 成功 | 消息被接收。QoS 1消息的发布将继续 |
16 | 0x10 | 没有匹配的订阅者 | 该消息被接受,但没有订阅者。仅由服务器发送。如果服务器知道没有匹配的订阅者,则可以使用此原因码而不是0x00(成功)。 |
128 | 0x80 | 未指定的错误 | 接收者不接受发布,而是不想揭示原因,或者它不匹配其他值之一。 |
131 | 0x83 | 具体实施错误 | 发布是有效的,但接收者不愿意接受。 |
135 | 0x87 | 未经授权 | 该发布未经授权。 |
144 | 0x90 | 主题名称无效 | 主题名称没有格式错误,但该客户端或服务器不接受。 |
145 | 0x91 | 使用中的数据包标识符 | 数据包标识符已经在使用中。这可能表明客户端和服务器之间的会话状态不匹配。 |
151 | 0x97 | 超出配额 | 超出了实施或 administrative imposed limit。 |
153 | 0x99 | 有效载荷格式无效 | 有效负载格式与指定的有效负载格式指示符不匹配。 |
发送 PUBACK 数据包的客户端或服务器必须使用 PUBACK 原因码之一[MQTT-3.4.2-1]。如果原因码为0x00(成功)并且没有属性,则可以省略原因码和属性长度。在这种情况下,PUBACK 的剩余长度为2。
3.4.2.2、PUBACK Properties PUBACK 属性
* 属性长度 Property Length:
PUBACK数据包可变报头中属性的长度编码为可变字节整数。如果剩余长度小于4,则没有属性长度,并使用0的值。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。UTF-8编码的字符串,代表与这个响应相关的原因。该原因字符串是一个人为可读的字符串,旨在用于诊断,不打算由接收方解析。
发送方使用这个值向接收方提供额外信息。如果该属性会使 PUBACK 数据包的大小超过接收方指定的最大数据包大小[MQTT-3.4.2-2],则发送方不得发送该属性。多次包含 "原因字符串 "是一种协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟 UTF-8 字符串对。此属性可用于提供额外的诊断或其他信息。如果该属性会使 PUBACK 数据包的大小超过接收方指定的最大数据包大小[MQTT-3.4.2-3],则发送方不得发送该属性。用户属性允许多次出现以代表多个名称、值对。同一名称允许出现多次。
3.4.3、PUBACK 有效载荷 PUBACK Payload
PUBACK数据包没有有效载荷。
3.4.4、PUBACK 动作 PUBACK Actions
这将在第4.3.2节中描述。
3.5、PUBREC-发布接收(QoS 2 交付 第1步) PUBREC-Publish received(QoS 2 delivery part1)
PUBREC数据包是对带有QoS 2的PUBLISH数据包的响应,它是QoS 2协议交换的第二个数据包。
3.5.1、PUBREC 固定报头 PUBREC Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (5) | 保留位 Reserved | ||||||
0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
剩余长度字段:即可变报头的长度,被编码为一个可变字节整数。
3.5.2、PUBREC 可变报头 PUBREC Variable Header
PUBREC报文的可变报头由以下字段组成,顺序为:被确认的PUBLISH数据包的数据包标识符、PUBREC原因码和属性。属性的编码规则在2.2.2节中描述。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | 报文标识符 MSB | |||||||
byte 2 | 报文标识符 LSB | |||||||
byte 3 | PUBREC 原因码 | |||||||
byte 4 | 属性长度 Property Length |
3.5.2.1、PUBREC 原因码 PUBREC Reason Code
可变报头的字节3是PUBREC的原因码。如果剩余长度为2,那么发布的原因码的值为0x00(成功)。
值(十进制) | 值(十六进制) | 原因码名称 | 描述 |
0 | 0x00 | 成功 | 该消息被接受,QoS 2消息的发布继续进行 |
16 | 0x10 | 没有匹配的订阅者 | 消息被接受,但没有订阅者。这仅由服务器发送。如果服务器知道没有匹配的用户,它可以使用这个原因码而不是0x00(成功)。 |
128 | 0x80 | 未指定的错误 | 接收者不接受发布,但不想透露原因,或不符合其他数值之一。 |
131 | 0x83 | 具体实施错误 | PUBLISH是有效的,但接收者不愿意接受。 |
135 | 0x87 | 未授权 | PUBLISH未被授权。 |
144 | 0x90 | 主题名称无效 | 主题名称没有格式错误,但不被这个客户端或服务器接受。 |
145 | 0x91 | 数据包标识符被使用 | 数据包标识符已经在使用中。这可能表示客户端和服务器之间的会话状态不匹配。 |
151 | 0x97 | 以超过配额 | 已超过实施或管理上的限制。 |
153 | 0x99 | 有效载荷格式无效 | 有效载荷格式与有效载荷格式指示符中指定的格式不一致。 |
发送 PUBREC 数据包的客户或服务器必须使用 PUBREC 原因码值之一。[mqtt-3.5.2-1]. 如果原因码是0x00(成功),并且没有属性,那么原因码和属性长度可以被省略。在这种情况下,PUBREC的剩余长度为2。
3.5.2.2、PUBREC 属性 PUBREC Properties
* 属性长度 Property Length:
PUBREC 数据包可变报头中属性的长度,以可变字节整数的形式编码。如果剩余长度小于4,就没有属性长度,使用0的值。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。后跟UTF-8编码的字符串,代表与这个响应相关的原因。这个原因字符串是人类可读的,用于诊断,不应该被接收者解析。
发送方使用此值来向接收方提供额外的信息。如果该属性会使PUBREC数据包的大小超过接收方指定的最大数据包大小[MQTT-3.5.2-2],则发送方不得发送该属性。多次包含 "原因字符串 "是一种协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟UTF-8字符串对。此属性可用于提供额外的诊断或其他信息。如果该属性会使PUBREC数据包的大小超过接收方指定的最大数据包大小[MQTT-3.5.2-3],则发送方不得发送该属性。用户属性允许多次出现以代表多个名称、值对。同一名称允许出现多次。
3.5.3、PUBREC 有效载荷 PUBREC Payload
PUBREC数据包没有有效载荷。
3.5.4、PUBREC 动作 PUBREC Actions
这将在第4.3.3节中描述。
3.6、PUBREL-发布释放(QoS 2 交付 第2步) PUBREL-Publish release (QoS 2 delivery part 2)
3.6.1、PUBREL 固定报头 PUBREL Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (6) | 保留位 Reserved | ||||||
1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | |
byte 2 | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
PUBREL数据包中固定报头的第3、2、1和0位是保留的,必须分别设置为0、0、1和0。服务器必须将任何其他值视为格式错误,并关闭网络连接[MQTT-3.6.1-1]。
剩余长度字段:即可变报头的长度,被编码为一个可变字节整数。
3.6.2、PUBREL 可变报头 PUBREL Variable Header
PUBREL包的可变报头包含以下字段,顺序是:来自被确认的PUBREC包的包标识符,PUBREL原因码,和属性。属性的编码规则在第2.2.2节描述。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | 报文标识符 MSB | |||||||
byte 2 | 报文标识符 LSB | |||||||
byte 3 | PUBREL 原因码 | |||||||
byte 4 | 属性长度 Property Length |
3.6.2.1、PUBREL 原因码 PUBREL Reason Code
可变报头中的第3字节是PUBREL原因码。如果剩余长度为2,则使用0x00的值(成功)。
值(十进制) | 值(十六进制) | 原因码名称 | 描述 |
0 | 0x00 | 成功 | 信息被释放。 |
146 | 0x92 | 未发现数据包标识符 | 数据包标识符未知。这在恢复期间不是一个错误,但在其他时候表示客户端和服务器上的会话状态不匹配。 |
发送PUBREL数据包的客户端或服务器必须使用PUBREL原因码值之一[MQTT-3.6.2-1]。如果原因码是0x00(成功),并且没有属性,那么原因码和属性长度可以省略。在这种情况下,PUBREL的剩余长度为2。
3.6.2.2、PUBREL 属性 PUBREL Properties
* 属性长度 Property Length:
PUBREL数据包可变报头中属性的长度,编码为可变字节整数。如果剩余长度小于4,就没有属性长度,使用0的值。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。后跟UTF-8编码的字符串,代表与该响应相关的原因。这个原因字符串是人类可读的,是为诊断而设计的,不应该被接收者解析。
发送方使用这个值向接收方提供额外的信息。如果该属性会使PUBREL数据包的大小超过接收方指定的最大数据包大小[MQTT-3.6.2-2],则发送方不得发送该属性。多次包含原因字符串是一种协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟 UTF-8 字符串对。此属性可用于为 PUBREL 提供额外的诊断或其他信息。如果发送方会增加 PUBREL 数据包的大小超过接收方指定的最大数据包大小,则发送方不得发送此属性 [MQTT-3.6.2-3]。允许用户属性出现多次以表示多个名称、值对。允许同一个名字出现多次。
3.6.3、PUBREL 有效载荷 PUBREL Payload
PUBREL 数据包没有有效载荷。
3.6.4、PUBREL 操作 PUBREL Actions
这在第 4.3.3 节中有描述。
3.7、PUBCOMP-发布完成(QoS 2 交付 第3步) PUBCOMP-Publish complete(QoS 2 delivery part 3)
PUBCOMP 数据包是对 PUBREL 数据包的响应。它是 QoS 2 协议交换的第四个也是最后一个数据包。
3.7.1、PUBCOMP 固定报头 PUBCOMP Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (7) | 保留位 Reserved | ||||||
0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
剩余长度字段:即可变报头的长度,被编码为可变字节整数。
3.7.2、PUBCOMP 可变报头 PUBCOMP Variable Header
PUBCOMP 数据包的可变报头按顺序包含以下字段:来自正在确认的 PUBREL 数据包的数据包标识符、PUBCOMP 原因码和属性。编码属性的规则在 2.2.2 节中描述。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | 报文标识符 MSB | |||||||
byte 2 | 报文标识符 LSB | |||||||
byte 3 | PUBCOMP 原因码 | |||||||
byte4 | 属性长度 Property Length |
3.7.2.1、PUBCOMP 原因码 PUBCOMP Reason Code
可变报头中的字节3是PUBCOMP的原因码。如果剩余长度为 2,则使用值 0x00(成功)。
值(十进制) | 值(十六进制) | 原因码名称 | 描述 |
0 | 0x00 | 成功 | 数据包标识符已发布。 QoS 2 消息的发布已完成。 |
146 | 0x92 | 未找到数据包标识符 | 数据包标识符未知。这不是恢复期间的错误,但在其他时候表示客户端和服务器上的会话状态不匹配。 |
发送 PUBCOMP 数据包的客户端或服务器必须使用 PUBCOMP 原因代码值之一 [MQTT-3.7.2-1]。如果原因代码为 0x00(成功)并且没有属性,则可以省略原因代码和属性长度。在这种情况下,PUBCOMP 的剩余长度为 2。
3.7.2.2、PUBCOMP 属性 PUBCOMP Properties
* 属性长度 Property Length:
PUBCOMP 数据包可变报头中属性的长度编码为可变字节整数。如果剩余长度小于 4,则没有属性长度并使用值 0。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。后跟表示与此响应关联的原因的 UTF-8 编码字符串。此原因字符串是为诊断而设计的人类可读字符串,不应由接收方解析。
发送方使用此值向接收方提供附加信息。如果发送方会增加 PUBCOMP 数据包的大小超过接收方指定的最大数据包大小,则发送方不得发送此属性 [MQTT-3.7.2-2]。多次包含原因字符串是协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟 UTF-8 字符串对。此属性可用于提供额外的诊断或其他信息。如果发送方会增加 PUBCOMP 数据包的大小超过接收方指定的最大数据包大小,则发送方不得发送此属性 [MQTT-3.7.2-3]。允许用户属性出现多次以表示多个名称、值对。允许同一个名字出现多次。
3.7.3、PUBCOMP 有效载荷 PUBCOMP Payload
PUBCOMP 数据包没有有效载荷。
3.7.4、PUBCOMP 操作 PUBCOMP Actions
这在第 4.3.3 节中有描述。
3.8、SUBSCRIBE-订阅请求 SUBSCRIBE request
SUBSCRIBE 数据包从客户端发送到服务器以创建一个或多个订阅。每个订阅都记录了客户对一个或多个主题的兴趣。服务器向客户端发送 PUBLISH 数据包,以转发已发布到与这些订阅匹配的主题的应用程序消息。 SUBSCRIBE 数据包还指定(对于每个订阅)服务器可以向客户端发送应用消息的最大 QoS。
3.8.1、SUBSCRIBE 固定报头 SUBSCRIBE Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (8) | 保留位 Reserved | ||||||
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length |
SUBSCRIBE 数据包的可变报头的第 3、2、1 和 0 位是保留的,必须分别设置为 0、0、1 和 0。服务器必须将任何其他值视为格式错误并关闭网络连接 [MQTT-3.8.1-1]。
剩余长度字段:即可变报头的长度加上有效载荷的长度,被编码为可变字节整数。
3.8.2、SUBSCRIBE 可变报头 SUBSCRIBE Variable Header
SUBSCRIBE 数据包的可变报头按顺序包含以下字段:数据包标识符和属性。 2.2.1 节提供了有关数据包标识符的更多信息。编码属性的规则在 2.2.2 节中描述。
非规范评注:下图显示了一个 SUBSCRIBE 可变报头的示例,其数据包标识符为 10 且没有属性。
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
报文标识符 | |||||||||
byte 1 | 报文标识符 MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 2 | 报文标识符 LSB (10) | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
byte 3 | 属性长度 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3.8.2.1、SUBSCRIBE 属性 SUBSCRIBE Properties
* 属性长度 Property Length:
SUBSCRIBE 数据包可变报头中属性的长度编码为可变字节整数。
* 订阅标识符 Subscription Identifier:
字节值为11 (0x0B) 为订阅标识符的标识符。后跟表示订阅标识符的可变字节整数。订阅标识符的值介于 1 到 268,435,455 之间。如果订阅标识符的值为 0,则为协议错误。多次包含订阅标识符为协议错误。
订阅标识符与作为此 SUBSCRIBE 数据包的结果创建或修改的任何订阅相关联。如果有订阅标识符,则它与订阅一起存储。如果未指定此属性,则订阅标识符的缺失将与订阅一起存储。
有关处理订阅标识符的更多信息,请参阅第 3.8.3.1 节。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟一个 UTF-8 字符串对。
允许用户属性出现多次以表示多个名称、值对。允许同一个名字出现多次。
非规范评注:SUBSCRIBE 数据包上的用户属性可用于将订阅相关属性从客户端发送到服务器。本规范未定义这些属性的含义。
3.8.3、SUBSCRIBE 有效载荷 SUBSCRIBE Payload
SUBSCRIBE 数据包的有效载荷包含一个主题过滤器列表,指示客户端想要订阅的主题。主题过滤器必须是 UTF-8 编码的字符串 [MQTT-3.8.3-1]。每个主题过滤器后跟一个订阅选项字节。
有效载荷必须包含至少一对主题过滤器和订阅选项 [MQTT-3.8.3-2]。没有有效负载的 SUBSCRIBE 数据包是协议错误。有关处理错误的信息,请参阅第 4.13 节。
3.8.3.1、订阅选项 Subscription Options
订阅选项的位 0 和 1 表示最大 QoS 字段。这给出了服务器可以向客户端发送应用程序消息的最大 QoS 级别。如果最大 QoS 字段的值为 3,则为协议错误。
订阅选项的第 2 位表示 No Local 选项。如果该值为 1,应用程序消息不得转发到 ClientID 等于发布连接 [MQTT-3.8.3-3] 的 ClientID 的连接。在共享订阅 [MQTT-3.8.3-4] 上将 No Local 位设置为 1 是一个协议错误。
订阅选项的第 3 位代表 Retain As Published 选项。如果为 1,则使用此订阅转发的应用程序消息会保留它们发布时使用的 RETAIN 标志。如果为 0,则使用此订阅转发的应用程序消息将 RETAIN 标志设置为 0。建立订阅时发送的保留消息将 RETAIN 标志设置为 1。
订阅选项的第 4 位和第 5 位表示保留处理选项。此选项指定在建立订阅时是否发送保留消息。这不会影响订阅后任何时候发送保留消息。如果没有与主题过滤器匹配的保留消息,则所有这些值的作用相同。这些值是:
- 0 = 在订阅时发送保留消息
- 1 = 仅当订阅当前不存在时才在订阅时发送保留消息
- 2 = 订阅时不发送保留消息
- 发送保留处理值 3 是一个协议错误。
订阅选项字节的第 6 位和第 7 位保留供将来使用。如果有效负载中的任何保留位非零 [MQTT-3.8.3-5],服务器必须将 SUBSCRIBE 数据包视为格式错误。
非规范评注:
- 当重新连接完成并且客户端不确定订阅是否在之前的会话连接中完成时,不发送现有订阅的保留消息很有用。
- 由于新订阅而不发送存储的保留消息在客户端希望接收更改通知并且不需要知道初始状态时很有用。
- 对于指示它不支持保留消息的服务器,保留为已发布和保留处理的所有有效值给出相同的结果,即不在订阅时发送任何保留消息并将所有消息的 RETAIN 标志设置为 0。
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
主题过滤器 Topic Filter | ||||||||
byte 1 | 长度 MSB | |||||||
byte 2 | 长度 LSB | |||||||
byte 3..N | 主题过滤器(Topic Filter) | |||||||
订阅选项 | ||||||||
保留位 |
保留处理 Retain Handling |
RAP | NL |
QoS 服务质量等级 |
||||
byte N+1 | 0 | 0 | X | X | X | X | X | X |
RAP 表示保留为已发布。NL 表示无本地。
非规范示例:下图显示了带有两个主题过滤器的 SUBSCRIBE 有效载荷示例。第一个是 QoS 1 的“a/b”,第二个是 QoS 2 的“c/d”。
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
主题过滤器(Topic Filter) | |||||||||
byte 1 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 2 | Length LSB (3) | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
byte 3 | ‘a’ (0x61) | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
byte 4 | ‘/’ (0x2F) | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
byte 5 | ‘b’ (0x62) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
服务质量等级(Requested QoS) | |||||||||
byte 6 | Requested QoS(1) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
主题过滤器(Topic Filter) | |||||||||
byte 7 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 8 | Length LSB (3) | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
byte 9 | ‘c’ (0x63) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
byte 10 | ‘/’ (0x2F) | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
byte 11 | ‘d’ (0x64) | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
服务质量等级(Requested QoS) | |||||||||
byte 12 | Requested QoS(2) | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
3.8.4、SUBSCRIBE 操作 SUBSCRIBE Actions
当服务器从客户端接收到一个 SUBSCRIBE 数据包时,服务器必须用一个 SUBACK 数据包进行响应 [MQTT-3.8.4-1]。 SUBACK 数据包必须具有与其确认的 SUBSCRIBE 数据包相同的数据包标识符 [MQTT-3.8.4-2]。
允许服务器在发送SUBACK包之前开始发送匹配订阅的PUBLISH包。
如果服务器接收到包含主题过滤器的 SUBSCRIBE 数据包,该主题过滤器与当前会话的非共享订阅的主题过滤器相同,则它必须用新订阅 [MQTT-3.8.4-3] 替换现有订阅。新订阅中的主题过滤器将与之前订阅中的相同,尽管其订阅选项可能不同。如果 Retain Handling 选项为 0,则必须重新发送与主题过滤器匹配的任何现有保留消息,但应用程序消息不得因替换订阅而丢失 [MQTT-3.8.4-4]。
如果服务器接收到与当前会话的任何主题过滤器都不相同的非共享主题过滤器,则会创建一个新的非共享订阅。如果 Retain Handling 选项不是 2,则所有匹配的保留消息都会发送给 Client。
如果服务器接收到与服务器上已存在的共享订阅的主题过滤器相同的主题过滤器,则将会话添加为该共享订阅的订阅者。不发送保留消息。
如果服务器接收到与任何现有共享订阅的主题过滤器不同的共享订阅主题过滤器,则会创建一个新的共享订阅。会话被添加为该共享订阅的订阅者。不发送保留消息。
有关共享订阅的更多详细信息,请参阅第 4.8 节。
如果服务器接收到包含多个主题过滤器的 SUBSCRIBE 数据包,它必须像接收到一系列多个 SUBSCRIBE 数据包一样处理该数据包,除了它将它们的响应组合成单个 SUBACK 响应 [MQTT-3.8.4-5]。
服务器发送给客户端的 SUBACK 数据包必须包含每个主题过滤器/订阅选项对的原因码 [MQTT-3.8.4-6]。这个原因码必须要么显示为该订阅授予的最大 QoS,要么指示订阅失败 [MQTT-3.8.4-7]。服务器授予的最大 QoS 可能低于订阅者请求的值。为响应订阅而发送的应用消息的 QoS 必须是原始发布消息的 QoS 和服务器授予的最大 QoS 中的最小值 [MQTT-3.8.4-8]。在原始消息发布时使用 QoS 1 且授予的最大 QoS 为 QoS 0 的情况下,允许服务器向订阅者发送消息的副本。
非规范评注:
- 如果订阅客户端已经将特定主题过滤器授予了最大 QoS 1,则与过滤器匹配的 QoS 0 应用程序消息将以 QoS 0 传递给客户端。这意味着客户端最多收到消息的一个副本。另一方面,发布到同一主题的 QoS 2 消息被服务器降级为 QoS 1 以传递给客户端,因此客户端可能会收到消息的重复副本。
- 如果订阅客户端已被授予最大 QoS 0,则最初发布为 QoS 2 的应用程序消息可能会在到客户端的跃点中丢失,但服务器永远不应发送该消息的副本。发布到同一主题的 QoS 1 消息在传输到该客户端时可能会丢失或重复。
- 订阅 QoS 2 的主题过滤器等同于说“我想接收与发布时 QoS 匹配的消息”。这意味着发布者负责确定消息可以传递的最大 QoS,但订阅者可以要求服务器将 QoS 降级到更适合其使用的水平。
订阅标识符是服务器中会话状态的一部分,并返回给接收匹配的 PUBLISH 数据包的客户端。当服务器接收到一个 UNSUBSCRIBE 数据包,当服务器从客户端接收到相同主题过滤器但具有不同订阅标识符或没有订阅标识符的订阅数据包,或者当服务器发送会话存在时,它们将从服务器的会话状态中删除0 在 CONNACK 数据包中。
订阅标识符不构成客户端中客户端会话状态的一部分。在一个有用的实现中,客户端会将订阅标识符与其他客户端状态相关联,当客户端取消订阅时,当客户端订阅具有不同标识符或没有标识符的相同主题过滤器时,或者当客户端收到会话在 CONNACK 数据包中存在 0。
服务器不需要在重传的 PUBLISH 数据包中使用同一组订阅标识符。客户端可以通过发送包含主题过滤器的订阅数据包来重新订阅,该主题过滤器与当前会话中现有订阅的主题过滤器相同。如果客户端在初始传输 PUBLISH 数据包后重新订阅并使用不同的订阅标识符,则允许服务器在任何重传中使用第一次传输的标识符。或者,允许服务器在重传期间使用新标识符。服务器在发送包含新标识符的 PUBLISH 数据包后,不允许恢复到旧标识符。
非规范评注:使用场景,用于说明订阅标识符:
- 客户端实现通过其编程接口指示发布与多个订阅匹配。每次订阅时,客户端实现都会生成一个新的标识符。如果返回的发布包含多个订阅标识符,则该发布匹配多个订阅。
- 客户端实现允许订阅者将消息定向到与订阅关联的回调。客户端实现生成一个标识符,该标识符唯一地将标识符映射到回调。当接收到发布时,它使用订阅标识符来确定驱动哪个回调。
- 客户端实现在传送已发布的消息时返回用于订阅应用程序的主题字符串。为此,客户端生成唯一标识主题过滤器的标识符。当接收到发布时,客户端实现使用标识符来查找原始主题过滤器并将它们返回给客户端应用程序。
- 网关将从服务器接收的发布转发给已订阅网关的客户端。网关实现维护它接收到的每个唯一主题过滤器到它也接收到的一组 ClientID、订阅标识符对的映射。它为转发给服务器的每个主题过滤器生成一个唯一标识符。当接收到发布时,网关使用它从服务器接收到的订阅标识符来查找与其关联的客户端标识符、订阅标识符对。它将这些添加到它发送给客户端的 PUBLISH 数据包中。如果上游服务器因为消息匹配多个订阅而发送了多个 PUBLISH 数据包,则此行为会镜像到客户端。
3.9、SUBACK-订阅确认 SUBACK-Subscribe acknowledgement
服务器向客户端发送 SUBACK 数据包以确认接收和处理 SUBSCRIBE 数据包。
一个 SUBACK 数据包包含一个原因码列表,它指定了授予的最大 QoS 级别或为 SUBSCRIBE 请求的每个订阅发现的错误。
3.9.1、SUBACK 固定报头 SUBACK Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (9) | 保留位 Reserved | ||||||
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length |
剩余长度字段:即可变报头的长度加上有效载荷的长度,编码为可变字节整数。
3.9.2、SUBACK 可变报头 SUBACK Variable Header
SUBACK 数据包的可变报头按顺序包含以下字段:来自正在确认的 SUBSCRIBE 数据包的数据包标识符和属性。
3.9.2.1、SUBACK 属性 SUBACK Property
* 属性长度 Property Length:
SUBACK报文的可变报头中属长度被编码为可变字节整数。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。后跟表示与此响应关联的原因的 UTF-8 编码字符串。此原因字符串是为诊断而设计的人类可读字符串,不应由客户端解析。
服务器使用此值向客户端提供附加信息。如果服务器会增加 SUBACK 数据包的大小超过客户端 [MQTT-3.9.2-1] 指定的最大数据包大小,则服务器不得发送此属性。多次包含原因字符串是协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性标识符。后跟 UTF-8 字符串对。此属性可用于提供额外的诊断或其他信息。如果服务器会增加 SUBACK 数据包的大小超过客户端 [MQTT-3.9.2-2] 指定的最大数据包大小,则服务器不得发送此属性。允许用户属性出现多次以表示多个名称、值对。允许同一个名字出现多次。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | 数据包标识符 MSB | |||||||
byte 2 | 数据包标识符 LSB |
3.9.3、SUBACK 有效载荷 SUBACK Payload
有效载荷包含一系列原因码。每个原因码对应于被确认的 SUBSCRIBE 数据包中的主题过滤器。 SUBACK 数据包中原因码的顺序必须与 SUBSCRIBE 数据包中主题过滤器的顺序匹配 [MQTT-3.9.3-1]。
值(十进制) | 值(十六进制) | 原因码名称 | 描述 |
0 | 0x00 | 授予 QoS 0 | 订阅被接受,发送的最大 QoS 将为 QoS 0。这可能比请求的 QoS 更低。 |
1 | 0x01 | 授予 QoS 1 | 订阅被接受,发送的最大 QoS 将为 QoS 1。这可能比请求的 QoS 更低。 |
2 | 0x02 | 授予 QoS 2 | 订阅已被接受,任何收到的 QoS 都将发送到此订阅。 |
128 | 0x80 | 未指定的错误 | 订阅未被接受并且服务器不想透露原因或其他原因代码均不适用。 |
131 | 0x83 | 具体实施错误 | SUBSCRIBE 有效但服务器不接受它。 |
135 | 0x87 | 未经授权 | 客户无权进行此订阅。 |
143 | 0x8F | 主题过滤器无效 | 主题过滤器已正确形成,但对于此客户端不允许。 |
145 | 0x91 | 在使用的数据包标识符 | 指定的数据包标识符已经在使用中。 |
151 | 0x97 | 超出配额 | 已超出实施或行政强加的限制。 |
158 | 0x9E | 不支持共享订阅 | 服务器不支持此客户端的共享订阅。 |
161 | 0xA1 | 不支持订阅标识符 | 服务器不支持订阅标识符;不接受订阅。 |
162 | 0xA2 | 不支持通配符订阅 | 服务器不支持万能订阅;不接受订阅。 |
发送 SUBACK 数据包的服务器必须为每个接收到的主题过滤器使用订阅原因代码之一 [MQTT-3.9.3-2]。
非规范评注:在相应的 SUBSCRIBE 数据包中,每个主题过滤器总是有一个原因代码。如果原因代码不是特定于主题过滤器(例如 0x91(正在使用的数据包标识符)),则为每个主题过滤器设置。
3.10、UNSUBSCRIBE-取消订阅请求 UNSUBSCRIBE-Unsubscribe request
UNSUBSCRIBE数据包由客户端发送至服务器,以取消对主题的订阅。
3.10.1、UNSUBSCRIBE 固定报头 UNSUBSCRIBE Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (10) | 保留位 Rserved | ||||||
1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | |
byte 2 | 剩余长度 Remaining Length |
UNSUBSCRIBE数据包的固定报头的第3、2、1和0位被保留,并且必须分别设置为0、0、1和0。服务器必须将任何其他值视为格式错误,并关闭网络连接[MQTT-3.10.1-1]。
剩余长度字段:即可变报头的长度(2字节)加上有效载荷的长度,被编码为一个可变字节整数。
3.10.2、UNSUBSCRIBE 可变报头 UNSUBSCRIBE Variable Header
UNSUBSCRIBE数据包的变量头包含以下字段,顺序为:数据包标识符和属性。第2.2.1节提供了关于数据包标识符的更多信息。在第2.2.2节中描述了属性的编码规则。
3.10.2.1、UNSUBSCRIBE 属性 UNSUBSCRIBE Properties
* 属性长度 Property Length:
在 SUBSCRIBE 数据包的可变报头中,属性的长度被编码为一个可变的字节整数。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后面是一个UTF-8字符串对。
用户属性允许多次出现,以代表多个名称、值对。同一名称允许出现多次。
非规范评注:UNSUBSCRIBE 数据包上的用户属性可用于将订阅相关的属性从客户端发送到服务器。这些属性的含义没有被本规范所定义。
3.10.3、UNSUBSCRIBE 有效载荷 UNSUBSCRIBE Pauload
UNSUBSCRIBE包的有效载荷包含客户希望取消订阅的主题过滤器的列表。UNSUBSCRIBE包中的主题过滤器必须是1.5.4节中定义的UTF-8编码字符串[MQTT-3.10.3-1],并以连续方式打包。
UNSUBSCRIBE数据包的有效载荷必须至少包含一个主题过滤器[MQTT-3.10.3-2]。一个没有有效载荷的UNSUBSCRIBE数据包是一个协议错误。有关处理错误的信息,请参考第4.13节。
非规范评注:下图显示了一个具有两个主题过滤器 "a/b "和 "c/d "的UNSUBSCRIBE数据包的有效载荷。
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
主题过滤器 | |||||||||
byte 1 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 2 | Length LSB (3) | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
byte 3 | ‘a’ (0x61) | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
byte 4 | ‘/’ (0x2F) | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
byte 5 | ‘b’ (0x62) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
主题过滤器 | |||||||||
byte 6 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 7 | Length LSB (3) | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
byte 8 | ‘c’ (0x63) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
byte 9 | ‘/’ (0x2F) | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
byte 10 | ‘d’ (0x64) | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
3.10.4、UNSUBSCRIBE 操作 UNSUBSCRIBE Actions
在 UNSUBSCRIBE 包中提供的主题过滤器(无论它们是否包含通配符)必须与服务器为客户端持有的当前主题过滤器集进行逐字比较。如果有任何过滤器完全匹配,那么其所属的订阅号必须被删除 [MQTT-3.10.4-1],否则不会发生额外的处理。
当服务器收到 UNSUBSCRIBE :
- 它必须停止添加任何与主题过滤器相匹配的新消息,以便交付给客户[MQTT-3.10.4-2]。
- 它必须完成符合主题过滤器的任何 QoS 1或 QoS 2 消息的交付,并且它已经开始向客户端发送这些消息[MQTT-3.10.4-3]。
- 它可能会继续传递任何现有的缓冲信息,以便传递给客户。
服务器必须通过发送 UNSUBACK 包来响应 UNSUBSCRIBE 请求 [MQTT-3.10.4-4] 。UNSUBACK 包必须具有与 UNSUBSCRIBE 包相同的包标识符。即使在没有删除主题订阅的情况下,服务器也必须以 UNSUBACK 来响应 [MQTT-3.10.4-5] 。
如果服务器收到包含多个主题过滤器的 UNSUBSCRIBE 数据包,它必须像收到多个 UNSUBSCRIBE 数据包的序列那样处理该数据包,但它只发送一个 UNSUBACK 响应 [MQTT-3.10.4-6]。
如果主题过滤器代表共享订阅,则此会话将从共享订阅中分离出来。 如果这个会话是与共享订阅相关联的唯一会话,则共享订阅被删除。请参阅第 4.8.2 节对共享订阅的处理的描述。
3.11、UNSUBACK-取消订阅的确认 UNSUBACK-Unsubscribe acknowledgement
UNSUBACK数据包由服务器发送至客户端以确认收到UNSUBSCRIBE数据包。
3.11.1、UNSUBACK 固定报头
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (11) | 保留位 Reserved | ||||||
1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
剩余长度字段:即可变报头的长度加上有效载荷的长度,被编码为一个可变字节整数。
3.11.2、UNSUBACK 可变报头 UNSUBACK Variable Header
UNSUBACK包的可变报头按顺序包括以下字段:来自被确认的 UNSUBSCRIBE 数据包的包标识符,以及属性。编码属性的规则在第2.2.2节中描述。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | 报文标识符 MSB | |||||||
byte 2 | 报文标识符 LSB |
3.11.2.1、UNSUBACK 属性 UNSUBACK Properties
* 属性长度 Property Length:
UNSUBACK数据包可变报头中属性的长度,以可变字节整数的形式编码。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。后跟UTF-8编码的字符串,代表与这个响应相关的原因。这个原因字符串是一个人类可读的字符串,用于诊断,不应该被客户端解析。
服务器使用这个值来向客户提供额外的信息。如果该属性会使 UNSUBACK 数据包的大小超过客户指定的最大数据包大小,则服务器不得发送该属性 [MQTT-3.11.2-1] 。多次包含 "原因字符串 "是一种协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟UTF-8字符串对。此属性可用于提供额外的诊断或其他信息。如果该属性会使UNSUBACK数据包的大小超过客户指定的最大数据包大小,则服务器不得发送该属性[MQTT-3.11.2-2] 。用户属性允许多次出现以代表多个名称、值对。同一名称允许出现多次。
3.11.3、UNSUBACK 有效载荷 UNSUBACK Payload
有效载荷包含一个原因码的列表。每个原因码对应于被确认的 UNSUBSCRIBE 数据包中的一个主题过滤器。UNSUBACK数据包中的原因码的顺序必须与 UNSUBSCRIBE 数据包中的主题过滤器的顺序相匹配[MQTT-3.11.3-1]。
一个字节的无符号取消订阅原因码的值如下所示。发送 UNSUBACK 包的服务器必须对收到的每个 Topic Filter 使用其中一个取消订阅原因码值 [MQTT-3.11.3-2]。
值(十进制) | 值(十六进制) | 原因码名称 | 描述 |
0 | 0x00 | 成功 | 订阅被删除。 |
17 | 0x11 | 没有找到订阅 | 客户端没有使用匹配的主题过滤器。 |
128 | 0x80 | 未指定的错误 | 取消订阅无法完成,而且服务器不希望透露原因,或其他原因代码都不适用。 |
131 | 0x83 | 具体实施错误 | UNSUBSCRIBE是有效的,但服务器不接受它。 |
135 | 0x87 | 未授权 | 客户端未被授权取消订阅。 |
143 | 0x8F | 主题过滤器无效 | 主题过滤器是正确形成的,但对这个客户来说是不允许的。 |
145 | 0x91 | 已被使用的数据包标识符 | 指定的数据包标识符已在使用中。 |
非规范评注:在相应的 UNSUBSCRIBE 数据包中,每个主题过滤器总是有一个原因码。如果原因码不是特定于主题过滤器(如0x91(使用中的数据包标识符)),它将为每个主题过滤器设置。
3.12、PINGREQ-PING请求 PINGREQ request
PINGREQ 数据包是由客户端发送给服务器的。它可以被用来:
- 在没有任何其他 MQTT 控制包被从客户端发送到服务器的情况下,向服务器表明客户端是活跃的。
- 要求服务器响应,以确认它是活跃的。
- 行使网络(Exercise the network)以表明网络连接是活动的。
这个数据包用于保持连接的处理。更多细节请参考3.1.2.10节。
3.12.1、PINGREQ 固定报头 PINGREQ Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (12) | 保留位 Reserved | ||||||
1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3.12.2、PINGREQ 可变报头 PINGREQ Variable Header
PINGREQ数据包没有可变报头。
3.12.3、PINGREQ 有效载荷 PINGREQ Payload
PINGREQ数据包没有有效载荷。
3.12.4、PINGREQ 操作 PINGREQ Actions
服务器必须发送一个PINGRESP 数据包以响应 PINGREQ 数据包 [MQTT-3.12.4-1]。
3.13、PINGRESP-PING响应 PINGRESP-PING response
PINGRESP数据包是由服务器向客户发送的,以响应PINGREQ数据包。它表明服务器是活跃的。
这个数据包用于Keep Alive处理。更多细节请参考3.1.2.10节。
3.13.1、PINGRESP 固定报头 PINGRESP Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (13) | 保留位 Reserved | ||||||
1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3.13.2、PINGRESP 可变报头 PINGRESP Variable Header
PINGRESP 数据包没有可变报头。
3.13.3、PINGRESP 有效载荷 PINGRESP Payload
PINGRESP 数据包没有有效载荷。
3.13.4、PINGRESP 操作 PINGRESP Actions
客户端在收到这个数据包后不采取任何行动
3.14、DISCONNECT-断开通知 DISCONNECT-Disconnect notification
DISCONNECT数据包是客户端或服务器发送的最后一个MQTT控制数据包。它表明网络连接被关闭的原因。在关闭网络连接之前,客户端或服务器可能会发送一个DISCONNECT数据包。如果网络连接被关闭,而客户端没有首先发送一个原因码为0x00(正常断开)的DISCONNECT数据包,并且该连接有一个Will Message,则 Will Message 被公布。更多细节请参考3.1.2.5节。
3.14.1、DISCONNECT 固定报头 DISCONNECT Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (14) | 保留位 Reserved | ||||||
1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
客户端或服务器必须验证保留位是否被设置为0,如果它们不是0,它将发送一个原因码为0x81(格式错误包)的 DISCONNECT 数据包,如4.13节[MQTT-3.14.1-1]所述。
剩余长度字段:即可变报头的长度,被编码为一个可变字节整数。
3.14.2、DISCONNECT 可变报头 DISCONNECT Variable Header
DISCONNECT数据包的可变报头按顺序包含以下字段。断开的原因码,和属性。属性的编码规则在第2.2.2节描述。
3.14.2.1、断开的原因码 Disconnect Reason Code
可变报头中的第 1 字节是断开原因码。如果剩余长度小于1,则使用0x00的值(正常断开)。
一个字节的无符号断开原因码字段的值如下所示。
值(十进制) | 值(十六进制) | 原因码名称 | 发送者 | 描述 |
0 | 0x00 | 正常断线 | 客户端 / 服务器 | 正常关闭连接。不要发送Will信息。 |
4 | 0x04 | 带有Will消息的断开 | 客户端 | 客户端希望断开连接,但要求服务器也公布其Will消息。 |
128 | 0x80 | 未指定的错误 | 客户端 / 服务器 | 连接已关闭,但发送者不希望透露原因,或其他原因码都不适用。 |
129 | 0x81 | 格式错误的数据包 | 客户端 / 服务器 | 收到的数据包不符合此规范。 |
130 | 0x82 | 协议错误 | 客户端 / 服务器 | 收到一个意外的或失序的数据包。 |
131 | 0x83 | 具体实施错误 | 客户端 / 服务器 | 收到的数据包是有效的,但不能被这个实施所处理。 |
135 | 0x87 | 未经授权 | 服务器 | 该请求未被授权。 |
137 | 0x89 | 服务器繁忙 | 服务器 | 服务器繁忙,无法继续处理来自该客户的请求。 |
139 | 0x8B | 服务器正在关闭 | 服务器 | 服务器正在关闭。 |
141 | 0x8D | Keep Alive 超时 | 服务器 | 连接已关闭,因为在1.5倍Keepalive时间内没有收到数据包。 |
142 | 0x8E | 会话被接管 | 服务器 | 另一个使用相同ClientID的连接,导致此连接被关闭。 |
143 | 0x8F | 主题过滤器无效 | 服务器 | 主题过滤器是正确形成的,但不被这个Sever接受。 |
144 | 0x90 | 主题名称无效 | 客户端 / 服务器 | 主题名称是正确形成的,但不被这个客户端或服务器接受。 |
147 | 0x93 | 超过接收上限 | 客户端 / 服务器 | 客户端或服务器收到超过接收上限的发布,但它没有发送 PUBACK 或 PUBCOMP 。 |
148 | 0x94 | 主题别名无效 | 客户端 / 服务器 | 客户端或服务器已收到包含主题别名的 PUBLISH 数据包,该主题别名大于它在 CONNECT 或 CONNACK 数据包中发送的最大主题别名。 |
149 | 0x95 | 数据包太大 | 客户端 / 服务器 | 数据包大小超过此客户端或服务器的最大数据包大小。 |
150 | 0x96 | 信息速率过高 | 客户端 / 服务器 | 收到的数据速率太高。 |
151 | 0x97 | 已超过配额 | 客户端 / 服务器 | 已经超过了执行或管理的限制。 |
152 | 0x98 | 管理操作 | 客户端 / 服务器 | 连接因管理操作而关闭。 |
153 | 0x99 | 有效载荷格式无效 | 客户端 / 服务器 | 有效载荷格式不符合有效载荷格式标识符所指定的格式。 |
154 | 0x9A | 不支持保留 | 服务器 | 服务器不支持保留信息。 |
155 | 0x9B | 不支持QoS | 服务器 | 客户端指定的 QoS 大于在 CONNACK 中最大 QoS 中指定的 QoS 。 |
156 | 0x9C | 使用另一个服务器 | 服务器 | 客户端应该暂时改变其服务器。 |
157 | 0x9D | 服务器已转移 | 服务器 | 服务器已被移动,客户端应永久改变其服务器位置。 |
158 | 0x9E | 不支持共享订阅 | 服务器 | 服务器不支持共享订阅。 |
159 | 0x9F | 连接率超过了 | 服务器 | 因为连接率太高,所以关闭此连接。 |
160 | 0xA0 | 最大连接时间 | 服务器 | 此连接已超过授权的最大连接时间。 |
161 | 0xA1 | 不支持订阅标识符 | 服务器 | 服务器不支持订阅标识符;订阅不被接受。 |
162 | 0xA2 | 不支持通配符订阅 | 服务器 | 服务器不支持通配符订阅;订阅不被接受。 |
发送 DISCONNECT 数据包的客户或服务器必须使用 DISCONNECT Reason Code 值之一[MQTT-3.14.2-1] 。如果原因码是0x00(正常断开连接)并且没有任何属性,那么原因代码和属性长度可以省略。在这种情况下,DISCONNECT的剩余长度为0。
非规范评注:
- DISCONNECT 数据包用于指示在没有确认数据包(如QoS 0发布)或客户端或服务器无法继续处理连接的情况下断开连接的原因。
- 该信息可以被客户端用来决定是否重试连接,以及在重试连接之前应该等待多长时间。
3.14.2.2、DISCONNECT 属性 DISCONNECT Properties
* 属性长度 Property Length:
DISCONNECT 数据包可变报头中属性的长度,以可变字节整数的形式编码。如果剩余长度小于2,则使用0的值。
* 会话过期间隔 Session Expiry Interval:
字节值为17 (0x11) 为会话到期间隔的标识符。后跟代表会话到期间隔的四字节整数,单位为秒。多次包含会话到期间隔是一个协议错误。
如果没有会话到期间隔,则使用 CONNECT 数据包中的会话到期间隔。
会话到期时间不得由服务器在 DISCONNECT 中发送[MQTT-3.14.2-2]。
如果 CONNECT 数据包中的 Session Expiry Interval 为零,那么在客户端发送的 DISCONNECT 数据包中设置非零的 Session Expiry Interval 就是一个协议错误。如果服务器收到这样一个非零的 Session Expiry Interval,它不会将其视为一个有效的 DISCONNECT 数据包。如第4.13节所述,服务器使用带有原因代码0x82(协议错误)的 DISCONNECT。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。后跟代表断开连接原因的UTF-8编码字符串。这个原因字符串是人类可读的,为诊断而设计,不应该被接收者解析。
如果该属性会使 DISCONNECT 数据包的大小超过接收方指定的最大数据包大小,则发送方不得发送该属性[MQTT-3.14.2-3] 。多次包含 "原因字符串 "是一种协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟UTF-8字符串对。此属性可用于提供额外的诊断或其他信息。如果该属性会使 DISCONNECT 数据包的大小超过接收方指定的最大数据包大小[MQTT-3.14.2-4],则发送方不得发送该属性。用户属性允许多次出现以代表多个名称、值对。同一名称允许出现多次。
* 服务器引用 Server Reference:
字节值为28 (0x1C) 为服务器引用的标识符。后跟一个UTF-8编码的字符串,客户可以用它来识别另一个要使用的服务器。多次包含服务器参考是一个协议错误。
服务器发送 DISCONNECT,包括一个服务器引用和原因码0x9C(使用另一个服务器)或0x9D(服务器移动),如4.13节所述。
有关如何使用服务器参考的信息,请参考第4.11节服务器重定向。
说明 |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
断开的原因码 Disconnect Reason Code | |||||||||
byte 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
属性 Properties | |||||||||
byte 2 | 长度 (5) | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
byte 3 | 会话过期间隔标识符 (17) | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
byte 4 | 会话过期间隔 (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
byte 7 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3.14.3、DISCONNECT 有效载荷 DISCONNECT Payload
DISCONNECT数据包没有有效载荷。
3.14.4、DISCONNECT 操作 DISCONNECT Actions
在发送一个DISCONNECT数据包后,发送者:
- 必须不再在该网络连接上发送任何MQTT控制包[MQTT-3.14.4-1]。
- 必须关闭该网络连接 [MQTT-3.14.4-2]。
在收到原因代码为0x00(成功)的 DISCONNECT 时,服务器:
- 必须丢弃与当前连接相关的任何Will消息,而不将其发布[MQTT-3.14.4-3],如3.1.2.5节中所述。
在收到DISCONNECT时,接收方:
- 应该关闭网络连接。
3.15、AUTH-认证交换 AUTH-Authentication exchange
一个AUTH数据包从客户端发送到服务器或服务器发送到客户端,作为扩展认证交换的一部分,如挑战/响应认证。如果CONNECT数据包不包含相同的认证方法,则客户端或服务器发送AUTH数据包是一个协议错误。
3.15.1、AUTH 固定报头 AUTH Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文类型 (15) | 保留位 Reserved | ||||||
1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | |
byte 2 | 剩余长度 Remaining Length | |||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
AUTH数据包的固定报头的第3、2、1和0位是保留的,必须全部设置为0,客户端或服务器必须将任何其他值视为格式错误,并关闭网络连接[MQTT-3.15.1-1]。
剩余长度字段:即可变报头的长度,被编码为可变字节整数。
3.15.2、AUTH 可变报头 AUTH Variable Header
AUTH数据包的变量头按顺序包含以下字段。验证原因码,和属性。属性的编码规则在2.2.2节中描述。
3.15.2.1、验证原因码 Authentication Reason Code
可变报头中的第0字节是验证原因码。一个字节的无符号 Authenticate Reason Code 字段的值如下所示。AUTH包的发送者必须使用其中一个认证原因码[MQTT-3.15.2-1]。
值(十进制) | 值(十六进制) | 原因码名称 | 发送者 | 描述 |
0 | 0x00 | 成功 | 服务器 | 认证成功 |
24 | 0x18 | 继续认证 | 客户端 / 服务器 | 用另一个步骤继续认证 |
25 | 0x19 | 重新认证 | 客户端 | 启动一个重新认证 |
如果原因码是0x00(成功),并且没有属性,那么原因码和属性长度可以省略。在这种情况下,AUTH的剩余长度为0。
3.15.2.2、AUTH 属性 AUTH Properties
* 属性长度 Property Length:
AUTH数据包可变报头中属性的长度,编码为可变字节整数。
* 认证方法 Authentication Method:
字节值为21 (0x15) 为认证方法的标识符。后跟一个UTF-8编码的字符串,包含认证方法的名称。遗漏认证方法或包含多于一次的认证方法是一个协议错误。关于扩展认证的更多信息,请参考4.12节。
* 认证数据 Authentication Data:
字节值为22 (0x16) 为认证数据的标识符。后跟包含认证数据的二进制数据。多次包含认证数据是一种协议错误。该数据的内容由认证方法定义。关于扩展认证的更多信息,请参考第4.12节。
* 原因字符串 Reason String:
字节值为31 (0x1F) 为原因字符串的标识符。后跟代表断开连接原因的UTF-8编码的字符串。这个原因字符串是人类可读的,为诊断而设计,不应该被接收方解析。
如果该属性会使AUTH数据包的大小超过接收方指定的最大数据包大小[MQTT-3.15.2-2],则发送方不得发送该属性。多次包含原因字符串是一种协议错误。
* 用户属性 User Property:
字节值为38 (0x26) 为用户属性的标识符。后跟UTF-8字符串对。此属性可用于提供额外的诊断或其他信息。如果该属性会使AUTH数据包的大小超过接收方指定的最大数据包大小,则发送方不得发送该属性[MQTT-3.15.2-3]。用户属性允许多次出现,以代表多个名称、值对。同一名称允许出现多次。
3.15.3、AUTH 有效载荷 Auth Payload
AUTH数据包没有有效载荷。
3.15.4、Auth 操作 Auth Actions
关于扩展认证的更多信息,请参考4.12节。
4、Operational behavior 操作行为
4.1、会话状态 Session State
为了实现 QoS 1 和 QoS 2 协议流,客户端和服务器需要将状态与客户端标识符相关联,这被称为会话状态。服务器也将订阅作为会话状态的一部分进行存储。
会话可以在一连串的网络连接中继续进行。它持续的时间是最近一次网络连接加上会话到期间隔。
客户端中的会话状态由以下部分组成:
- 已经发送到服务器的 QoS 1 和 QoS 2 信息,但尚未完全确认。
- 已从服务器收到的 QoS 2 消息,但尚未完全确认。
服务器中的会话状态包括:
- 会话的存在,即使会话状态的其他部分是空的。
- 客户端的订阅,包括任何订阅标识符。
- 已经发送给客户的 QoS 1 和 QoS 2 消息,但还没有被完全确认。
- 等待传输给客户的 QoS 1 和 QoS 2 消息,以及可选的等待传输给客户的 QoS 0 消息。
- 已从客户处收到的 QoS 2 消息,但尚未完全确认。
- 如果会话当前没有连接,则会话将结束的时间和会话状态将被丢弃。
保留的消息不构成服务器中会话状态的一部分,它们不会因会话结束而被删除。
4.1.1、存储会话状态 Storing Session State
当网络连接打开时,客户机和服务器决不能丢弃会话状态[MQTT-4.1.0-1]。服务器必须在网络连接关闭并且会话到期间隔已过时丢弃会话状态 [MQTT-4.1.0-2]。
非规范评注:客户端和服务器实现的存储能力在容量方面当然会有限制,并可能受制于管理策略。存储的会话状态可以作为管理员操作的结果而被丢弃,包括对定义条件的自动响应。这具有终止会话的效果。这些行动可能是由资源限制或其他操作原因引起的。硬件或软件故障有可能导致客户端或服务器所存储的会话状态的丢失或损坏。为谨慎起见,应评估客户机和服务器的存储能力,以确保它们是足够的。
4.1.2、会话状态的非规范示例 Session State non-normative examples
例如,一个电表读数解决方案可能使用 QoS 1 消息来保护读数免受网络损失。解决方案的开发者可能已经确定电力供应足够可靠,在这种情况下,客户端和服务器中的数据可以存储在易失性内存中,而不会有太大的丢失风险。
相反,一个停车计时器支付应用供应商可能决定,支付信息不应该由于网络或客户端故障而丢失。因此,他们要求所有的数据在通过网络传输之前被写入非易失性存储器中。
4.2、网络连接
MQTT协议需要一个底层传输,提供一个有序的、无损的、从客户端到服务器、从服务器到客户端的字节流。本规范并不要求支持任何特定的传输协议。客户端或服务器可以支持这里列出的任何传输协议,或符合本节要求的任何其他传输协议。
客户端或服务器必须支持使用一个或多个底层传输协议,这些协议提供从客户端到服务器以及从服务器到客户端的有序、无损的字节流[MQTT-4.2-1]。
非规范评注:
- [RFC0793]中定义的TCP/IP可用于MQTT v5.0。以下传输协议也是合适的:TLS [RFC5246]、WebSocket [RFC6455]。
- TCP 端口 8883 和 1883 在 IANA 注册,分别用于 MQTT TLS 和非 TLS 通信。
- 无连接的网络传输方式,如用户数据报协议(UDP)本身并不适合,因为它们可能会丢失或重新排列数据。
4.3、服务质量级别和协议流 Quality of Service levels and protocol flows
MQTT根据以下章节中定义的服务质量(QoS)等级来交付应用信息。交付协议是对称的,在下面的描述中,客户端和服务器可以分别扮演发送者或接收者的角色。交付协议只关注应用信息从单个发送者到单个接收者的交付。当服务器向一个以上的客户端传递应用信息时,每个客户端都被独立处理。用于向外交付应用消息的QoS级别可能与入站应用消息的QoS级别不同。
4.3.1、QoS 0:至多交付一次 QoS 0:At most once delivery
消息根据基础网络的能力被交付。接收方不发送响应,发送方不进行重试。消息要么到达接收方,要么根本不到达。
在 QoS 0 传递协议中,发送方:
- 必须发送一个 QoS 为 0 且 DUP 标志设置为 0 的 PUBLISH 包 [MQTT-4.3.1-1]。
在 QoS 0 传递协议中,接收方:
- 在收到PUBLISH数据包时接受消息的所有权。
下图为 QoS 0 协议流程图的非规范示例:
发送方行为 | 控制包 | 接收者行为 |
PUBLISH QoS 0,DUP=0 | ——> | 将应用消息传递给适当的接收者 |
4.3.2、QoS 1:至少交付一次 QoS 1:At least once delivery
这种服务质量水平确保消息至少到达接收方一次。QoS 1 PUBLISH 数据包在其可变报头中有一个数据包标识符,并由一个 PUBACK 数据包确认。第2.2.1节提供了关于数据包标识符的更多信息。
在 QoS 1 交付协议中,发送方:
- 必须在每次有新的应用消息要发布时分配一个未使用的数据包标识符[MQTT-4.3.2-1]。
- 必须发送一个包含数据包标识符的,QoS 1 和 DUP 标志设置为 0 的 PUBLISH 包 [MQTT-4.3.2-2]。
- 必须将该 PUBLISH 数据包视为 "未确认",直到它从接收方收到相应的 PUBACK 数据包。关于未确认信息的讨论,请参考第4.4节[MQTT-4.3.2-3]。
一旦发送方收到PUBACK数据包,数据包标识符就可以重新使用了。
请注意,发送方在等待接收确认时,允许其进一步发送具有不同数据包标识符的PUBLISH数据包。
在 QoS 1 交付协议中,接收方:
- 必须用一个包含来自传入的 PUBLISH 包的数据包标识符的 PUBACK 包来响应,并接受该应用消息的所有权[MQTT-4.3.2-4]。
- 在它发送了一个 PUBACK 包之后,接收方必须将任何包含相同的数据包标识符的传入的 PUBLISH 包视为新的应用消息,而不考虑其 DUP 标志的设置[MQTT-4.3.2-5]。
发送者行为 | MQTT 控制包 | 接收者行为 |
存储消息 | ||
发送 PUBLISH QoS 1,DUP=0, <数据包标识符> | ——> | |
发起对应用信息的转发传递 | ||
<—— | 发送 PUBACK <数据包标识符> | |
丢弃消息 |
接收方在发送 PUBACK 之前不需要完成应用消息的交付。当它的原始发送者收到 PUBACK 包时,应用消息的所有权被转移到接收者。
4.3.3、QoS 2:精确的一次传递 QoS 2:Exactly once delivery
这是最高的服务质量水平,用于既不允许丢失也不允许重复的信息。与QoS 2相关的开销会增加。
QoS 2 消息在其可变报头中有一个数据包标识符。第2.2.1节提供了关于数据包标识符的更多信息。QoS 2 PUBLISH 包的接收者通过两步确认过程确认收到。
在 QoS 2 交付协议中,发送方:
- 当它有一个新的应用消息要发布时,必须分配一个未使用的数据包标识符 [MQTT-4.3.3-1]。
- 必须发送一个包含该数据包标识符的,QoS 2 和 DUP 标志设置为 0 的 PUBLISH 包 [MQTT-4.3.3-2]。
- 必须将 PUBLISH 包视为 "未确认",直到它从接收方收到相应的 PUBREC 包 [MQTT-4.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]。
一旦发送方收到 PUBCOMP 数据包或原因码为 0x80 或更大值的 PUBREC,数据包标识符就可以重新使用了。
请注意,发送方在等待接收确认时,允许进一步发送具有不同数据包标识符的PUBLISH数据包,但要遵守第4.9节中描述的流量控制。
在 QoS 2 交付协议中,接收方:
- 必须以包含来自传入的 PUBLISH 包的数据包标识符的 PUBREC 来响应,并接受应用消息的所有权 [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]。
4.4、消息交付重试 Message delivery retry
当客户端在 Clean Start 设置为 0 且存在会话的情况下重新连接时,客户端和服务器都必须重新发送任何未确认的带有原始数据包标识符的 PUBLISH 数据包(QoS>0时)和 PUBREL 数据包。这是客户端或服务器必须重新发送消息的唯一情况。客户端和服务器不得在任何其他时间重新发送消息 [MQTT-4.4.0-1]。
如果收到的 PUBACK 或 PUBREC 包含一个 0x80 或更大的原因码,则相应的 PUBLISH 数据包被视为已确认,且不得重传[MQTT-4.4.0-2]。
发送方行为 | MQTT 控制包 | 接收者行为 |
存储信息 | ||
PUBLISH QoS 2,DUP=0, <数据包标识符> | ||
——> | ||
存储<数据包标识符>,然后启动应用消息的后续交付 | ||
PUBREC <数据包标识符><原因码> | ||
<—— | ||
丢弃消息,存储收到的 PUBREC <数据包标识符> | ||
PUBREL <数据包标识符> | ||
——> | ||
丢弃<数据包标识符> | ||
发送 PUBCOMP <数据包标识符> | ||
<—— | ||
丢弃存储状态 |
接收方在发送 PUBREC 或 PUBCOMP 之前不需要完成应用消息的交付。当它的原始发送者收到 PUBREC 数据包时,应用消息的所有权就转移到了接收者身上。然而,在接受所有权之前,接收者需要对可能导致转发失败的条件进行所有检查(例如,超过配额、授权等)。接收方使用 PUBREC 中适当的原因码表示成功或失败。
4.5、消息接收 Message receipt
当服务器获得传入的应用程序消息的所有权时,它必须将其添加到那些具有匹配的订阅的客户端的会话状态中[MQTT-4.5.0-1]。匹配规则在第 4.7 节中定义。
在正常情况下,客户端会收到响应于他们所创建的订阅的消息。客户端也可能收到不匹配其任何明确的订阅的消息。如果服务器自动分配给客户端一个订阅,这种情况就会发生。当 UNSUBSCRIBE 操作正在进行时,客户端也可能收到消息。客户机必须根据适用的 QoS 规则确认它收到的任何发布包,而不管它是否选择处理它所包含的应用消息[MQTT-4.5.0-2]。
4.6、消息排序 Message ordering
在执行第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] 。
有序的主题是这样一个主题:在这个主题中,客户可以确定来自同一客户和同一 QoS 的应用消息是按照它们被发布的顺序收到的。当服务器处理已发布到有序主题的消息时,它必须按照从任何给定的客户端收到的顺序向消费者发送 PUBLISH 包(对于相同的主题和 QoS)[MQTT-4.6.0-5]。这是对上述规则的补充。
默认情况下,当服务器在非共享订阅上转发消息时,它必须将每个主题视为有序的主题。[mqtt-4.6.0-6]。服务器可以提供一种管理机制或其他机制来允许一个或多个主题不被当作有序主题。
非规范评注:
上面列出的规则确保当一个消息流被发布并订阅给具有 QoS 1 的有序主题时,订阅者收到的每个消息的最终副本将按照它们被发布的顺序。如果消息被重新发送,重复的消息可以在收到早期的一个消息后收到。例如,发布者可能按照1,2,3,4的顺序发送消息,但如果在消息3发送后出现网络断开,用户可能按照1,2,3,2,3,4的顺序接收。
4.7、主题名称和主题过滤器 Topic Names and Topic Filters
主题级别分隔符用于在主题名称中引入结构。如果存在,它将主题名称划分为多个 "主题级别"。
一个订阅的主题过滤器可以包含特殊的通配符,它允许一个客户同时订阅多个主题。
通配符可以在主题过滤器中使用,但决不能在主题名称中使用[MQTT-4.7.0-1]。
4.7.1.1、主题级别分隔符 Topic level separator
正斜线('/'U+002F)用于分隔主题树中的每一级,并为主题名称提供一个层次结构。当订阅客户指定的主题过滤器中遇到两个通配符中的任何一个时,主题级别分隔符的使用意义重大。主题级分隔符可以出现在主题过滤器或主题名称的任何地方。相邻的主题级别分隔符表示一个零长度的主题级别。
4.7.1.2、多级通配符 Multi-level wildcard
数字符号('#'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 "是无效的。
4.7.1.3、单层通配符 Single-level wildcard
加号('+'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 "匹配 "+/+"和"/+",但不匹配 "+"
4.7.2、以 $ 开头的主题
服务器决不能将以通配符(# 或 +)开头的主题过滤器与以 $ 开头的主题名称相匹配 [MQTT-4.7.2-1]。服务器应该防止客户端使用这种主题名称与其他客户端交换消息。服务器实现可以将以 $ 开头的主题名称用于其他目的。
非规范评注:
- $SYS/ 已被广泛采用,作为包含服务器特定信息或控制API的主题的前缀。
- 应用程序不能为了自己的目的而使用带有 $ 字开头的主题。
- 对 "#" 的订阅将不会收到任何发布在以 $ 开头的主题上的消息。
- 订阅 "+/monitor/Clients " 将不会收到发布到 "$SYS/monitor/Clients " 的任何消息。
- 订阅 "$SYS/#" 将收到发布到以 "$SYS/" 开头的主题的消息。
- 订阅 "$SYS/monitor/+" 将收到发布在 "$SYS/monitor/Clients " 的信息。
- 如果一个客户要接收以 $SYS/ 开头的主题和不以 $ 开头的主题的信息,它必须同时订阅 "#" 和 "$SYS/#"。
4.7.3、主题语义和用法 Topic semantic and usage
以下规则适用于主题名称和主题过滤器:
- 所有的主题名称和主题过滤器都必须至少有一个字符的长度 [MQTT-4.7.3-1] 。
- 主题名称和主题过滤器是区分大小写的。
- 主题名称和主题过滤器可以包括空格字符。
- 前导或尾随的 '/' 可创建一个独特的主题名称或主题过滤器。
- 仅由 '/' 字符组成的主题名称或主题过滤器是有效的。
- 主题名称和主题过滤器不得包括空字符(Unicode U+0000)[Unicode] [MQTT-4.7.3-2] 。
- 主题名称和主题过滤器是UTF-8编码的字符串;它们的编码不得超过65,535字节[MQTT-4.7.3-]。请参考1.5.4节。
除了UTF-8编码字符串的总长度所带来的限制外,主题名称或主题过滤器中的级别数量没有限制。
当服务器执行订阅匹配时,它不得对主题名称或主题过滤器进行任何规范化处理,也不得对未识别的字符进行任何修改或替换[MQTT-4.7.3-4]。主题过滤器中的每个非通配符级别必须与主题名称中的相应级别的字符相匹配,才能匹配成功。
非规范评注:
- UTF-8编码规则意味着主题过滤器和主题名称的比较可以通过比较编码后的UTF-8字节,或通过比较解码后的Unicode字符来进行。
- "ACCOUNTS "和 "Accounts "是两个不同的主题名称。
- "Accounts payable"是一个有效的主题名称。
- "/finance "与 "finance "是不同的。
一条应用消息被发送到其主题过滤器与附加到应用消息的主题名称相匹配的每个客户订阅。主题资源可以由管理员在服务器中预定义,也可以由服务器在收到第一个具有该主题名称的订阅或应用程序消息时动态地创建。服务器也可以使用安全组件来授权给定客户在主题资源上的特定操作。
4.8、订阅 Subscriptions
MQTT提供两种订阅方式,共享和非共享。
非规范评注:在MQTT的早期版本中,所有的订阅都是非共享的。
4.8.1、非共享订阅 Non-shared Subscriptions
一个非共享的订阅只与创建它的MQTT会话相联系。每个订阅包括一个主题过滤器(Topic Filter),指出要在该会话上传递消息的主题,以及订阅选项。服务器负责收集符合过滤器的消息,并在该会话的 MQTT 连接处于活动状态时将其传送。
一个会话不能有一个以上具有相同主题过滤器的非共享订阅,因此主题过滤器可用作识别该会话中的订阅的键。
如果有多个客户端,每个客户端对同一主题都有自己的非共享订阅,那么每个客户端都会得到在该主题上发布的应用程序消息的自己的副本。这意味着非共享订阅不能用于在多个消费客户端之间平衡应用消息的负载,因为在这种情况下,每个消息都会被传递给每个订阅的客户端。
4.8.2、共享订阅 Shared Subscriptions
共享订阅可以与多个订阅的 MQTT 会话相关联。与非共享订阅一样,它有一个主题过滤器和订阅选项;然而,符合其主题过滤器的发布只被发送到其订阅会话中的一个。共享订阅在几个消费的客户端并行地分享发布的处理时很有用。
共享订阅使用一种特殊风格的主题过滤器来识别。这个过滤器的格式是:$share/{ShareName}/{filter}。
- $share 是一个字面字符串,标志着该主题过滤器是一个共享订阅主题过滤器。
- {ShareName} 是一个不包括"/"、"+"或 "#"的字符串。
- {filter} 字符串的其余部分的语法和语义与非共享订阅中的主题过滤器相同。请参考第4.7节。
共享订阅的Topic Filter必须以 $share/ 开头,并且必须包含至少一个字符的 ShareName [MQTT-4.8.2-1]。共享名称必须不包含 "/"、"+"或 "#" 等字符,但必须在后面加上一个 "/" 字符。这个 "/" 字符后面必须有一个主题过滤器 [MQTT-4.8.2-2],如4.7节所述。
非规范评注:共享订阅是在 MQTT 服务器的范围内定义的,而不是在会话的范围内。共享订阅的主题过滤器中包含了一个 ShareName,因此在一个服务器上可以有一个以上的共享订阅具有相同的 {过滤器} 组件。通常情况下,应用程序使用ShareName来表示共享订阅的会话组。
例如:
- 共享订阅 "$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/+" 是不同的共享订阅,尽管它们有相同的 ShareName。虽然它们可能在某种程度上有关系,但它们之间的具体关系并没有因为它们有相同的 ShareName 而被暗示。
共享订阅是通过在 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 时离开共享订阅。
- 每个共享订阅是独立于任何其他的。有可能有两个具有重叠过滤器的共享订阅。在这种情况下,符合两个共享订阅的消息将被两个共享订阅分别处理。如果一个客户有一个共享订阅和一个非共享订阅,并且一个消息与它们都匹配,客户将收到一个消息的副本,因为它有非共享订阅。该消息的第二个副本将被传递给共享订阅的其中一个订阅者,这可能导致第二个副本被发送到该客户。
4.9、流量控制 Flow Control
客户端和服务器通过使用 3.1.2.11.* 节和 3.2.2.3.* 节中描述的接收最大值来控制他们收到的未确认的 PUBLISH 包的数量。接收最大值建立了一个发送配额,用于限制在没有收到 PUBACK(对于QoS 1) 或 PUBCOMP(对于QoS 2) 的情况下可以发送的 PUBLISH 的 QoS > 0 数据包的数量。PUBACK 和 PUBCOMP 以下面描述的方式补充配额:
- 客户端或服务器必须将其初始发送配额设置为一个不超过接收最大值的非零值[MQTT-4.9.0-1]。
- 每次客户端或服务器在 QoS > 0 时发送一个 PUBLISH 包,它就会递减发送配额。如果发送配额达到零,客户端或服务器必须不再发送任何 QoS>0 的 PUBLISH 包 [MQTT-4.9.0-2]。它可以继续发送 QoS 为 0 的 PUBLISH 包,也可以选择暂停发送这些包。即使配额为零,客户端和服务器也必须继续处理和响应所有其他的 MQTT 控制包 [MQTT-4.9.0-3]。
发送配额以1为单位递增:
- 每次收到一个 PUBACK 或 PUBCOMP 数据包时(无论该 PUBACK 或 PUBCOMP 是否带有错误码)。
- 每次收到返回码为 0x80 或更高的 PUBREC 数据包时。
如果发送配额已经等于初始发送配额,则不会再递增。试图递增超过初始发送配额可能是由于在建立新的网络连接后重新传输PUBREL数据包造成的。
关于客户端和服务器在被发送的PUBLISH数据包超过接收最大值允许范围时的反应,请参阅3.3.4节。
发送配额和接收最大值在不同的网络连接中不被保留,并且在每个新的网络连接中被重新初始化,如上所述。它们不是会话状态的一部分。
4.10、请求/响应 Request/Response
一些应用程序或标准可能希望通过 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 节中描述的相关数据属性。
4.10.1、基本请求响应(非规范) Basic Request Response(non-normaltive)
请求/响应的交互过程如下:
- 一个MQTT客户端(请求者)向一个主题发布一个请求消息。请求消息是一个带有响应主题的应用消息。
- 另一个MQTT客户端(响应者)已经订阅了一个主题过滤器,该过滤器与请求消息发布时使用的主题名称相匹配。因此,它收到了该请求消息。可能有多个响应者订阅了这个主题名称,也可能没有。
- 响应者根据请求消息采取适当的行动,然后向请求消息中携带的响应主题属性中的主题名称发布响应消息。
- 在典型的使用中,请求者已经订阅了 "响应主题",从而收到 "响应消息"。然而,其他一些客户可能订阅了响应主题,在这种情况下,响应消息也将被该客户接收和处理。与请求消息一样,发送响应消息的主题可以被多个客户端订阅,也可以不被订阅。
如果请求消息包含一个相关数据属性,响应者就会把这个属性复制到响应消息中,响应消息的接收者就会用这个属性把响应消息与原始请求联系起来。响应消息不包括响应主题属性。
MQTT服务器会转发请求消息中的响应主题和相关数据属性以及响应消息中的相关数据。服务器对待请求消息和响应消息就像对待任何其他应用消息一样。
请求者在发布请求消息之前通常会订阅响应主题。如果在发送响应消息时没有响应主题的订阅者,那么响应消息将不会被传递给任何客户。
请求消息和响应消息可以是任何 QoS,并且响应者可以使用具有非零会话过期时间的会话。通常在 QoS为0 的情况下发送请求消息,并且只在预期应答者被连接时发送。然而,这并不是必须的。
响应者可以使用共享订阅来允许一个响应的客户端池。然而,请注意,当使用共享订阅时,多个客户端之间的消息传递顺序是不保证的。
请求者有责任确保它有必要的权限来发布到请求主题,并订阅它在响应主题属性中设置的主题名称。响应者有责任确保它有权力订阅请求主题并发布到响应主题。虽然主题授权不属于本规范的范围,但建议服务器实现这种授权。
4.10.2、确定响应主题的值(非规范) Determining a Response Topic value(non-normaltive)
请求者可以以他们选择的任何方式(包括通过本地配置)确定用作其响应主题的主题名称。为了避免不同请求者之间的冲突,被请求者客户端使用的响应主题最好是对该客户端唯一的。由于请求者和响应者通常需要对这些主题进行授权,因此使用随机的主题名称可能是一种授权挑战。
为了帮助解决这个问题,本规范在 CONNACK 数据包中定义了一个称为响应信息的属性。服务器可以使用这个属性来指导客户端选择要使用的响应主题。这种机制对客户端和服务器来说都是可选的。在连接时,客户端通过设置 CONNECT 数据包中的请求响应信息属性,请求服务器发送一个响应信息。这将导致服务器在 CONNACK 数据包中插入一个发送的响应信息属性(一个UTF-8编码的字符串)。
该规范没有定义响应信息的内容,但它可用于传递主题树的全局唯一部分,该部分至少在其会话生命周期内为该客户保留。使用这种机制可以使这种配置在服务器中完成一次,而不是在每个客户端中完成。
关于响应信息的定义,请参考3.1.2.11.7节。
4.11、服务器重定向 Server redirection
服务器可以通过发送带有原因码 0x9C(使用另一个服务器)或 0x9D(服务器已移动)的 CONNACK 或 DISCONNECT 来请求客户端使用另一个服务器,如4.13节所述。当发送这些原因码之一时,服务器可能还包括一个服务器引用属性,以指示客户应该使用的服务器的位置。
原因码 0x9C(使用另一个服务器)指定客户端应暂时切换到使用另一个服务器。另一个服务器要么是客户已经知道的,要么是用一个服务器引用指定的。
原因码 0x9D (服务器移动) 指定客户端应该永久地转换到使用另一个服务器。另一个服务器要么已经为客户所知,要么使用服务器引用来指定。
服务器引用是一个UTF-8编码的字符串。这个字符串的值是一个空格分隔的引用列表。这里没有指定引用的格式。
非规范评注:
- 建议每个引用由一个名称组成,后面可选择一个冒号和一个端口号。如果名称中包含冒号,那么名称字符串可以用方括号括起来("["和']")。由方括号括起来的名称不能包含右方括号("]")字符。这是用来表示一个使用冒号分隔符的IPv6字面地址。这是[RFC3986]中描述的URI权限的简化版本。
- 服务器引用中的名称通常代表主机名、DNS 名称 [RFC1035]、SRV 名称 [RFC2782] ,或字面 IP 地址。冒号分隔符后面的值通常是一个十进制的端口号。如果端口信息来自于名称解析(比如SRV)或者是默认的,就不需要这个了。
- 如果给出了多个引用,期望客户会选择其中一个。
- 服务器引用的例子有:myserver.xyz.org、myserver.xyz.org:8883、10.10.151.22:8883 [fe80::9610:3eff:fe1c]:1883 。
服务器被允许永远不发送服务器引用,而客户端被允许忽略服务器引用。这项功能可以用来实现负载平衡、服务器重新定位和客户端对服务器的配置。
4.12、增强认证 Enhanced authentication
MQTT CONNECT 数据包支持使用用户名和密码字段进行网络连接的基本认证。虽然这些字段是为简单的密码认证而命名的,但它们可以被用来进行其他形式的认证,如将令牌作为密码传递。
增强型认证将这种基本认证扩展到包括挑战/响应式认证。它可能涉及客户端和服务器之间在 CONNECT 数据包之后和 CONNACK 数据包之前交换 AUTH 数据包。
为了开始增强型认证,客户端在 CONNECT 数据包中包括一个认证方法。这指定了要使用的认证方法。如果服务器不支持由客户提供的认证方法,它可能会发送一个带有 0x8C(错误的认证方法)或 0x87(未授权)的原因码的 CONNACK,如第4.13节所述,并且必须关闭网络连接[MQTT-4.12.0-1]。
认证方法是客户端和服务器之间关于在认证数据中发送的数据和 CONNECT 中的任何其他字段的含义的协议,以及客户端和服务器为完成认证所需的交换和处理。
非规范评注:认证方法通常是一种 SASL 机制,使用这种注册名称有助于交换。然而,认证方法并不局限于使用注册的 SASL 机制。
如果客户机选择的认证方法指定客户机先发送数据,客户机应在 CONNECT 数据包中包括一个认证数据属性。该属性可用于提供认证方法所指定的数据。认证数据的内容是由认证方法定义的。
如果服务器需要额外的信息来完成认证,它可以向客户端发送一个 AUTH 数据包。该数据包必须包含一个 0x18(继续认证) 的原因码 [MQTT-4.12.0-2]。如果认证方法要求服务器向客户端发送认证数据,它将在认证数据中发送。
客户端通过进一步发送 AUTH 数据包来响应服务器的 AUTH 数据包。该数据包必须包含一个 0x18(继续认证) 的原因代码[MQTT-4.12.0-3]。如果认证方法要求客户端为服务器发送认证数据,它将在认证数据中发送。
客户端和服务器根据需要交换 AUTH 数据包,直到服务器通过发送一个原因码为 0 的 CONNACK 来接受认证。如果接受认证需要向客户端发送数据,则会在认证数据中发送。
客户端可以在这个过程中的任何时候关闭连接。在这样做之前,它可能会发送一个 DISCONNECT 数据包。服务器可以在这个过程中的任何时候拒绝认证。如第4.13节所述,它可能会发送一个带有 0x80 或以上原因码的CONNACK,并且必须关闭网络连接[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 rc=0x18 认证方法="SCRAM-SHA-1" 认证数据=server-first-data;
- 客户端到服务器:AUTH rc=0x18 认证方法="SCRAM-SHA-1" 认证数据=client-final-data;
- 服务器到客户端:CONNACK rc=0 认证方法="SCRAM-SHA-1" 认证数据=server-final-data。
展现 Kerberos 考察的非规范性示例:
- 客户端到服务器:CONNECT 认证方法="GS2-KRB5";
- 服务器到客户端:AUTH rc=0x18 认证方法="GS2-KRB5";
- 客户端到服务器:AUTH rc=0x18 认证方法="GS2-KRB5" 认证数据=初始环境令牌;
- 服务器到客户端:AUTH rc=0x18 认证方法="GS2-KRB5" 认证数据=响应的上下文令牌;
- 客户端到服务器:AUTH rc=0x18 认证方法="GS2-KRB5";
- 服务器到客户端:CONNACK rc=0 认证方法="GS2-KRB5" 认证数据=认证的结果。
4.12.1、重新认证 Re-authentication
如果客户端在 CONNECT 数据包中提供了一个认证方法,它可以在收到 CONNACK 后的任何时候启动重新认证。它通过发送一个原因码为 0x19(重新认证)的 AUTH 数据包来实现这一点。客户端必须将认证方法设置为与最初用于认证网络连接的认证方法相同的值[MQTT-4.12.1-1]。如果认证方法要求先提供客户数据,则该 AUTH 数据包包含作为认证数据的第一条认证数据。
服务器通过向客户端发送 AUTH 数据包来响应该重新认证请求,该数据包的原因码为 0x00(成功),表示重新认证已经完成,或者原因码为 0x18(继续认证),表示需要更多的认证数据。客户端可以通过发送一个原因代码为 0x18(继续认证)的 AUTH 数据包来响应额外的认证数据。这个流程与原始认证一样继续,直到重新认证完成或重新认证失败。
如果重新认证失败,客户端或服务器应发送带有适当的原因代码的 DISCONNECT,如第4.13节所述,并且必须关闭网络连接 [MQTT-4.12.1-2] 。
在这个重新认证的序列中,客户端和服务器之间的其他数据包的流动可以继续使用以前的认证。
非规范评注:服务器可能会通过拒绝重新认证来限制客户在重新认证中可以尝试的更改范围。例如,如果服务器不允许改变用户名,它可以拒绝任何改变用户名的重新认证尝试。
4.13、处理错误 Handling error
4.13.1、格式错误的数据包和协议错误 Malformed Packet and Protocol Errors
格式错误的数据包和协议错误的定义包含在第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 控制包的某些特征,它可能无法检测到错误,因此它可能允许数据被损坏。
4.13.2、其他错误 Other error
除格式错误的数据包和协议错误外,发送方无法预料其他错误,因为接收方可能有它没有告知发送方的限制条件。接收方的客户端或服务器可能会遇到短暂的错误,例如内存不足,从而无法成功处理单个MQTT控制包。
原因码为 0x80 或更大的确认包 PUBACK、PUBREC、PUBREL、PUBCOMP、SUBACK、UNSUBACK 表示由包标识符识别的接收包有错误。对其他会话或同一会话上流动的其他数据包没有任何影响。
CONNACK 和 DISCONNECT 数据包允许一个 0x80 或更大的原因码,表示网络连接将被关闭。如果指定了 0x80 或更大的原因码,那么无论 CONNACK 或 DISCONNECT 是否被发送,网络连接都必须被关闭 [MQTT-4.13.2-1]。发送这些原因码中的一个不会对任何其他会话产生影响。
如果控制数据包包含多个错误,数据包的接收者可以按任何顺序验证数据包,并对发现的任何错误采取适当的行动。
5、安全性(非规范) Security(non-normative)
5.1、简介 Introduction
强烈建议提供 TLS [RFC5246] 的服务器实现应该使用 TCP 端口 8883(IANA 服务名称:secure-mqtt)。
安全是一个快速变化的世界,所以在设计一个安全的解决方案时,总是使用最新的建议。
有一些威胁是解决方案供应商应该考虑的。比如说:
- 设备可能被破坏;
- 客户端和服务器中静止的数据可能被访问;
- 协议行为可能有副作用(如 "定时攻击");
- 拒绝服务(DoS)攻击;
- 通信可能被拦截、改变、重新路由或披露;
- 注入欺骗性的MQTT控制数据包。
MQTT解决方案经常被部署在恶劣的通信环境中。在这种情况下,实施方案将经常需要提供以下机制:
- 用户和设备的认证;
- 对服务器资源的访问授权;
- MQTT控制包和其中包含的应用数据的完整性;
- MQTT控制包和其中包含的应用数据的保密性。
作为一个传输协议,MQTT 只关注消息的传输,提供适当的安全功能是实施者的责任。这通常是通过使用 TLS[RFC5246] 来实现的。
除了技术安全问题外,还可能有地域性(如美国-欧盟隐私保护框架[USEUPRIVSH])、行业性(如PCI DSS[PCIDSS])和监管方面的考虑(如Sarbanes-Oxley[SARBANES])。
5.2、MQTT 解决方案:安全和认证 MQTT solutions:security and certification
一个实施方案可能希望提供与特定行业安全标准的一致性,如NIST网络安全框架[NISTCSF]、PCI-DSS[PCIDSS])、FIPS-140-2[FIPS1402]和NSA套件B[NSAB]。
在NIST网络安全框架[NISTCSF]内使用MQTT的指南可以在MQTT补充出版物《MQTT和NIST改善关键基础设施网络安全的框架》[MQTTNIST]中找到。使用经过行业验证、独立核实和认证的技术将有助于满足合规要求。
5.3、轻量级密码学和受限设备 Lightweight crytography and constrained devices
高级加密标准[AES]是最广泛采用的加密算法。许多处理器中都有对AES的硬件支持,但对于嵌入式处理器来说并不常见。加密算法ChaCha20 [CHACHA20]在软件中的加密和解密速度要快得多,但没有AES那么广泛。
ISO 29192[ISO29192]对加密基元提出了建议,专门调整了在受限的 "低端 "设备上的性能。
5.4、实施说明 Implementation notes
在实现或使用MQTT时,有许多安全问题需要考虑。下面的部分不应该被认为是一个 "检查清单"。
一个实现可能希望实现以下的一些或全部。
5.4.1、服务器对客户端的认证 Authentication of Clients by the Server
CONNECT数据包包含用户名和密码字段。实现可以选择如何利用这些字段的内容。他们可以提供自己的认证机制,使用外部认证系统,如 LDAP [RFC4511] 或 OAuth [RFC6749] 令牌,或利用操作系统的认证机制。
MQTT v5.0提供了一个增强的认证机制,如4.12节所述。使用这个机制需要客户端和服务器都支持它。
以明文传递认证数据、混淆此类数据元素或不要求认证数据的实现应注意,这可能会引起中间人和重放攻击。第5.4.5节介绍了确保数据隐私的方法。
客户端和服务器之间的虚拟专用网络(VPN)可以提供信心,即数据只从授权的客户端接收。
在使用TLS[RFC5246]的情况下,服务器可以使用从客户端发送的TLS证书来验证客户端的身份。
一个实现可能会允许认证,即凭证在客户端到服务器的应用消息中被发送。
5.4.2、服务器对客户端的授权 Authorization of Clients by the Server
如果一个客户已经成功地通过了认证,那么服务器实现应该在接受其连接之前检查它是否被授权。
授权可以基于客户端提供的信息,如用户名、客户端的主机名/IP地址或认证机制的结果。
特别是,实现应检查客户端是否被授权使用客户端标识符,因为这可以访问MQTT会话状态(在4.1节中描述)。这种授权检查是为了防止一个客户意外地或恶意地提供一个已经被其他客户使用的客户标识符。
一个实现应该提供在 CONNECT 之后进行的访问控制,以限制客户端向特定主题发布或使用特定主题过滤器订阅的能力。一个实现应该考虑限制对具有广泛范围的 "主题过滤器 "的访问,例如 "#主题过滤器"。
5.4.3、客户端对服务器的认证 Authentication of Server by the Client
MQTT协议不是信任对称的。当使用基本认证时,没有任何机制可以让客户端对服务器进行认证。某些形式的扩展认证确实允许相互认证。
在使用TLS[RFC5246]的情况下,从服务器发送的TLS证书可以被客户端用来认证服务器。从一个IP地址为多个主机名提供MQTT服务的实现应该注意到[RFC6066]第3节中定义的对TLS的服务器名称指示扩展,这允许客户端告诉服务器它试图连接的服务器的主机名。
一个实施方案可能会允许认证,其中凭证是在从服务器到客户端的应用消息中发送的。MQTT v5.0提供了一个增强的认证机制,如第4.12节所述,它可以用来认证服务器到客户端。使用这个机制需要客户端和服务器的支持。
客户端和服务器之间的 VPN 可以使人相信客户端正在连接到预定的服务器。
5.4.4、应用程序消息和MQTT控制包的完整性 Integrity of Application Messages and MQTT Control Packets
应用程序可以独立地在其应用程序消息中包括哈希值。这可以在网络上和休息时提供发布数据包内容的完整性。
TLS [RFC5246]提供了哈希算法来验证网络上发送的数据的完整性。
使用 VPN 连接客户端和服务器,可以为 VPN 所覆盖的网络部分提供数据的完整性。
5.4.5、应用程序消息和MQTT控制包的隐私性 Privacy of Application Messages and MQTT Control Packets
TLS [RFC5246]可以为网络上发送的数据提供加密。有一些有效的 TLS 密码套件包括一个不对数据进行加密的 NULL 加密算法。为了确保隐私,客户和服务器应该避免这些密码套件。
一个应用程序可以独立地对其应用程序信息的内容进行加密。这可以在网络上和休息时提供应用消息的隐私。这不会为应用程序消息的其他属性提供隐私,如主题名称。
客户端和服务器实现可以为静态数据提供加密存储,如作为会话的一部分存储的应用程序消息。
使用VPN连接客户机和服务器,可以在VPN覆盖的网络部分提供数据隐私。
5.4.6、消息传输的不可抵赖性 Non-repudiation of message transmission
应用设计者可能需要考虑适当的策略来实现端到端的不可抵赖性。
5.4.7、检测客户端和服务器的破坏情况 Detecting compromise of Clients and Servers
使用TLS[RFC5246]的客户端和服务器实现应该提供能力,以确保在启动TLS连接时提供的任何TLS证书都与连接的客户端或被连接的服务器的主机名相关。
使用TLS的客户端和服务器实现可以选择提供检查证书撤销列表(CRLs [RFC5280])和在线证书状态协议(OSCP)[RFC6960]的功能,以防止被撤销的证书被使用。
物理部署可能会将防篡改硬件与应用信息中的特定数据传输相结合。例如,一个电表可能有一个嵌入式GPS,以确保它不被用于未经授权的地点。[IEEE8021AR]是一个标准,用于实现使用加密的标识符来验证设备身份的机制。
5.4.8、检测异常行为 Detecting abnormal behavior
服务器实现可能会监控客户端的行为以检测潜在的安全事件。比如说:
- 重复的连接尝试;
- 重复的认证尝试;
- 连接的异常终止;
- 主题扫描(试图发送或订阅许多主题);
- 发送无法投递的信息(没有订阅主题的人);
- 连接但不发送数据的客户端。
服务器实现可能会关闭违反其安全规则的客户端的网络连接。
检测到不受欢迎的行为的服务器实施方案可能会根据IP地址或客户识别码等标识符来实现动态阻断列表。
部署可以使用网络级控制(如果有的话)来实现速率限制或基于IP地址或其他信息的阻断。
5.4.9、其他安全考虑 Other security considerations
如果客户端或服务器TLS证书丢失或被认为可能被破坏,它们应该被撤销(利用CRLs [RFC5280] 和/或OSCP [RFC6960])。
客户端或服务器认证凭证,如用户名和密码,如果丢失或被认为受到损害,应该被撤销和/或重新签发。
在长效连接的情况下:
- 使用TLS [RFC5246]的客户端和服务器实现应允许会话重新协商,以建立新的加密参数(更换会话密钥,改变密码套件,改变认证凭证)。
- 服务器可以关闭客户的网络连接,并要求他们用新的凭证重新认证。
- 服务器可以要求他们的客户端使用4.12.1节中描述的机制定期进行重新认证。
受限设备和受限网络上的客户可以利用TLS [RFC5246]会话恢复,以减少重新连接TLS [RFC5246]会话的成本。
连接到一个服务器的客户与连接到同一服务器的其他客户有一种相互信任的关系,这些客户有权力在相同的主题上发布数据。
5.4.10、SOCKS 的使用 Use of SOCKS
客户端的实现应该意识到,有些环境需要使用SOCKSv5 [RFC1928] 代理来进行外向网络连接。一些MQTT实施方案可以通过使用SOCKS来使用替代的安全隧道(例如SSH)。如果实施方案选择使用SOCKS,它们应该支持匿名和用户名、密码认证的SOCKS代理。在后一种情况下,实现者应该意识到SOCKS认证可能以明文形式出现,因此应该避免使用相同的凭证来连接到MQTT服务器。
5.4.11、安全配置文件 Security profiles
实施者和解决方案设计者可能希望将安全视为一套可应用于MQTT协议的配置文件。下面是一个分层安全等级的例子。
5.4.11.1、清晰的通信配置文件 Clear communication profile
当使用清晰的通信配置文件时,MQTT协议在一个开放的网络上运行,没有额外的安全通信机制。
5.4.11.2、安全的网络通信配置文件 Secured network communication profile
当使用安全网络通信配置文件时,MQTT协议在具有安全控制的物理或虚拟网络上运行,例如,VPN或物理安全网络。
5.4.11.3、安全的传输配置文件 Secured transport profile
当使用安全传输配置文件时,MQTT协议通过物理或虚拟网络运行,并使用提供认证、完整性和隐私的TLS [RFC5246]。
TLS [RFC5246] 客户端认证可以在用户名和密码字段所提供的 MQTT 客户端认证之外使用,也可以代替 MQTT 客户端认证。
5.4.11.4、行业特定的安全配置文件 Industry specific security profiles
预计MQTT协议将被设计成行业特定的应用配置文件,每个配置文件都定义了一个威胁模型和用于解决这些威胁的特定安全机制。关于具体安全机制的建议通常来自于现有的工作,包括:
- [NISTCSF] NIST网络安全框架
- [NIST7628] NISTIR 7628智能电网网络安全指南
- [FIPS1402] 加密模块的安全要求(FIPS PUB 140-2)。
- [PCIDSS] PCI-DSS支付卡行业数据安全标准
- [NSAB] 国家安全局套装B加密法
6、使用 WebSocket 作为网络传输 Using WebSocket as a network transport
如果MQTT是通过WebSocket [RFC6455]连接传输的,则适用以下条件:
- MQTT控制包必须以WebSocket二进制数据帧发送。如果收到任何其他类型的数据帧,收件人必须关闭网络连接[MQTT-6.0.0-1];
- 一个WebSocket数据帧可以包含多个或部分MQTT控制包。接收方不得认为MQTT控制包在WebSocket帧边界上是对齐的 [MQTT-6.0.0-2];
- 客户端必须将 "mqtt "列入其提供的WebSocket子协议列表中 [MQTT-6.0.0-3];
- 服务器选择并返回的WebSocket子协议名称必须是 "mqtt" [MQTT-6.0.0-4];
- 用于连接客户端和服务器的WebSocket URI对MQTT协议没有影响。
6.1、IANA 的考虑 IANA consideration
本规范要求IANA修改WebSocket MQTT子协议在 "WebSocket子协议名称 "注册表下的注册,其数据如下:
子协议标识符 Subprotocol Identifier | mqtt |
子协议通用名称 Subprotocol Common Name | mqtt |
子协议的定义 Subprotocol Definition | http://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html |
7、一致性 Conformance
MQTT规范定义了MQTT客户端实现和MQTT服务器实现的一致性。一个MQTT实现可以作为一个MQTT客户端和一个MQTT服务器来遵守。
7.1、符合性条款 Conformance clause
7.1.1、MQTT 服务器符合性条款 MQTT Server conformance clause
关于服务器的定义,请参考术语部分中的服务器。
一个MQTT服务器只有在满足以下所有陈述时才符合本规范:
- 服务器发送的所有MQTT控制包的格式与第2章和第3章中描述的格式一致。
- 它遵循第4.7节所述的主题匹配规则和第4.8节的订阅规则。
- 它满足以下各章中确定的MUST级要求,但只适用于客户端的要求除外:第1章;第2章;第3章;第4章;第6章。
- 它不要求使用本规范之外定义的任何扩展,以便与任何其他符合要求的实现进行互操作。
7.1.2、MQTT 客户端符合性条款 MQTT Client conformance clause
关于客户端的定义,请参考术语部分中的客户端。
一个MQTT客户端只有在满足以下所有声明的情况下才符合本规范:
- 客户端发送的所有MQTT控制包的格式与第2章和第3章中描述的格式一致。
- 它满足以下各章中确定的MUST级别的要求,但只适用于服务器的要求除外:第1章;第2章;第3章;第4章;第6章。
- 它不要求使用本规范以外定义的任何扩展,以便与任何其他符合要求的实现进行互操作。
8、附录 Appendix
8.1、附录A.鸣谢 Appendix A.Acknowledments
技术委员会特别感谢Andy Stanford-Clark博士和Arlen Nipper,他们是MQTT协议的原始发明者,并对他们在标准化过程中的持续支持表示感谢。
在本规范制定过程中,以下人员是OASIS技术委员会的成员,在此对他们的贡献表示感谢。
Participants:
- Senthil Nathan Balasubramaniam (Infiswift)
- Dr. Andrew Banks, editor (IBM)
- Ken Borgendale, editor (IBM)
- Ed Briggs, editor (Microsoft)
- Raphael Cohn (Individual)
- Richard Coppen, chairman (IBM)
- William Cox (Individual)
- Ian Craggs , secretary (IBM)
- Konstantin Dotchkoff (Microsoft)
- Derek Fu (IBM)
- Rahul Gupta, editor (IBM)
- Stefan Hagen (Individual)
- David Horton (Solace Systems)
- Alex Kritikos (Software AG, Inc.)
- Jonathan Levell (IBM)
- Shawn McAllister (Solace Systems)
- William McLane (TIBCO Software Inc.)
- Peter Niblett (IBM)
- Dominik Obermaier (dc-square GmbH)
- Nicholas O'Leary (IBM)
- Brian Raymor, chairman (Microsoft)
- Andrew Schofield (IBM)
- Tobias Sommer (Cumulocity)
- Joe Speed (IBM)
- Dr Andy Stanford-Clark (IBM)
- Allan Stockdill-Mander (IBM)
- Stehan Vaillant (Cumulocity)
关于对MQTT早期版本的贡献者名单,请参考MQTT v3.1.1规范[MQTTV311]中的附录A。
8.2、附录2.强制性规范声明(非规范) Appendix B.Mandatory normative statement(non-normative)
声明序号 | 规范声明 |
---|---|
[MQTT-1.5.3-1] | UTF-8编码字符串中的字符数据必须是按照Unicode规范 [Unicode] 定义的和在RFC3629 [RFC3629] 中重申的有效的UTF-8格式。特别需要指出的是,这些数据不能包含字符码在U+D800和U+DFFF之间的数据。如果服务端或客户端收到了一个包含无效UTF-8字符的控制报文,它必须关闭网络连接。 |
[MQTT-1.5.3-2] | UTF-8编码的字符串不能包含空字符U+0000。如果客户端或服务端收到了一个包含U+0000的控制报文,它必须关闭网络连接。 |
[MQTT-1.5.3-3] | UTF-8编码序列0XEF 0xBB 0xBF总是被解释为U+FEFF(零宽度非换行空白字符),无论它出现在字符串的什么位置,报文接收者都不能跳过或者剥离它。 |
[MQTT-2.2.2-1] | 表格 2.2中任何标记为“保留”的标志位,都是保留给以后使用的,必须设置为表格中列出的值。 |
[MQTT-2.2.2-2] | 如果收到非法的标志,接收者必须关闭网络连接。 |
[MQTT-2.3.1-1] | SUBSCRIBE,UNSUBSCRIBE和PUBLISH(QoS大于0)控制报文必须包含一个非零的16位报文标识符(Packet Identifier。 |
[MQTT-2.3.1-2] | 客户端每次发送一个新的这些类型的报文时都必须分配一个当前未使用的报文标识符。 |
[MQTT-2.3.1-3] | 如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。当客户端处理完这个报文对应的确认后,这个报文标识符就释放可重用。QoS 1的PUBLISH对应的是PUBACK,QoS 2的PUBLISH对应的是PUBCOMP,与SUBSCRIBE或UNSUBSCRIBE对应的分别是SUBACK或UNSUBACK |
[MQTT-2.3.1-4] | 发送一个QoS 0的PUBLISH报文时,相同的条件也适用于服务端。 |
[MQTT-2.3.1-5] | QoS设置为0的PUBLISH报文不能包含报文标识符。 |
[MQTT-2.3.1-6] | PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的报文标识符。 |
[MQTT-2.3.1-7] | 与 [MQTT-2.3.1-6] 类似,SUBACK和UNSUBACK必须包含在对应的SUBSCRIBE和UNSUBSCRIBE报文中使用的报文标识符。 |
[MQTT-3.1.0-1] | 客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是CONNECT报文。 |
[MQTT-3.1.0-2] | 在一个网络连接上,客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接。 |
[MQTT-3.1.2-1] | 如果协议名不正确服务端可以断开客户端的连接,也可以按照某些其它规范继续处理CONNECT报文。对于后一种情况,按照本规范,服务端不能继续处理CONNECT报文。 |
[MQTT-3.1.2-2] | 如果发现不支持的协议级别,服务端必须给发送一个返回码为0x01(不支持的协议级别)的CONNACK报文响应CONNECT报文,然后断开客户端的连接。 |
[MQTT-3.1.2-3] | 服务端必须验证CONNECT控制报文的保留标志位(第0位)是否为0,如果不为0必须断开客户端连接。 |
[MQTT-3.1.2-4] | 如果清理会话(CleanSession)标志被设置为0,服务端必须基于当前会话(使用客户端标识符识别)的状态恢复与客户端的通信。如果没有与这个客户端标识符关联的会话,服务端必须创建一个新的会话。在连接断开之后,当连接断开后,客户端和服务端必须保存会话信息。 |
[MQTT-3.1.2-5] | 当清理会话标志为0的会话连接断开之后,服务端必须将之后的QoS 1和QoS 2级别的消息保存为会话状态的一部分,如果这些消息匹配断开连接时客户端的任何订阅。 |
[MQTT-3.1.2-6] | 如果清理会话(CleanSession)标志被设置为1,客户端和服务端必须丢弃之前的任何会话并开始一个新的会话。会话仅持续和网络连接同样长的时间。与这个会话关联的状态数据不能被任何之后的会话重用。 |
[MQTT-3.1.2.7] | 保留消息不是服务端会话状态的一部分,会话终止时不能删除保留消息。 |
[MQTT-3.1.2-8] | 遗嘱标志(Will Flag)被设置为1,表示如果连接请求被接受了,遗嘱(Will Message)消息必须被存储在服务端并且与这个网络连接关联。之后网络连接关闭时,服务端必须发布这个遗嘱消息,除非服务端收到DISCONNECT报文时删除了这个遗嘱消息。 |
[MQTT-3.1.2-9] | 如果遗嘱标志被设置为1,连接标志中的Will QoS和Will Retain字段会被服务端用到,同时有效载荷中必须包含Will Topic和Will Message字段。 |
[MQTT-3.1.2-10] | 一旦被发布或者服务端收到了客户端发送的DISCONNECT报文,遗嘱消息就必须从存储的会话状态中移除。 |
[MQTT-3.1.2-11] | 如果遗嘱标志被设置为0,连接标志中的Will QoS和Will Retain字段必须设置为0,并且有效载荷中不能包含Will Topic和Will Message字段。 |
[MQTT-3.1.2-12] | 如果遗嘱标志被设置为0,网络连接断开时,不能发送遗嘱消息。. |
[MQTT-3.1.2-13] | 如果遗嘱标志被设置为0,遗嘱QoS也必须设置为0(0x00)。 |
[MQTT-3.1.2-14] | 如果遗嘱标志被设置为1,遗嘱QoS的值可以等于0(0x00),1(0x01),2(0x02)。它的值不能等于3。 |
[MQTT-3.1.2-15] | 如果遗嘱标志被设置为0,遗嘱保留(Will Retain)标志也必须设置为0。 |
[MQTT-3.1.2-16] | 如果遗嘱保留被设置为0,服务端必须将遗嘱消息当作非保留消息发布。 |
[MQTT-3.1.2-17] | 如果遗嘱保留被设置为1,服务端必须将遗嘱消息当作保留消息发布。 |
[MQTT-3.1.2-18] | 如果用户名(User Name)标志被设置为0,有效载荷中不能包含用户名字段。 |
[MQTT-3.1.2-19] | 如果用户名(User Name)标志被设置为1,有效载荷中必须包含用户名字段。 |
[MQTT-3.1.2-20] | 如果密码(Password)标志被设置为0,有效载荷中不能包含密码字段。 |
[MQTT-3.1.2-21] | 如果密码(Password)标志被设置为1,有效载荷中必须包含密码字段 |
[MQTT-3.1.2-22] | 如果用户名标志被设置为0,密码标志也必须设置为0。 |
[MQTT-3.1.2-23] | 客户端负责保证控制报文发送的时间间隔不超过保持连接的值。如果没有任何其它的控制报文可以发送,客户端必须发送一个PINGREQ报文。 |
[MQTT-3.1.2-24] | 如果保持连接的值非零,并且服务端在一点五倍的保持连接时间内没有收到客户端的控制报文,它必须断开客户端的网络连接,认为网络连接已断开。 |
[MQTT-3.1.3-1] | 如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。 |
[MQTT-3.1.3-2] | 服务端使用客户端标识符 (ClientId) 识别客户端。连接服务端的每个客户端都有唯一的客户端标识符(ClientId)。客户端和服务端都必须使用ClientId识别两者之间的MQTT会话相关的状态。 |
[MQTT-3.1.3-3] | 客户端标识符 (ClientId) 必须存在而且必须是CONNECT报文有效载荷的第一个字段。 |
[MQTT-3.1.3-4] | 客户端标识符必须是1.5.3节定义的UTF-8编码字符串。 |
[MQTT-3.1.3-5] | 服务端必须允许1到23个字节长的UTF-8编码的客户端标识符,客户端标识符只能包含这些字符:“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”(大写字母,小写字母和数字)。 |
[MQTT-3.1.3-6] | 服务端可以允许客户端提供一个零字节的客户端标识符 (ClientId) ,如果这样做了,服务端必须将这看作特殊情况并分配唯一的客户端标识符给那个客户端。然后它必须假设客户端提供了那个唯一的客户端标识符,正常处理这个CONNECT报文。 |
[MQTT-3.1.3-7] | 如果客户端提供了一个零字节的客户端标识符,它必须同时将清理会话标志设置为1。 |
[MQTT-3.1.3-8] | 如果客户端提供的ClientId为零字节且清理会话标志为0,服务端必须发送返回码为0x02(表示标识符不合格)的CONNACK报文响应客户端的CONNECT报文,然后关闭网络连接。 |
[MQTT-3.1.3-9] | 如果服务端拒绝了这个ClientId,它必须发送返回码为0x02(表示标识符不合格)的CONNACK报文响应客户端的CONNECT报文,然后关闭网络连接。 |
[MQTT-3.1.3-10] | 遗嘱主题必须是 1.5.3节定义的UTF-8编码字符串。 |
[MQTT-3.1.3-11] | 用户名必须是 1.5.3节定义的UTF-8编码字符串。 |
[MQTT-3.1.4-1] | 服务端必须按照3.1节的要求验证CONNECT报文,如果报文不符合规范,服务端不发送CONNACK报文直接关闭网络连接。 |
[MQTT-3.1.4-2] | 如果ClientId表明客户端已经连接到这个服务端,那么服务端必须断开原有的客户端连接。 |
[MQTT-3.1.4-3] | 服务端必须按照 3.1.2.4节的描述执行清理会话的过程。 |
[MQTT-3.1.4-4] | 服务端必须发送返回码为零的CONNACK报文作为CONNECT报文的确认响应。 |
[MQTT-3.1.4-5] | 如果服务端拒绝了CONNECT,它不能处理客户端在CONNECT报文之后发送的任何数据。 |
[MQTT-3.2.0-1] | 服务端发送CONNACK报文响应从客户端收到的CONNECT报文。服务端发送给客户端的第一个报文必须是CONNACK。 |
[MQTT-3.2.2-1] | 如果服务端收到清理会话(CleanSession)标志为1的连接,除了将CONNACK报文中的返回码设置为0之外,还必须将CONNACK报文中的当前会话设置(Session Present)标志为0。 |
[MQTT-3.2.2-2] | 如果服务端收到一个CleanSession为0的连接,当前会话标志的值取决于服务端是否已经保存了ClientId对应客户端的会话状态。如果服务端已经保存了会话状态,它必须将CONNACK报文中的当前会话标志设置为1。 |
[MQTT-3.2.2-3] | 如果服务端没有已保存的会话状态,它必须将CONNACK报文中的当前会话设置为0。还需要将CONNACK报文中的返回码设置为0。 |
[MQTT-3.2.2-4] | 如果服务端发送了一个包含非零返回码的CONNACK报文,它必须将当前会话标志设置为0。 |
[MQTT-3.2.2-5] | 如果服务端发送了一个包含非零返回码的CONNACK报文,那么它必须关闭网络连接。. |
[MQTT-3.2.2-6] | 如果认为上表格3.1中的所有连接返回码都不太合适,那么服务端必须关闭网络连接,不需要发送CONNACK报文。 |
[MQTT-3.3.1-1] | 客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1。 |
[MQTT-3.3.1-2] | 对于QoS 0的消息,DUP标志必须设置为0 |
[MQTT-3.3.1-3] | 服务端发送PUBLISH报文给订阅者时,收到(入站)的PUBLISH报文的DUP标志的值不会被传播。发送(出站)的PUBLISH报文与收到(入站)的PUBLISH报文中的DUP标志是独立设置的,它的值必须单独的根据发送(出站)的PUBLISH报文是否是一个重发来确定。 |
[MQTT-3.3.1-4] | PUBLISH报文不能将QoS所有的位设置为1。如果服务端或客户端收到QoS所有位都为1的PUBLISH报文,它必须关闭网络连接。 |
[MQTT-3.3.1-5] | 如果客户端发给服务端的PUBLISH报文的保留(RETAIN)标志被设置为1,服务端必须存储这个应用消息和它的服务质量等级(QoS),以便它可以被分发给未来的主题名匹配的订阅者。 |
[MQTT-3.3.1-6] | 一个新的订阅建立时,对每个匹配的主题名,如果存在最近保留的消息,它必须被发送给这个订阅者。 |
[MQTT-3.3.1-7] | 如果服务端收到一条保留(RETAIN)标志为1的QoS 0消息,它必须丢弃之前为那个主题保留的任何消息。它应该将这个新的QoS 0消息当作那个主题的新保留消息,但是任何时候都可以选择丢弃它 — 如果这种情况发生了,那个主题将没有保留消息。 |
[MQTT-3.3.1-8] | 服务端发送PUBLISH报文给客户端时,如果消息是作为客户端一个新订阅的结果发送,它必须将报文的保留标志设为1。 |
[MQTT-3.3.1-9] | 当一个PUBLISH报文发送给客户端是因为匹配一个已建立的订阅时,服务端必须将保留标志设为0,不管它收到的这个消息中保留标志的值是多少。 |
[MQTT-3.3.1-10] | 保留标志为1且有效载荷为零字节的PUBLISH报文会被服务端当作正常消息处理,它会被发送给订阅主题匹配的客户端。此外,同一个主题下任何现存的保留消息必须被移除,因此这个主题之后的任何订阅者都不会收到一个保留消息。 |
[MQTT-3.3.1-11] | 服务端不能存储零字节的保留消息。 |
[MQTT-3.3.1-12] | 如果客户端发给服务端的PUBLISH报文的保留标志位0,服务端不能存储这个消息也不能移除或替换任何现存的保留消息。 |
[MQTT-3.3.2-1] | 主题名必须是PUBLISH报文可变报头的第一个字段。它必须是 1.5.3节定义的UTF-8编码的字符串。 |
[MQTT-3.3.2-2] | PUBLISH报文中的主题名不能包含通配符。 |
[MQTT-3.3.2-3] | 服务端发送给订阅客户端的PUBLISH报文的主题名必须匹配该订阅的主题过滤器(根据 4.7节定义的匹配过程)。 |
[MQTT-3.3.4-1] | PUBLISH报文的接收者必须按照根据PUBLISH报文中的QoS等级发送响应,见表格3.4的描述。 |
[MQTT-3.3.5-1] | 服务端必须将消息分发给所有订阅匹配的QoS等级最高的客户端。 |
[MQTT-3.3.5-2] | 如果服务端实现不授权某个客户端发布PUBLISH报文,它没有办法通知那个客户端。它必须按照正常的QoS规则发送一个正面的确认,或者关闭网络连接。 |
[MQTT-3.6.1-1] | PUBREL控制报文固定报头的第3,2,1,0位是保留位,必须被设置为0,0,1,0。服务端必须将其它的任何值都当做是不合法的并关闭网络连接。 |
[MQTT-3.8.1-1] | SUBSCRIBE控制报固定报头的第3,2,1,0位是保留位,必须分别设置为0,0,1,0。服务端必须将其它的任何值都当做是不合法的并关闭网络连接。 |
[MQTT-3.8.3-1] | SUBSCRIBE报文有效载荷中的主题过滤器列表必须是1.5.3节定义的UTF-8字符串。 |
[MQTT-3.8.3-2] | 如果服务端选择不支持包含通配符的主题过滤器,必须拒绝任何包含通配符过滤器的订阅请求。 |
[MQTT-3.8.3-3] | SUBSCRIBE报文的有效载荷必须包含至少一对主题过滤器 和 QoS等级字段组合。没有有效载荷的SUBSCRIBE报文是违反协议的。 |
[MQTT-3-8.3-4] | 如果有效载荷中的任何位是非零值,或者QoS不等于0,1或2,服务端必须认为SUBSCRIBE报文是不合法的并关闭网络连接。 |
[MQTT-3.8.4-1] | 服务端收到客户端发送的一个SUBSCRIBE报文时,必须使用SUBACK报文响应。 |
[MQTT-3.8.4-2] | SUBACK报文必须和等待确认的SUBSCRIBE报文有相同的报文标识符。 |
[MQTT-3.8.4-3] | 如果服务端收到一个SUBSCRIBE报文,报文的主题过滤器与一个现存订阅的主题过滤器相同,那么必须使用新的订阅彻底替换现存的订阅。新订阅的主题过滤器和之前订阅的相同,但是它的最大QoS值可以不同。与这个主题过滤器匹配的任何现存的保留消息必须被重发,但是发布流程不能中断。 |
[MQTT-3.8.4-4] | 如果服务端收到包含多个主题过滤器的SUBSCRIBE报文,它必须如同收到了一系列的多个SUBSCRIBE报文一样处理那个,除了需要将它们的响应合并到一个单独的SUBACK报文发送。 |
[MQTT-3.8.4-5] | 服务端发送给客户端的SUBACK报文对每一对主题过滤器 和QoS等级都必须包含一个返回码。这个返回码必须表示那个订阅被授予的最大QoS等级,或者表示这个订阅失败。 |
[MQTT-3.8.4-6] | 服务端可以授予比订阅者要求的低一些的QoS等级。为响应订阅而发出的消息的有效载荷的QoS必须是原始发布消息的QoS和服务端授予的QoS两者中的最小值。如果原始消息的QoS是1而被授予的最大QoS是0,允许服务端重复发送一个消息的副本给订阅者。 |
[MQTT-3.9.3-1] | 返回码的顺序必须和SUBSCRIBE报文中主题过滤器的顺序相同。 |
[MQTT-3.9.3-2] | 0x00, 0x01, 0x02, 0x80之外的SUBACK返回码是保留的,不能使用。 |
[MQTT-3.10.1-1] | UNSUBSCRIBE报文固定报头的第3,2,1,0位是保留位且必须分别设置为0,0,1,0。服务端必须认为任何其它的值都是不合法的并关闭网络连接。 |
[MQTT-3.10.3-1] | UNSUBSCRIBE报文中的主题过滤器必须是连续打包的、按照1.5.3节定义的UTF-8编码字符串。 |
[MQTT-3.10.3-2] | UNSUBSCRIBE报文的有效载荷必须至少包含一个消息过滤器。没有有效载荷的UNSUBSCRIBE报文是违反协议的。 |
[MQTT-3.10.4-1] | UNSUBSCRIBE报文提供的主题过滤器(无论是否包含通配符)必须与服务端持有的这个客户端的当前主题过滤器集合逐个字符比较。如果有任何过滤器完全匹配,那么它(服务端)自己的订阅将被删除,否则不会有进一步的处理。 |
[MQTT-3.10.4-2] | 如果服务端删除了一个订阅,它必须停止分发任何新消息给这个客户端。 |
[MQTT-3.10.4-3] | 如果服务端删除了一个订阅,它必须完成分发任何已经开始往客户端发送的QoS 1和QoS 2的消息。 |
[MQTT-3.10.4-4] | 服务端必须发送UNSUBACK报文响应客户端的UNSUBSCRIBE请求。UNSUBACK报文必须包含和UNSUBSCRIBE报文相同的报文标识符。 |
[MQTT-3.10.4-5] | 即使没有删除任何主题订阅,服务端也必须发送一个SUBACK响应。 |
[MQTT-3.10.4-6] | 如果服务端收到包含多个主题过滤器的UNSUBSCRIBE报文,它必须如同收到了一系列的多个UNSUBSCRIBE报文一样处理那个报文,除了将它们的响应合并到一个单独的UNSUBACK报文外。 |
[MQTT-3.12.4-1] | 服务端必须发送 PINGRESP报文响应客户端的PINGREQ报文。 |
[MQTT-3.14.1-1] | 服务端必须验证所有的保留位都被设置为0,如果它们不为0必须断开连接。 |
[MQTT-3.14.4-1] | 客户端发送DISCONNECT报文之后,必须关闭网络连接。 |
[MQTT-3.14.4-2] | 客户端发送DISCONNECT报文之后,不能通过那个网络连接再发送任何控制报文。 |
[MQTT-3.14.4-3] | 服务端收到DISCONNECT报文时,必须丢弃任何与当前连接关联的未发布的遗嘱消息,具体描述见 3.1.2.5节。 |
[MQTT-4.1.0-1] | 在整个会话期间,客户端和服务端都必须存储会话状态。 |
[MQTT-4.1.0-2] | 会话必须至少持续和它的活跃网络连接同样长的时间。 |
[MQTT-4.3.1-1] | 对于QoS 0的分发协议,发送者必须发送QoS等于0,DUP等于0的PUBLISH报文。 |
[MQTT-4.4.0-1] | 客户端设置清理会话(CleanSession)标志为0重连时,客户端和服务端必须使用原始的报文标识符重发任何未确认的PUBLISH报文(如果QoS>0)和PUBREL报文。 |
[MQTT-4.5.0-1] | 服务端接管入站应用消息的所有权时,它必须将消息添加到订阅匹配的客户端的会话状态中。匹配规则定义见 4.7节。 |
[MQTT-4.5.0-2] | 客户端必须按照可用的服务质量(QoS)规则确认它收到的任何PUBLISH报文,不管它选择是否处理报文包含的应用消息。 |
[MQTT-4.6.0-1] | 重发任何之前的PUBLISH报文时,必须按原始PUBLISH报文的发送顺序重发(适用于QoS 1和QoS 2消息)。 |
[MQTT-4.6.0-2] | 必须按照对应的PUBLISH报文的顺序发送PUBACK报文(QoS 1消息)。 |
[MQTT-4.6.0-3] | 必须按照对应的PUBLISH报文的顺序发送PUBREC报文(QoS 2消息)。 |
[MQTT-4.6.0-4] | 必须按照对应的PUBREC报文的顺序发送PUBREL报文(QoS 2消息)。 |
[MQTT-4.6.0-5] | 服务端必须默认认为每个主题都是有序的。它可以提供一个管理功能或其它机制,以允许将一个或多个主题当作是无序的。 |
[MQTT-4.6.0-6] | 服务端处理发送给有序主题的消息时,必须按照上面的规则将消息分发给每个订阅者。此外,它必须按照从客户端收到的顺序发送PUBLISH报文给消费者(对相同的主题和QoS)。 |
[MQTT-4.7.1-1] | 主题过滤器中可以使用通配符,但是主题名不能使用通配符。 |
[MQTT-4.7.1-2] | 多层通配符必须位于它自己的层级或者跟在主题层级分隔符后面。不管哪种情况,它都必须是主题过滤器的最后一个字符。 |
[MQTT-4.7.1-3] | 在主题过滤器的任意层级都可以使用单层通配符,包括第一个和最后一个层级。然而它必须占据过滤器的整个层级。 |
[MQTT-4.7.2-1] | 服务端不能将 $ 字符开头的主题名匹配通配符 (#或+) 开头的主题过滤器。 |
[MQTT-4.7.3-1] | 所有的主题名和主题过滤器必须至少包含一个字符。 |
[MQTT-4.7.3-2] | 主题名和主题过滤器不能包含空字符 (Unicode U+0000) [Unicode] 。 |
[MQTT-4.7.3-3] | 主题名和主题过滤器是UTF-8编码字符串,它们不能超过65535字节。 |
[MQTT-4.7.3-4] | 匹配订阅时,服务端不能对主题名或主题过滤器执行任何规范化(normalization)处理,不能修改或替换任何未识别的字符。 |
[MQTT-4.8.0-1] | 除非另有说明,如果服务端或客户端遇到了协议违规的行为,它必须关闭传输这个协议违规控制报文的网络连接。 |
[MQTT-4.8.0-2] | 如果客户端或服务端处理入站控制报文时遇到了瞬时错误,它必须关闭传输那个控制报文的网络连接。 |
[MQTT-6.0.0-1] | MQTT控制报文必须使用WebSocket二进制数据帧发送。如果收到任何其它类型的数据帧,接收者必须关闭网络连接。 |
[MQTT-6.0.0-2] | 单个WebSocket数据帧可以包含多个或者部分MQTT报文。接收者不能假设MQTT控制报文按WebSocket帧边界对齐。 |
[MQTT-6.0.0-3] | 客户端必须将字符串 mqtt 包含在它提供的WebSocket子协议列表里。 |
[MQTT-6.0.0-4] | 服务端选择和返回的WebSocket子协议名必须是 mqtt |
[MQTT-7.0.0-1] | MQTT实现可以同时是MQTT客户端和MQTT服务端。接受入站连接和建立到其它服务端的出站连接的服务端必须同时符合MQTT客户端和MQTT服务端的要求。 |
[MQTT-7.0.0-2] | 为了与任何其它的一致性实现交互操作,一致性实现不能要求使用在本规范之外定义的任何扩展。 |
[MQTT-7.1.1-1] | 满足一致性要求的服务端必须支持使用一个或多个底层传输协议,只要它提供有序的、可靠的、双向字节流(从客户端到服务端和从服务端到客户端) |
[MQTT-7.1.2-1] | 满足一致性要求的客户端必须支持使用一个或多个底层传输协议,只要它提供有序的、可靠的、双向字节流(从客户端到服务端和从服务端到客户端) |
8.3、附录C.MQTT v5.0 中的新功能摘要(非规范)
以下是MQTT v5.0增加的新功能:
- 会话过期(Session expiry):将Clean Session标志分成Clean Start标志和Session Expiry间隔,前者表示会话应该在不使用现有会话的情况下开始,后者表示断开连接后保留会话的时间。会话到期时间间隔可以在断开连接时修改。在MQTT v3.1.1中,将Clean Start设置为1,Session Expiry Interval设置为0,相当于将Clean Session设置为1。
- 消息过期:允许在消息发布时设置过期时间间隔。
- 所有ACK上的原因码:改变所有的响应数据包以包含一个原因码。这包括 CONNACK、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBACK、UNSUBACK、DISCONNECT 和 AUTH。这允许调用者确定请求的功能是否成功。
- 所有ACK上的原因字符串:改变大多数有原因码的数据包,也允许有一个可选的原因字符串。这是为确定问题而设计的,并不打算由接收方来解析。
- 服务器断开连接:允许服务器发送DISCONNECT,以表明连接被关闭的原因。
- 有效载荷格式和内容类型:允许在发布消息时指定有效载荷格式(二进制、文本)和MIME风格的内容类型。这些将被转发给消息的接收者。
- 请求/响应:在MQTT中正式确定请求/响应模式,并提供响应主题和相关数据属性,以允许响应消息被路由回请求的发布者。同时,增加客户端从服务器获取关于如何构建响应主题的配置信息的能力。
- 共享订阅:增加对共享订阅的支持,允许一个订阅的负载平衡消费者。
- 订阅ID:允许在SUBSCRIBE上指定一个数字的订阅标识符,并在消息传递时返回。这允许客户端确定哪个或哪些订阅导致消息被传递。
- 主题别名:通过允许将主题名称缩写为一个小的整数,减少MQTT数据包的开销大小。客户端和服务器独立地指定他们允许多少个主题别名。
- 流量控制:允许客户端和服务器独立地指定他们允许的未完成的可靠消息(QoS>0)的数量。发送者暂停发送此类消息,以保持在此配额以下。这被用来限制可靠消息的速率,并限制一次有多少消息在飞行。
- 用户属性:为大多数数据包添加用户属性。PUBLISH上的用户属性包括在消息中,并由客户端应用程序定义。PUBLISH上的用户属性和Will属性由服务器转发给消息的接收者。CONNECT、SUBSCRIBE和UNSUBSCRIBE数据包上的用户属性由服务器实现定义。CONNACK PUBACK、PUBREC、PUBREL、PUBCOMP、SUBACK、UNSUBACK和AUTH数据包上的用户属性由发送者定义,并且对发送者的实现是唯一的。用户属性的含义不是由MQTT定义的。
- 最大数据包大小:允许客户端和服务器独立指定他们支持的最大数据包大小。会话伙伴发送更大的数据包是一个错误。
- 可选的服务器功能可用性:定义一组服务器不允许的功能,并为服务器提供一种机制,以便向客户端说明这一点。可以用这种方式指定的功能有。最大QoS、保留可用、通配符订阅可用、订阅标识符可用和共享订阅可用。客户端使用服务器已声明不可用的功能是一个错误。在MQTT的早期版本中,服务器有可能通过声明客户端未被授权使用某项功能而不实施该功能。该功能允许声明这种可选行为,并在客户端使用这些功能之一时添加特定的原因代码。
- 增强认证:提供一个机制来启用挑战/响应式认证,包括相互认证。如果客户端和服务器都支持,这允许使用SASL风格的认证,并包括客户端在连接中重新认证的能力。
- 订阅选项:提供订阅选项,主要定义为允许消息桥接应用。这些选项包括不发送来自本客户端的消息(noLocal),以及处理订阅时保留的消息的选项。
- 遗嘱延迟:增加了在连接结束和发送遗嘱信息之间指定一个延迟的能力。这样做的目的是,如果与会话的连接被重新建立,那么遗嘱信息就不会被发送。这允许连接的短暂中断而不通知其他人。
- 服务器保持连接:允许服务器指定它希望客户使用的值作为保持连接。这允许服务器设置一个最大允许的保持连接,并让客户端遵守它。
- 分配的客户端ID:在客户端ID是由服务器分配的情况下,返回分配的客户端ID。这也解除了服务器分配的ClientID只能用于Clean Session=1连接的限制。
- 服务器引用:允许服务器指定在CONNACK或DISCONNECT时使用的另一个服务器。这可以作为一个重定向或做配置使用。