标签:
这份文档将分成以下七个部分来组织:
l 简介
l MQTT 控制包的格式
l MQTT 控制包
l 操作行为
l 安全
l 使用 WebSocket 作为网络传输
l 目标的一致性
网络连接
为MQTT 传输协议提供底层构建:
l 它连接着客户端和服务端
l 它提供双工发送有序、无损、字节流的能力
详情请移步 4.2
一个使用 MQTT 的程序或设备。一个客户端总是建立一个网络连接到服务端。它可以:
l 发布其他客户端可能感兴趣的应用消息
l 只接受订阅的应用消息
l 退订放弃接收应用消息
l 向服务器发起断开连接
一个介于客户端之间的信息中转器,连接客户端之间的订阅。一个服务端:
l 接受来自客户端的网络连接
l 接受由客户端发布的应用消息
l 处理来自客户端订阅和退订请求
l 转发应用消息到匹配的客户端
一个订阅由一个主题过虑器和一个最大的QoS (服务质量)组成。一个订阅只能关联一个会话。
一个会话可以包含多个订阅。一个会话内的所有订阅必须有唯一的主题过滤器。
作为应用信息的标志,匹配服务端上的订阅。服务端发送一个应用消息的副本到匹配的客户端。
一个包含在一个订阅中的表达式,去匹配感兴趣一个或者多个主题。一个主题过滤器可以包含通配符。
客户端和服务端之间的一个有状态的交互信息体。一些会话只活跃在一个网络连接,有一些可以跨越多个连续的网络连接,在客户端和服务端之间。
一个包的信息是通过网络连接来承载。在MQTT 的规范文档里定义了14种不同类型的控制包,其中一个(PUBLISH 包)被用来发送应用消息。
在一个字节里使用7到0表示。7是最高的有效位,0则是最低的有效位。
整形数据值是使用大端的编码16位表示:既高位字节在低位字节的前面。这意味着一个16位的消息在网络传输时,最高有效字节(MSB)会被先发送,然后是最低有效字节(LSB)。
在控制包里的文本字段会使用UTF-8进行编码。UTF-8是一种基于 Unicode 字符的高效编码,在基于文本的通信中,优于基于ASCII 字符的编码。
在每个待编码的字符串前面追加两个字节作为它编码后的长度,如下下图所示。因此,单个编码的字段编码长度是有限制的,最大可达65535 字节。
字节 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字节1 |
字符串长度 MSB |
|||||||
字节 2 |
字符串长度 LSB |
|||||||
字节 3 … |
UTF-8编码的字符数据(如果长度 > 0) |
UTF-8 编码的字符数据一定是符合Unicode 规范文档和RFC 定义的格式。尤其不能包含 U+D800 – U+DFFF之间的码值。如果服务端或客户端接受到一个含有不规范的UTF-8 编码的数据包,它会关闭网络连接[MQTT-1.5.3-1]。
一个UTF-8 编码的字符串一定不能包含一个null(U+0000)编码的字符。如果一个接受者(客户端/服务端) 接收一个包含U+0000 的控制包,它将会关闭网络连接[MQTT-1.5.3-2]。
数据里不建议包含下面列出的Unicode 编码。如果一个接收者(客户端/服务端) 接收到一个包含列表中的任何一个,它可能会关闭网络连接。
l U+0001 – U+001F 控制字符
l U+007F – U+009F 控制字符
在Unicode 编码规范里,有一些编码是非字符(如:U+0FFFF)
一个UTF-8 编码的序列 0xEF、0xBB、0xBF 总是会被解码成 U+FEFF(“零宽度非换行空格”),当它出现在一个字符串中时,数据包接收者不能跳过或过滤掉它[MQTT-1.5.3-3]。
例如,字符串“A□”是一个大写的拉丁字母“A”后跟着一个U+2A6D4 码值(它代表一个CJK象形文字扩展字符“B”) 将会被编译成如下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字节 1 |
字符串长度MSB (0x00) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
字节2 |
字符串长度 LSB(0x05) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
字节3 |
‘A’ (0x41) |
|||||||
|
0 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
字节4 |
(0xF0) |
|||||||
|
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
字节5 |
(0xAA) |
|||||||
|
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
字节6 |
(0x9B) |
|||||||
|
1 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
字节7 |
(0x94) |
|||||||
|
1 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
在这份文档里使用黄色突显统一声明,每一声明都会分配一个[MQTT-x.x.x-y]格式的引用。
MQTT 协议通过交换一些列的 MQTT 控制包进行工作。在这个章节里将带你认识这些包的格式。
一个 MQTT 控制包有下面的三个部分组成,它总是以下面表格所示的顺序进行排列。
固定头,所有的 MQTT 控制包都有 |
可变头,一些 MQTT 控制包才有 |
负载,一些 MQTT 控制包才有 |
每一个 MQTT 控制包都包含一个固定头。格式如下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字节 1 |
MQTT 控制包类型 |
特定的标志位 |
||||||
字节 2…. |
剩余的长度 |
位置:字节 1 [7-4]位
用4位的无符号值表示,值如下所示:
名字 |
值 |
流转方向 |
描述 |
保留 |
0 |
禁止 |
保留 |
CONNECT |
1 |
客户端到服务端 |
客户端请求连接到服务端 |
CONNACK |
2 |
服务端到客户端 |
连接确认 |
PUBLISH |
3 |
客户端到服务单 或 服务端到客户端 |
发布消息 |
PUBACK |
4 |
客户端到服务单 或 服务端到客户端 |
发布确认 |
PUBREC |
5 |
客户端到服务单 或 服务端到客户端 |
发布收到(有保证的交付第一部分)received |
PUBREL |
6 |
客户端到服务单 或 服务端到客户端 |
发布释放(有保证的交付第二部分)release |
PUBCOMP |
7 |
客户端到服务单 或 服务端到客户端 |
发布完成(有保证的交付第三部分)complete |
SUBSCRIBE |
8 |
客户端到服务端 |
客户端订阅请求 |
SUBACK |
9 |
服务端到客户端 |
订阅确认 |
UNSUBSCRIBE |
10 |
客户端到服务端 |
客户端退订请求 |
UNSUBACK |
11 |
服务端到客户端 |
退订确认 |
PINGREQ |
12 |
客户端到服务端 |
ping请求 |
PINGRESP |
13 |
服务端到客户端 |
ping响应 |
DISCONNECT |
14 |
客户端到服务端 |
客户端断开 |
Reserved |
15 |
禁止 |
保留 |
在固定头里的第一个字节里剩余的位[3-0],包含着每个不同类型的MQTT 控制包的对应的标志位值,正如下列出表格所示。在下列表中,当一个标志位被标记为“保留”时,这表明它留作将来使用,所以一定不能为这些位设置值[MQTT-2.2.2-1]。如果接收者接收到非法的标记为值,它会主动关闭网络连接[MQTT-2.2.2-2]。想了关于处理此错误的更多细节,请移步到4.8小节。
控制包 |
固定头标志 |
3 |
2 |
1 |
0 |
CONNECT |
保留 |
0 |
0 |
0 |
0 |
CONNACK |
保留 |
0 |
0 |
0 |
0 |
PUBLISH |
MQTT3.1.1中使用 |
DUP1 |
QoS2 |
QoS1 |
RETAIN3 |
PUBACK |
保留 |
0 |
0 |
0 |
0 |
PUBREC |
保留 |
0 |
0 |
0 |
0 |
PUBREL |
保留 |
0 |
0 |
1 |
0 |
PUBCOMP |
保留 |
0 |
0 |
0 |
0 |
SUBSCRIBE |
保留 |
0 |
0 |
1 |
0 |
SUBACK |
保留 |
0 |
0 |
0 |
0 |
UNSUBSCRIBE |
保留 |
0 |
0 |
1 |
0 |
UNSUBACK |
保留 |
0 |
0 |
0 |
0 |
PINGREQ |
保留 |
0 |
0 |
0 |
0 |
PINGRESP |
保留 |
0 |
0 |
0 |
0 |
DISCONNECT |
保留 |
0 |
0 |
0 |
0 |
DUP1 = 重复分发一个“PUBLISH”控制包
QoS2 = “PUBLISH”服务的质量(QoS)
RETAIN3 =“PUBLISH”剩余标志
关于PUBLISH里的DUP,QOS,RETAIN标志位的更多细节,请查阅3.3.1 小节中关于它们的描述。
位置:从第二个字节开始
“剩余的长度”是指当前包剩余的字节数,包括了可变头和负载。但是不包含用于编码“剩余的长度”的字节。
剩余长度使用可变长度的编码方式进行编码,单个字节的值可达127。超过的值采用下面方式处理。每个字节最低7位用于数据的编码,最高有效位用于表示后续字节。因此,每个字节可以表示128个值和一个“延续位”。剩余长度字段最大可以用4个字节表示。
[作者注]
上面讲的定义过于抽象,很难理解启动的含义。这里举一个例子说明一下。例如1,这里有一个十进制的数字“64”,因为其值小于127,所以用一个字节就可以表示出来:
十进制:64
十六进制:0x40
二进制:0100 0000
例子2,当这个数字是321时,应该如何编码呢?在做之前,我们可以先拆分一下这个数字为65 + (2 * 128)。从上面可以看出,这个数字可以用两个字节进行编码。根据算法,可以得出第一个字节的值为193(65 + 128),第二个字节的值为2。
十进制:[193] [2]
十六进制:[0xC1] [0x02]
从上面的结果可以看到,第二个字节的值为(第1字节值 - 65) / 128。
非规范注释
这允许应用程序发送高达268435455(256MB) 的控制包。在信道中可以用0xFF,0xFF,0xFF,0x7F表示。下面列出剩余长度值和编码字节对应关系:
位码 |
从(From) |
到(TO) |
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) |
非规范注释
编译的值范围:[0-268435455]
某些类型的MQTT 控制包会包含一个可变头,介于固定头和负载之间。可变头的内容取决于包的类型。但是,可变头中的标识符字,在几种包类型里是段通用的。
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
很多类型控制包的可变头组件都包含2个字节的包标识符。这些控制包是 PUBLISH(QoS > 0), PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK。
SUBSCRIBE, UNSUBSCRIBE 和 PUBLISH(QoS > 0) 控制包必须包含一个非零的16位包标识符[MQTT-2.3.1-1]。客户端每次发送其类型中一个新包时,必须给它分配一个当前没有使用过的包标识符[MQTT-2.3.1-2]。如果客户端想重发个别控制包时,那么它必须使用和之前相同的包标识符。当客服端处理完对应的请求确认包后,这个包标识符就可以重用。例如,在PUBLISH,当QoS =1对应确认包为PUBACK,当QoS=2对应为PUBCOMP。至于SUBSCRIBE 或 UNSUBSCRIBE 对应为 SUBACK,UNSUBACK[MQTT-2.3.1-3]。在相同条件下,服务端发送一个QoS > 0 的 PUBLISH 时,上诉规则也成立[MQTT-2.3.1-4]。
如果一个 PUBLISH 包的QoS的值设置为0时,它一定不会包含一个包标识符[MQTT-2.3.1-5]。
一个 PUBACK, PUBREC, PUBREL 包中的包标识符一定要与源 PUBLISH 相同[MQTT-2.3.1-6]。同理,SUBACK 和 UNSUBACK 与之对应的 SUBSCRIBE 和 UNSUBSCRIBE 一致[MQTT-2.3.1-7]。
下面表格列出控制包包含包标识符:
类型 |
包标识符字段 |
CONNECT |
NO |
CONNACK |
NO |
PUBLISH |
YES(QoS > 0) |
PUBACK |
YES |
PUBREC |
YES |
PUBREL |
YES |
PUBCOMP |
YES |
SUBSCRIBE |
YES |
SUBACK |
|
UNSUBSCRIBE |
YES |
UNSUBACK |
YES |
PINGREQ |
NO |
PINGRESP |
NO |
DISCONNECT |
NO |
客户端和服务端可以独立分配自己的包标识符。因此,它们可以使用相同的包标识符参与并发的信息通信。
非规范注释
一个客户端发送一个PUBLSH 包含0x1234 包标识符给服务端,它在收到服务器回复的一个包含相同标识符但是不一样的PUBLISH 包之前,会先收到一个 PUBACK。
Client Server
PUBLISH Packet Identifier=0x1234---à
?--PUBLISH Packet Identifier=0x1234
PUBACK Packet Identifier=0x1234---à
?--PUBACK Packet Identifier=0x123
一些 MQTT 控制包会包含一个负载作为最后一部分,在第3章会有其更多的细节。在PUBLISH 包中,它代表着一个应用信息。如下表格列出不同类型的控制包的负载:
控制包 |
负载 |
CONNECT |
Required(必须) |
CONNACK |
None(没有) |
PUBLISH |
Option(可选) |
PUBACK |
None(没有) |
PUBREC |
None(没有) |
PUBREL |
None(没有) |
PUBCOMP |
None(没有) |
SUBSCRIBE |
Required(必须) |
SUBACK |
Required(必须) |
UNSUBSCRIBE |
Required(必须) |
UNSUBACK |
None(没有) |
PINGREQ |
None(没有) |
PINGRESP |
None(没有) |
DISCONNECT |
None(没有) |
当一个客户端到服务端之间的网络连接建立完成后,客户端向服务端发送的第一个包必须是 CONNECT[MQTT-3.1.0-1]。
一个客户端在整个网络连接生命周期只能够发送一次 CONNECT 包。服务端必须处理从客户端第二次发过的违规 CONNECT 包,并断开与客户端的连接[MQTT-3.1.0-2]。查看4.8小节了解处理此错误的更多信息。
负载可以包含一个或多个编码字段。它们为客户端指定一个唯一的标识符,一个Will topic,Will message,用户名和密码。但是,客户端标识符是可选的,它的出现以否有可变头中的标志位设置有关。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|||||
字节 1 |
MQTT 控制包类型(1) |
保留 |
|||||||||||
|
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
|||||
字节 2…. |
剩余的长度 |
剩余长度字段
剩余长度等于可变头长度(10字节)加上负载的长度。它的编码方式请参考2.2.3小节。
CONNECT 包的可变头有四部分组成:协议名称、协议级别、连接标记和Keep Alive(注意顺序)。
协议名称是一个用 UTF-8 编码的字符串,代表协议名称“MQTT”。这个字符串,它的偏移量和长度不会随着 MQTTT 规范版本的变迁而改变。
如果协议名称不正确,服务端可能会会关闭连接,或者按照其他的方式继续处理 CONNECT 包。在稍后的例子里,服务端采用是第一种方案,不会继续处理 CONNECT 包[MQTT-3.1.2-1]。
非规范的注释
包检测器,如防火墙,会使用协议名称来识别 MQTT 信息流。
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
协议级别 |
|||||||||
byte 7 |
级别(4) |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
客户端使用8位无符号值表示协议的修订级别。3.1.1版本的协议级别用4(0x04)来表示。如果从客户端发出的 CONNECT 包中包含一个服务端不支持的协议级别,服务端会向客户端返回包含 0x01(不接受的协议级别)的CONNACK 包,并关闭连接[MQTT-3.1.2-2]。
连接标志字节中包含了用于设置 MQTT 连接行为的若干参数。它决定这些字段否是可以在负载中出现。
连接标志位列表:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
用户名 |
密码 |
Will Retain |
Will QoS |
Will Flag |
Clean Session |
保留 |
|
byte 8 |
× |
× |
× |
× |
× |
× |
× |
0 |
服务端必须要校验 CONNECT 控制包里的保留位值是否为 0,如果不为0,关闭连接[MQTT-3.1.2-3]。
位置:连接标志字节的第1位
这个标志位指定处理会话状态(session state)的方式.
客户端和服务端可以保存会话的状态,保证消息传输的可靠性(断线重连期间,消息不丢失)。这个标志用来控制会话的生命周期。
如果 Clean Session 被设置为0,服务端可以基于会话的状态(基于客户端的标识符)恢复之前与客户端之间的通信会话。如果没有会话和客户标识符关联,服务端必须创建一个新的会话与之关联。当客户端和服务端断开连接后,双方要保存会话[MQTT-3.1.2-4]。当一个Clean Session位置为0的会话断连后,服务端必须继续保存客户关注过且QoS值为1和2的消息,这些都是会话状态的一部分[MQTT-3.1.2-5]。
它也可以使用相同的策略保存 QoS值为0的消息。
如果 Clean Session 被设置为1,客户端和服务端将丢弃之前的会话并创建信息的会话。这个会话的生命周期等同这个网络连接。状态数据将不会与后续会话共享[MQTT-3.1.2-6]。
客户端的会话状态包含:
l 从服务端接收到,但尚未确认的 QoS为1和2消息
服务端的会话状态包含:
l 一个会话实例,即使其状态为空
l 客户端的订阅
l 发送到客户端,但尚未确认的QoS为1和2消息
l 从客户端接收到,但尚未确认的 QoS为1和2消息
l 可选,待发送到客户端 QoS为0的消息
在服务端,信息的保存不属于会话状态的组成部分,它们不会因为会话的实效而被删除[MQTT-3.1.2.7]。
查看4.1 小节获取更多关于存储状态的限制和细节。
当 clean session 被设置为1时,客户端和服务度不需要处理的会话状态的删除。
非规范注释
在发生故障时,确保状态的一致性,这样,客户端可以复制它的状态,设置clean session 的值为1去连接服务端,直到它能连接成功为止。
非规范注释
一般,一个客户端会一直使用相同的 clean session(1/0) 设置去连接,很少会交替的使用。但是,如何选择,这个有你具体的应用需求决定。但你需要知道的是,当一个客户端使用了clean session 设置为1将会丢失旧的的应用消息(断连期间),并且在每次重连后需要重新订阅需要关注的主题。反之,当它设置为0时,不会丢失QoS为1和2的消息(断连期间)。因此,假如你对消息连续性要求非常严格,可以使用 QoS为1或2,clean session 为0的设置。
非规范注释
当一个客户端通过clean session为0连接时,在断连后,服务端会维护它的MQTT会话状态,当在某个时间点它们再次重连时,建议也是使用相同的 clean session设置。假如,确实不再需要该会话时,可以在最后一次会话中使用 clean session为1进行连接,且在使用完后关闭它。
位置:连接标志字节中的第2位
如果 will标志被设置为1,且连接请求被接受时,服务端会保存一个与网络连接关联的will 消息。当这个连接被关闭时,服务端一定会将它发布出去,除非再次之前服务端接收到一个 DISCONNECT 包(会触发删除will消息)[MQTT-3.1.2-8]。
在下列场景会触发will 消息发送,但不限于:
l I/O 错误或服务端检测网络连接失败
l 客户端在 keep alive 周期内无法正常通信
l 没有发送 DISCONNECT 的客户端关闭操作
l 协议错误导致的服务端关闭连接
如果 will 标记设置为1,服务端将会使用连接标记字节中的 will QoS 和 will retain,且will 主题(topic) 和 will 消息必须在负载中出现[MQTT-3.1.2-9]。
服务端一旦发布完成(will 消息)或接收到一个 DISCONNECT 包时,will 消息必将会被从会话状态中移除[MQTT-3.1.2-10]。
如果 will 标记设置为0时,连接标记字节中的will QoS 和 will Retain 字段必须设置为0,且负载中不能出现will 主题(topic) 和 will 消息[MQTT-3.1.2-11。
如果will 标记设置0,当网络连接结束时,服务度不会发布一个will 消息[MQTT-3.1.2-12]。
服务端应当尽快的发布will 消息。但是,当服务器关闭或故障时,will 消息发布可能会被推迟到随后服务器重启之后。
位置:连接标记字节中第3和 4位
这两个位指定将要发布 will 消息的QoS级别。
如果 will 标记设置为0,那么 will QoS 必须设置为0(0x00)[MQTT-3.1.2-13]。
如果 will 标记设置为1,will Qos 可以设置为0(0x000)、1(0x01)、2(0x02)。但是它不能设置为3(0x03)[MQTT-3.1.2-14]。
位置:连接标记字节中第5位
这个位控制,当will 消息被发布时,是否保存will 消息。
如果will标记设置为0,那么will Retain 必须设置为0[MQTT-3.1.2-15]。
如果will 标记设置为1:
l 如果will Retain设置为0,服务端必须发布一个非存储的will消息[MQTT-3.1.2-16]。
l 如果will Retain设置为1,服务端必须发布一个可存储的will消息[MQTT-3.1.2-17]。
位置:连接标记字节中第6位
如果密码标记设置为0,密码不允许出现在负载里[MQTT-3.1.2-20]。
如果密码标记设置为1,密码必须出现在负载里[MQTT-3.1.2-21]。
如果用户名标记设置为0,密码标记必须设置为0[MQTT-3.1.2-22]。
位置:连接标记字节中第7位
如果用户名标记设置为0,用户名不允许出现在负载中[MQTT-3.1.2-18]。
如果用户名标记设置为1,用户名必须出现在负载中[MQTT-3.1.2-19]。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 9 |
Keep Alive MSB |
|||||||
byte 10 |
Keep Alive LSB |
Keep Alive 以秒为单位。使用一个16位的数值表示,它表示客户端最大空闲时间。它的职责是保证客户端发送控制包的空闲(间隔)时间不能超过 Keep Alive的值。当客户端长时间没有控制包需要发送时,必须发送一个 PINGREQ包[MQTT-3.1.2-23]。
客户端随时都可以发送 PINGREQ包,当服务端和网络连接正常的时,会回复一个 PINGRESP 包给客户端。
如果 Keep Alive 设置为一个非0值,且服务端在1.5倍的Keep Alive时间周期内没有收到任何一个从客户端发过来的控制包,它将会断开与客户端之间网络连接[MQTT-3.1.2-24]。
当客户端发送一个 PINGREQ包后,如果,其在一个有效的时间内没有收到服务端回复的PINGRESP包,它应该关闭和服务端之间的网络连接。
当Keep Alive设置为0时,相当于关闭keep alive(保活)机制。意思是,在这用场景下,关闭非活动的客户端连接就不是必须的了。
注意:服务端可以关闭一个非活动或已经没有响应的客户端,不管客户端设置的Keep Alive值。
非规范注释
Keep Alive值得设置根据具体的应用场景需要;一般设置几分钟。最大值是18个小时15分15秒。
|
描述 |
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(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 |
协议级别 |
|||||||||
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte7 |
级别(4) |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
连接标记字节 |
|||||||||
byte 8 |
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
用户名 |
密码 |
Will Retain |
Will QoS |
Will Flag |
Clean Session |
保留 |
|||
1 |
1 |
0 |
0 |
1 |
1 |
1 |
0 |
||
Keep Alive |
|||||||||
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 |
CONNECT 包的负载包含一个或多个带长度前缀的域(fields),具体包含什么由可变头中的标记位决定。如果存在,必须按照下列的顺序进行排列,will主题(topic),will消息,用户名,密码[MQTT-3.1.3-1]。
每个连接到服务端的客户端唯一标识(客户端Id)。客户端Id通常会被客户端和服务端用于关联会话状态[MQTT-3.1.3-2]。
客户端Id必须放在CONNECT包负载中的第一个域中[MQTT-3.1.3-3]。
客户端Id必须是UTF-8编码的字符串,关于UTF-8编码请参考文档中1.5.3小节的内容[MQTT-3.1.3-4]。
服务端允许客户端Id的UTF-8编码字节长度为1到23,且只能包含以下字符,“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”[MQTT-3.1.3-5]。
服务端可以允许客户度Id的编码长度超过23。服务端也允许客户端Id包含超出上面列出字符。
服务端可以允许客户设置一个0字节长度的客户Id,如果这样做了,服务端必须把这样情况当做特殊情况来处理,并且分配一个唯一的客户端Id给客户端。然后,如正常的CONNECT包(带有效的客户端Id)一样处理它[MQTT-3.1.3-6]。
如果客户端分配了一个0字节的客户端Id,客户端必须同时将clean session设置为1[MQTT-3.1.3-7]。
如果客户端分配了一个0字节的客户端Id,并将clean session设置为0,服务端必将回复一个包含0x02(非法的标识符)返回码的CONNACK包,并关闭网络连接[MQTT-3.1.3-8]。
如果服务端拒绝了客户端Id,它必须要返回一个包含0x02返回码的CONNACK的响应包[MQTT-3.1.3-9]。
非规范的注释
一个客户端实现要提供一个便利的方法生成一个随机的客户端Id。当clean session设置0时,推荐使用这种方法。
如果will标记设置为1,will主题将是负载中的下一个域(字段)。Will主题必须是一个UTF-8编码的字符串,关于UTF-8编码请参考文档中的1.5.3小节[MQTT-3.1.3-10]。
如果will标记设置为1 ,will消息将是负载中的下一个域(字段)。Will消息定义了应用消息,服务端将会如3.1.2.5小节中描述那样将它发布到will主题上。这个域有2个字节长度组成,紧跟是0或多个字节will消息的内容序列。跟随的数据的字节长度不含前面的那2个字节。当will消息被发布到will主题时,仅仅是will消息内容部分,而不包括开始的两个字节。
如果用户名标记设置为1,它将是负载的下一个域。用户名必须是一个UTF-8编码的字符串[MQTT-3.1.3-11]。它可以被服务端用作认证和授权。
如果密码标记设置为1,它将是负载的下一个域。密码域包含一个2字节前缀的长度字段和0到65535个字节的二进制数据(它不包含表示长度本身的两个字节)。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
Keep Alive MSB |
|||||||
byte 2 |
Keep Alive LSB |
|||||||
byte 3 … |
数据(length > 0) |
注意:在同一个TCP端口或其他的网络节点上,服务器可以支持多个协议(包括早期的协议版本)。如果服务器的协议版本是MQTT 3.1.1,它会做如下的校验:
1.如果在网络连接创建成功后,服务端在有效的时间间隔内没有接收到一个CONNECT 包,服务端应该关闭这个连接。
2.服务端必须校验CONNECT包是否符合规范(如3.1小节所诉)。如果不合法,关闭网络连接(不回复CONNACK 包)[MQTT-3.14-1]。
3.服务端可以检查CONNECT包中的内容以满足更多的约束,可以执行认证和授权检查。如果不通过,它应该返回一个合适的带非0返回码的CONNACK包,并关闭连接。更多的细节请查看3.2小节。
如果校验成功,服务端执行下面的步骤:
1.如果客户端Id关联的客户端已经连接到服务端,那么,服务端必须关闭此客户端[MQTT-3.14-2]。
2.服务端必须执行clean session的处理(如3.1.2.4所描述)[MQTT-3.1.4-3]。
3.服务端必须回复一个带非0返回值的CONNACK包对CONNECT包进行确认[MQTT-3.14-4]。
4.可以消息分发和keep alive 监听
客户端发送完一个CONNECT包之后,可以马上发送其他的控制包;客户端不需要等待服务端的CONNACK
如果服务端拒绝CONNECT,它不会处理CONNECT包之后发送的任何数据[MQTT-3.1.4-5]。
非规范注释
客户端通常会等待一个CONNACK包,然而,如果客户端利用这个空档继续向服务端发送控制包,这将简化客户端的实现,因为它不必关心连接的状态。在收到服务端返回拒绝CONNACK包之前,客户端可以接受任何的数据。
CONNACK包是服务端接收到客户端发送的CONNECT包后的响应。第一个从服务端发送客户端的包必须是CONNACK包[MQTT-3.2.0-1]。
如果客户端在合理的时间里,没有接收到从服务端返回的CONNACK包,客户端应该关闭这个连接。这个“合理”时间对不同的应用类型和通信的基础设施要不同的要求。
数据格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(2) |
保留 |
||||||
|
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩余长度字段
这是可变头的长度,CONNACK包的长度为2。
数据格:
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
连接确认标记 |
保留 |
SP1 |
|||||||
byte 1 |
|
0 |
0 |
0 |
0 |
1 |
0 |
0 |
× |
连接返回码 |
|||||||||
byte 2 |
|
× |
× |
× |
× |
× |
× |
× |
× |
“连接确认标记”用一个字节来表示。7-1位是保留位,必须设置为0。0位(SP1)是当前会话(session present)标记。
位置:连接确认标记字节第0位
如果服务端接受一个clean session为1的连接,服务端必须返回一个session present和返回码都为0的CONNACK确认包[MQTT-3.2.2-1]。
如果服务端接受了一个clean session为0的连接,session present的值取决于服务端是否已经保存与客户端Id关联的会话状态。如果服务端已经保存,它必须把CONNACK里的session present设置为1[MQTT-3.2.2-2]。反之,必须设置为0。除此之外,还要设置一个为0的返回码[MQTT-3.2.2-3]。
Session present 能让客户端和服务端建立一个状态共享的视图,不管这时是否有会话状态的存在。
一旦一个会话初始化完成后,客户端和会话状态期望服务端可以维护这个会话状态。如果从客户端接受到的session present不是服务端所预期的值时,客户端可以选择是否继续使用当前会话,也可以关闭它。客户端可以丢弃客户端和服务端的会话状态,先断开连接,使用clean session为1重连,之后再断开连接。
如果一个服务端发送一个了一个包含非0的返回码CONNACK包,那么必须将它的session present设置为0[MQTT-3.2.2-4]。
可变头的第2个字节。
连接返回码的值用一个无符号字节表示。如果服务端接收到一个无法处理的(不是包格式问题,某些业务上或是安全上的限制,如认证等)CONNECT包时,那么,它将会返回一个合适的、包含非0的连接返回码的CONNACK包。如果服务端发送完了上诉格式的包后,它必须关闭连接[MQTT-3.2.2-5]。下面列表列出了连接返回码的值:
值 |
响应返回码 |
描述 |
0 |
0x00 |
连接被接受 |
1 |
0x01 |
连接拒绝,服务端不支持的MQTT协议级别 |
2 |
0x02 |
连接拒绝,服务端不允许的客户端Id |
3 |
0x03 |
连接拒绝,网络连接已经建立,但是服务端不可用 |
4 |
0x04 |
连接拒绝,数据中的用户名和密码有缺陷 |
5 |
0x05 |
连接拒绝,客户端没有连接的权限 |
6-255 |
× |
保留 |
如果没有在上表列出的连接返回值默认是可以用的,除此之外,服务端将会关闭网络连接,且不会发送CONNAC返回包[MQTT-3.2.2-6]。
CONNACK包没有负载。
PUBLISH控制包用于客户端和服务端之间的应用消息的传输。
数据格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(3) |
DUP |
服务质量(QoS) |
保留 |
||||
|
0 |
0 |
1 |
1 |
× |
× |
× |
|
byte 2 |
剩余长度 |
|
位置:第1个字节中的第3位
如果DUP标记设置为0,它表示这个PUBLISH包(客户端或服务端发送的)是第一次发送。反之,如果设置为1,表示它可能是重复分发的了。
当服务端或客户端需要重新发送一个PUBLISH包时,需要将DUP设置为1[MQTT-3.3.1-1]。所有QoS值为0的消息,它的DUP必须设置0[MQTT-3.3.1-2]。
当一个输入的PUBLISH包经由服务端转发给关注者时,其原始的DUP值是不会被转发。输出和输入的PUBLISH包中DUP标记是分开设置的。这取决于这个包是否是重发[MQTT-3.3.1-3]。
非规范注释
当接收端收到一个DUP为1的控制包时,并不一定是重复包。
非规范注释
DUP标记只是指定控制包自身是否重复,而无法判断其所包含的应用消息的重复性。当客户端接收到一个QoS为1、DUP为0的PUBLISH包时,虽然它的包标识符是唯一的,但是它包含的应用消息可能之前已经发送过。有关包的标识符的描述,2.3.1小节有详尽的说明。
位置:第1个字节中的第1-2位
这个字段指定应用消息交付的保证级别。QoS级别如下:
QoS级别 |
2(位) |
1(位) |
描述 |
0 |
0 |
0 |
最多交付一次 |
1 |
0 |
1 |
最少交付一次 |
2 |
1 |
0 |
只交付一次 |
- |
1 |
1 |
保留(不能使用) |
一个PUBLISH包中的QoS位不能全部设置为1。不管是服务端还是客户接收到一个QoS位全部都是1的PUBLISH包时,它们都会关闭网络连接[MQTT-3.3.1-4]。
位置:第1个字节中的第0位
如果客户端向服务端发送一个RETAIN为1的PUBLISH包时,服务端会保存它的应用消息和QoS,以便将来分发给它的订阅者们(匹配它的主题名字的)[MQTT-3.3.1-5]。当一个新订阅成功建立后,匹配这个主题的、最后保存的消息会被发送到订阅者,如果没有,什么都不做[MQTT-3.3.1-6]。如果服务端接收到一个QoS为0、RETAIN为1的消息,它会丢弃之前为这个主题而保存的所有消息[MQTT-3.3.1-7]。关于存储状态的更多信息,请参考4.1小节。
当一个PUBLISH包发送到客户端时,它必须设置RETAIN为0,因为它匹配的是一个已建立的关注,不管这个标记怎样设置[MQTT-3.3.1-9]。
如果服务端接收到一个RETAIN为1、包含0字节负载的PUBLISH包时,它会“正常地”处理这个包,然后转发给订阅过的客户端。除此之外,会删除所有跟这个主题名字相同的已存消息。这样会导致后来订阅这个主题的客户端服务收到保存的信息[MQTT-3.3.1-10]。这里的“正常地”的意思是,只有已存的客户端才能接收到该消息。服务端不会保存一个0字节的、可保存消息[MQTT-3.3.1-11]。
如果一个由客户端发送到服务端的PUBLISH包的RETAIN标记为0,服务端不会保存这个消息,也不会删除或覆盖任何已经保存的信息[MQTT-3.3.1-12]。
非规范注释
保存消息在发布者不定期发送状态消息的场景下很有用。一个的订阅者可以接收到最新的状态值。
剩余长度字段
这个长度等于可变头的长度加上负载的长度。
可变头包含如下顺序的字段:主题名称、包标识符。
主题名称定义了被发送负载数据的通道信息。
主题名称必须放在PUBLISH包的可变头中第一个字段。它必须是一个UTf-8编码的字符串[MQTT-3.3.2-1]。
PUBLISH包中的主题名称一定不能包含通配字符[MQTT-3.3.2-2]。
PUBLISH包中的主题名称是服务端转发的定位器,只会发送给那些和主题名称匹配的订阅者们,关于如何处理主题的匹配话题,请参考4.7小节[MQTT-3.3.2-3]。
然而,由于服务端可以重写主题名称,这导致,它的主题名称可能跟原PUBLISH包的主题不一致。
包标识符只会出现在QoS为1或2的PUBLISH包中。2.3.1小节提供更多关于包标识符的信息。
值 |
|
主题名称 |
a/b |
包标识符 |
10 |
负载包含着具体的应用消息。其内容和数据格式有具体的应用指定。它的长度可以用固定头中的剩余长度减去可变头的长度得到。对于PUBLISH包来说,一个0字节的负载也是有效的。
PUBLISH包的接收者必须要要返回一个合适的响应。具体的响应内容取决于PUBLISH包中的QoS[MQTT-3.3.4.1]。
QoS级别 |
预期的响应 |
None |
|
QoS 1 |
PUBACK |
QoS 2 |
PUBREC |
客户端使用一个PUBLISH包发送一个应用消息给服务端,服务端会把这个消息分发给所有匹配的订阅的客户端。
当客户端使用包含通配符的主题过滤器来构造订阅时,它可能会让客户端的订阅重叠,导致发送的消息会匹配到多个主题过滤器。这时,服务端必须要根据匹配订阅的最大QoS去交付消息[MQTT-3.3.5-1]。此外,服务端鉴于订阅的每个QoS,可能还要为那些额外匹配的订阅交付消息副本。
对于接收PUBLISH的接收端,在不同QoS级别下会有不同的操作,具体细节会在4.3小节讲述。
如果服务端的实现不分发由客户端发送的PUBLISH包;它没有办法通知客户端。因此,要么根据QoS规则构建一个合适的确认包,要么只能关闭连接[MQTT-3.3.5-2]。
PUBACK包是QoS为1的PUBLISH包的响应。
数据结构:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(4) |
保留 |
||||||
|
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩余长度
这里只有可变头的长度,PUBACK包只有2个字节。
这里只包含了待确认的PUBLISH包的包标识符。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
PUBACK包没有负载。
会在4.3.2小节讲解。
PUBREC包是QoS为2 PUBISH包的响应。它是QoS 2协议交互的第2个包。
数据结构:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(5) |
保留 |
||||||
|
0 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩余长度
这里只有可变头的长度,PUBREC包只有2个字节。
这里只包含了待确认的PUBLISH包的包标识符。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
没有负载。
会在4.3.3小节讲解。
PUBREL包是QoS为2 PUBISH包的响应。它是QoS 2协议交互的第3个包。
数据结构:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(6) |
保留 |
||||||
|
0 |
1 |
1 |
0 |
0 |
0 |
1 |
0 |
byte 2 |
剩余长度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
PUBREL包的固定头第一个字节的3、2、1、0位是保留位,必须分别设置为0、0、1和0。除此之外的值,服务端都会关闭连接[MQTT-3.6.1-1]。
剩余长度
这里只有可变头的长度,PUBREC包只有2个字节。
这里只包含了待确认的PUBLISH包的包标识符。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
没有负载。
会在4.3.3小节讲解。
PUBCOMP包是QoS为2 PUBISH包的响应。它是QoS 2协议交互的第4个包。
数据结构:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(7) |
保留 |
||||||
|
0 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩余长度
这里只有可变头的长度,PUBREC包只有2个字节。
这里只包含了待确认的PUBLISH包的包标识符。
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
没有负载
会在4.3.3小节讲解。
SUBSCRIBE包是从客户端发送到服务端,它会触发服务端创建一个或多个订阅。每个订阅会登记客户端感兴趣的一个或多个主题。服务端在转发PUBLISH包到客户端的时候,就是匹配这些订阅中的主题进行的。SUBSCRIBE还需要指定(为每一个订阅)最大的QoS,以保证服务端可以将应用消息发送到客户端。
数据结构:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(8) |
保留 |
||||||
|
1 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
byte 2 |
剩余长度 |
SUBSCRIBE包固定头第1个字节的3、2、1、0位是保留位,必须分别设置为0、0、1和0。除此之外的值,服务端都会关闭连接[MQTT-3.8.1-1]。
剩余长度
可变头的长度加上负载的长度
可变头包含一个包标识符。更多关于包标识符的内容,请参考2.3.1小节。
|
描述 |
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 |
SUBSCRIBE包的负载包含了一个主题过滤器列表,用于指明客户端想要订阅的主题。SUBSCRIBE负载中的主题过滤器必须使用UTF-8编码的字符串[MQTT-3.8.3-1]。服务端应该支持包含通配符(4.7.1小节中定义)的主题过滤器。如果它不支持包含通配符的主题过滤器,它必须拒绝任何包含通配符的订阅请求[MQTT-3.8.3-2]。紧随其后的是一个被称为请求QoS(1字节表示)。基于这里的最大QoS级别,服务可以将应用信息发布给客户端。
SUBSCRIBE包的负载必须至少包含一个主题过滤器/QoS对。一个没有负载的SUBSCRIBE包是不符合协议规范的[MQTT-3.8.3-3]。查看4.8小节了解更多处理此错误的细节。
负载格式:
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
主题过滤器 |
||||||||
byte 1 |
Length MSB |
|||||||
byte 2 |
Length LSB |
|||||||
byte 3…N |
主题过滤器 |
|||||||
请求的QoS |
||||||||
|
保留 |
QoS |
||||||
byte N+1 |
0 |
0 |
0 |
0 |
0 |
0 |
× |
请求QoS字节的前6位在本版本协议里没有使用。它们留作将来使用。在保留位上设置任何的非0或QoS的取值不是0、1、2时,服务端必须视其为无效的SUBSCRIBE包,并关闭连接[MQTT-3.8.3-4]。
主题名称 |
“a/b” |
请求的QoS |
0x01 |
主题名称 |
“c/d” |
请求的QoS |
0x02 |
当服务端接收到一个来自客户端的SUBSCRIBE包时,服务端必须响应一个SUBACK包[MQTT-3.8.3-1]。且这个SUBACK的包标识符一定要和待应答的SUBSCRIBE的标识符相同[MQTT-3.8.4-2]。服务端允许在没有收到SUBACK包前,开始发送匹配这个订阅的PUBLISH包。
如果服务端接收到一个SUBSCRIBE包,其包含的主题过滤器跟一个已存在的订阅中相同时,它必须用一个新的订阅完全取代旧的订阅。虽然这个新订阅的主题过滤器和之前的一样,但是它的最大QoS有可能不相同。而且,和它匹配的保存消息必须全部重发,正在发送当中的消息可以不中断[MQTT-3.8.4-3]。如果没有和已存在的订阅中的主题过滤器相同,之后,会创建一个新的订阅并发送与之匹配的保存消息。
如果服务端接收到的SUBSCRIBE包中包含多个主题过滤器时,它必须像处理连续单个SUBSCRIBE包一样,只不过不是每个处理返回一个SUBACK包,而是把它的处理结果联合在一个单独的SUBACK包中返回[MQTT-3.8.4-4]。
服务端响应的SUBACK包中必须包含一个与没个主题过滤器/QoS对对应返回码。这个返回码要么表明订阅被允许,要就是订阅失败[MQTT-3.8.4-5]。服务端可以授予一个比订阅者请求更低QoS的级别。订阅的响应负载中的QoS最小值必须由原始消息的QoS,它的最大值必须由服务端授予。当源消息以QoS为1发布,且最大的QoS被授予为0时,可以允许服务端重复发送该消息的副本[MQTT-3.8.4-6]。
非规范性例子
如果一个订阅客的户端被授予一个特定的主题过滤器的最大QoS为1,那么,一个QoS为0的匹配应用信息会被以QoS为0发送到客户端。这个意味着客户端最多只能接收一次该消息。另一个场景,当一个QoS为2的匹配应用消息会先被服务端降级为QoS为1,然后才发送到客户端,这就意味着客户端可能会接收到重复的消息。
如果订阅的客户端被授予的最大QoS为0,之后,一个原来QoS为2的应用消息匹配到该订阅时,客户端可能会丢失掉这个消息,但是服务端绝对不会再次发送它。一个QoS为1的消息被匹配时,该客户端可能丢失该消息或重复收到它。
非规范性注释
订阅一个QoS为2的主题过滤器,相当于你告诉服务端,我只想接收匹配这个主题过滤器且以QoS发布的消息。这意味着,服务端有责任决定交付消息的QoS级别,除此之外,订阅者也需要服务端对QoS进行适当的降级,以适应订阅者的使用场景。
SUBACK包是服务端给客户端发送SUBSCRIBE包的响应,它是接收的凭证和服务端的处理结果。一个SUBACK包含一系列的返回码,它指定了每个订阅的被授予的最大QoS级别。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(9) |
保留 |
||||||
|
1 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度 |
剩余长度字段
可变头长度(2字节)加上负载的长度。
可变头包含来自待确认的SUBSCRIBE包的包标识符。其数据格式如下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
负载包含一系列的返回码。每个返回码和待确认的SUBSCRIBE包中的主题过滤器对应。它们的顺序必须和待确认SUBSCRIBE包中的主题过滤器列一致[MQTT-3.9.3-1]。
负载中的返回码使用一个字节表示,其格式如下:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
返回码 |
|||||||
byte 1 |
0 |
0 |
0 |
0 |
0 |
× |
× |
合法的返回码:
0x00–成功(最大QoS为 0)
0x01–成功(最大QoS为 1)
0x02–成功(最大QoS为 2)
0x80–失败
除了上面列出的返回码外,其他的值都是SUBACK的保留值,所以一定不能使用[MQTT-3.9.3-2]。
下面的一个例子简要的描述了一下负载的字节格式。
成功–最大QoS为 0 |
0 |
成功–最大QoS为 2 |
2 |
失败 |
128 |
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
成功–最大QoS为 0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
成功–最大QoS为 2 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
byte 3 |
失败 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
UNSUBSCRIBE是客户端发送到服务端取消订阅主题的包。
数据格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(10) |
保留 |
||||||
|
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度 |
UNSUBSCRIBE包固定头第1个字节的3、2、1、0位是保留位,必须分别设置为0、0、1和0。除此之外的值,服务端都会关闭连接[MQTT-3.10.1-1]。
剩余长度字段
可变头长度(2字节)加上负载的长度。
可变头包含一个包标识符。更多关于包标识符的细节请参考2.3.1小节。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
UNSUBSCRIBE的负载包含一个客户端想要退订的主题过滤器集。UNSUBSCRIBE包中的主题过滤器必须使用UTF-8编码的字符串[MQTT-3.10.3-1]。
UNSUBSCRIBE包中的负载必须至少包含一个主题过滤器。一个没有负载的UNSUBSCRIBE包定义是不符合协议规范的[MQTT-3.10.3-2]。查看4.8小节获取更多处理此错误的信息。
下面的例子简要的描述UNSUBSCRIBE的负载:
主题过滤器 |
“a/b” |
主题过滤器 |
“c/d” |
|
描述 |
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 |
1 |
0 |
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 7 |
Length MSB(0) |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 8 |
Length LSB(3) |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
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 |
UNSUBSCRIBE包中主题过滤器会与保存服务端的过滤器集进行字符比较。如果可以精确的匹配到过滤器,之后,它会删除这个订阅,反之,不做任何额外的处理[MQTT-3.10.4-1]。
如果一个服务器删除了一个订阅:
l 它必须停止交付新增的任何消息给该客户端[MQTT-3.10.4-2]
l 它必须完成交付退订发生之前的QoS为1和2的消息[MQTT-3.10.4-3]
l 它可以继续给客户端交付已经在消息缓存中的消息
服务端必须为UNSUBSCRIBE请求返回一个UNSUBACK响应包。并且这个UNSUBACK包的包标识符要和原来的UNSUBSCRIBE的一样[MQTT-3.10.4-4]。哪怕没有主题订阅被删除,服务端也必须返回一个UNSUBACK响应包[MQTT-3.10.4-5]。
如果服务端接收到的UNSUBSCRIBE包中包含的是多个主题过滤器,那么,它必须如处理连续单个主题过滤器那样处理它,只不过将其处理结果放到一个单独的UNSUBACK包中返回[MQTT-3.10.4-6]。
UNSUBACK是服务端发给客户端UNSUBSCRIBE请求的响应,它是服务端接收完成的凭证。
数据格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(11) |
保留 |
||||||
|
1 |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度 |
剩余长度字段
这跟可变头的长度一样。对于UNSUBACK包,它的取值是2。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字节 1 |
包标识符 MSB |
|||||||
字节 2 |
包标识符 LSB |
没有负载。
PINGREQ包被客户端发送服务端,用于:
1.当客户端没有任何有效的控制包要发送到服务端时,为了让服务端知道客户还“活着”(心跳机制)。
2.服务端用于响应确认请求,表明它还“活着”。
3.用于检测网络连接是否可用
这个包被用在“保活处理场景里”,更多的细节请参考3.1.2.10小节。
数据格式:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
byte 1 |
MQTT控制包类型(12) |
保留 |
||||||
|
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度(0) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
PINGREQ包没有可变头。
没有负载。
服务端必须为PINGREQ请求返回一个PINGRESP响应。它表明服务端还“活着”[MQTT-3.12.4-1]。
PINGRESP是服务端为PINGREQ请求返回一个PINGRESP响应。它表明服务端还“活着”。这个包被用在“保活处理场景里”,更多的细节请参考3.1.2.10小节。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(13) |
保留 |
||||||
|
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度(0) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
没有可变头。
没有负载。
DISCONNECT包是客户端发送到服务端的最后的包。它表明客户端将要断开连接。
数据格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包类型(14) |
保留 |
||||||
|
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩余长度(0) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
服务端必须校验断开连接包中的保留位,是否是设置为0,如果有非0的设置,关闭网络连接[MQTT-3.14.1-1]。
没有可变头。
没有负载。
当客户端发送一个DISCONNECT包后:
l 必须关闭网络连接[MQTT-3.14.4-1]。
l 该网络连接不能再发送任何控制包[MQTT-3.14.4-2]。
服务端接收到DISCONNECT后:
l 必须丢掉与这个连接关联的、且还没有发送的消息,如3.1.2.5小节描述那样[MQTT-3.14.4-3]。
为了提供有保证的服务质量(QoS),客户端和服务端的会话状态保存时很有必要的。客户端和服务端必须保存整个会话生命周期内的状态[MQTT-4.1.0-1]。一个会的生命周期至少要和它活跃的连接一样长[MQTT-4.1.0-2]。
在服务端,保存消息不是会话状态职责范围。服务端会保存这些消息,直到客户端删除它们。
非规范性注释
客户端和服务的存储容量肯定是有限,且可能会受到其他的管理的约束,比如像回复的最大保存时间。管理员的一些行为也可能会让会话状态丢失,这包括一些自定义条件的自动响应。这会导致整个会话失效。这些行为可能是资源的限制或者其他的操作原因引起的。因此,我们要谨慎的对客户端和服务端的存储能力进行合理的评估,确保他们足够。
非规范性注释
硬件或软件上的缺陷也可能会导致存储的会话状态丢失或损坏。
非规范性注释
管理员的一些操作,软硬件的缺陷都可能会导致一些正常操作的状态丢失。一个管理员操作可以是自定条件的自动响应。例如,服务端可以依据一些外部的条件,决定一个或个多消息是否能交付。
非规范性注释
MQTT的使用者应该去合理的评估MQTT客户端和服务端实现的存储能力,确保他们是否能够达到你们的要求。
例如,用户希望收集电表的度数时,他们可能需要使用QoS为1的消息,因为他们需要防止度数在网络传输过程丢失的可能性,然而,如果他们可以持续供电情况下,也可以把这些丢失风险低的数据存保存在内存中。
相反,一个停车场计费应用服务,为了避免发送消息的丢失,在消息发送之前,都会先把它强制写入非易失的记忆体(硬盘等)中。
MQTT协议需要一个基础的传输器,它为客户端到服务端或服务端到客户端提供有序的、无损的、基于字节流通信协议。
当客户端与clean session为0重连的时候,客户端和服务端需要重发任何没有确认的PUBLISH包(QoS > 0)和PUBREL(使用原包的包标识符)[MQTT-4.4.0-1]。
客户端实现协议流时,必须遵守下面的规则:
l 当它重发任何PUBLISH包,它的发送的顺序必须和原来的一致(QoS为1和2)
l 它必须按照接收到的PUBLISH包的相应顺序发送回复PUBACK(QoS 为1)
l 它必须按照接收到的PUBLISH包的相应顺序发送回复PUBREC(QoS 为2)
l 它必须按照接收到的PUBLISH包的相应顺序发送回复PUBREL(QoS 为2)
主题层级分隔符的使用,可以让主题名称拥有层级结构。如果它存在,他会将主题名字切分成多个主题层级。一个订阅的主题过滤器可以包含特定的通配符,这样你就可以使用一个订阅请求订阅多个主题。通配符可以被主题过滤器使用,但是,主题名称一定不能使用通配符[MQTT-4.7.1-1]。
正斜杠(“/” U+002F)被用作主题树里的每层之间的分隔符,并且为主题名称提供了继承的能力。主题层级分隔符可以出现在主题过滤器或主题名称的任何位置。
井号(“#” U+0023)是一个通配符,可以匹配主题内的任意层级。多级通配代表父级和任意的子级。它必须由“#”本身或跟随一个层级分隔符来指定。无论什么情况下,在主题过滤器里,它(“#”)必须是最后的字符[MQTT-4.7.1-2]。
非规范性注释
例如,如果一个客户端订阅了“sport/tennis/player1/#”,它可以收到发布到如下主题下的消息:
l “sport/tennis/player1”
l “sport/tennis/player1/ranking”
l “sport/tennis/player1/score/wimbledon”
非规范性注释
l “sport/#”也可以匹配单个“sport”,因为#包含父级。
l “#” 是合法的,将会接收每个应用消息
l “sport/tennis#”是不合法
l “sport/tennis/#/ranking”也是不合法
单级的通配符用加号(‘+’ U+002B)来表示。
单级通配符可以在主题过滤器中的任何层级上,包括第1和最后一层。如果在过滤器的某一层使用了它,那么它需要占据整层,既这层只能有它一个字符[MQTT-4.7.1-3]。在过滤器里,它可以在多个层级下使用,也可以结合多层级通配符来使用。
非规范性注释
例如,“sport/tennis/+”可以匹配”sport/tennis/player1”和“sport/tennis/player2”,但不能匹配“sport/tennis/player1/ranking”。同理,因为单级通配符只能匹配单个层级,因此,”sport/+”不能匹配“sport”,但是可以匹配“sport/”。
非规范性注释
l “+”是合法
l “+/tennis/#”是合法
l “sport+”是不合法
l “sport/+/player1”是合法
l “/finance”可以匹配“+/+”和“/+”,但是不匹配“+”
服务端一定不会匹配以通配符(#/+)开头的主题过滤器和以$号开头的主题名称[MQTT-4.7.2-1]。服务端应该阻止客户端使用类似这样的主题名称或主题过滤器和其他的客户端通信。服务端实现可以使用以$开头主题名称去做其他用途。
非规范性注释
l $SYS/ 作为主题名称前缀已被广泛使用,其包含服务端特定信息或控制APIs
l 应用不能使用以$开头的主题名称,无论基于什么用途
非规范性注释
l 订阅了“#”订阅者,将接收不到任何发布到以$开头的主题消息
l 订阅了“+/monitor/Clients”订阅者,将接收不到任何发布到“$SYS/monitor/Clients”的消息
l 订阅了“$SYS/#”订阅者,将接收不到发布到以“$SYS/”开头的主题消息
l 订阅了“$SYS/monitor/+”的订阅者,将会接收到发布到“$SYS/monitor/Client”消息
l 为了让客户端可以同时接收来自以$SYS开头和不以$开头的主题消息,它必须要订阅“#”和“$SYS/#”主题。
下面的规则适用于主题或主题过滤器:
l 所有的主题名称或主题过滤器的字符至少包含一个字符[MQTT-4.7.3-1]
l 主题名称和主题过滤器是区分大小写的
l 主题名称和主题过滤器可以包含空格字符
l “/”字符放在主题名称和主题过滤器的头部和尾部是不一样的
l 由单个“/”定义的主题名称或主题过滤器是合法
l 主题名称和主题过滤器一定不能不含null字符(Unicode U+0000)[MQTT-4.7.3-2]
l 主题名称和主题过滤器都要使用UTF-8编码的字符串定义,它们最大编码字节长度一定不能超过65535[MQTT-4.7.3-3]。关于UTF-8字符串编码,请参考1.5.3小节。
这里没有限制主题名称(或主题过滤器)的层级数,只对编码的总长度有限制。因为它们的编码字节数用2个字节表示,所以最大只能到65535。
当服务端执行订阅匹配的时候,它一定不会处理任何标准化(保留的),或使用不符合规范字符修改或替换的主题名称和主题过滤器[MQTT4.7.3-4]。
没有包含通配符的主题或主题过滤器都是采用同层级内字符比较进行匹配,如果完全匹配,那么匹配成功,反之,不成功。
非规范性注释
UTF-8编码规则让主题名称或主题过滤器的比较变得简单,你可以直接比较它们的UTF-8字节,也可以比较它们的原始字符。
非规范性注释
l “ACCOUNTS”和“Accounts”是两个不同的主题名称
l “Accounts payable”也是一个合法的主题名称
l “/finance”和“finance”不相同
一个应用消息会被服务端分转发到每个与其主题名称或主题过滤器匹配的客户端订阅中,主题资源可以由管理员预先创建好,也可以由服务端在收到第一次订阅或一个应用信息时,动态去创建。服务端也可以使用一个安全中间件,使用授权方式去限制客户端对主题资源的使用。
如果没有特别的说明,如果客户端或服务端接收到的控制包包含不符合协议规范的设置,客户端或服务端必须关闭这个连接[MQTT-4.8.0-1]。MQTT的客户端或服务端实现可能会遇到一些瞬态(如,内部的缓冲区满了)的错误,导致处理MQTT包失败。
如果客户端或服务端正在处理一个输入的控制包时,遇到了一个瞬态的错误,它必须关闭与这个包关联的连接[MQTT-4.8.0-2]。如果服务端检测到一个瞬态的错误,它不应该关闭连接或影响到与其它客户端交互。
标签:
原文地址:http://blog.csdn.net/ljf10010/article/details/51424506