规范中的关键字“MUST”,“MUSTNOT”,“REQUIRED”,“SHALL”,“SHALLNOT”,“SHOULD”,“SHOULDNOT”,“RECOMMENDED”,“MAY”,“OPTIONAL”的解释见IETFRFC2119[RFC2119]。
网络连接
由底层传输协议提供给MQTT使用的架构
示例见4.2节
应用消息
指通过MQTT在网络中传输的应用程序数据。当应用消息通过MQTT传输的时候会附加上质量服务(QoS)和话题名称。
客户端
指使用MQTT的程序或设备。客户端总是去连接服务端。它可以
服务端
扮演订阅或发布应用消息的客户端之间的中间人。一个服务端
订阅
一个订阅由一个话题过滤器和一个最大的QoS组成。一个订阅只能关联一个会话。一个会话可以包含多个订阅。每个订阅都有不同的话题过滤器。
话题名称
指附着于应用消息的标签,服务端用它来匹配订阅。服务端给每个匹配到的客户端发送一份应用信息的拷贝。
话题过滤器
包含在订阅里的一个表达式,来表示一个或多个感兴趣的话题。话题过滤器可以包含通配符。
会话
一个有状态的客户端和服务端的交互。有些会话的存续依赖于网络连接,而其他则可以跨越一个客户端和服务端之间的多个连续的网络连接。
MQTT控制包
通过网络连接发送的包含一定信息的数据包。MQTT规范定义了14个不同类型的控制包,其中一个(PUBLISH包)用来传输应用信息。
一个字节有8个位,从0到7。位7是最高有效位,位0是最低有效位。
整型数据值是16位大端序列:高阶字节在低阶字节之前。这意味着一个16位的字被放到网络上的时候,前面是最高有效位,后面是最低有效位。
控制包中的文本字段被编码为UTF-8字符串。UTF-8[RFC3629]是一种高效的Unicode[Unicode]编码方式,它优化了ASCII字符的编码,来支持基于文本的通信。
每个字符串都有一个两个字节的字段作为前缀,给出UTF-8编码字符串的长度。如下图所示Figure1.1StructureofUTF-8encodedstrings。因此这种UTF-8编码的字符的大小有一定的限制,编码后不能超过65535个字节。
除非特别说明,所有的UTF-8编码字符串的长度都可以是0到65535个字节。
Figure1.1StructureofUTF-8encodedstrings|bit|7|6|5|4|3|2|1|0|byte1|stringlengthMSB|byte2|stringlengthLSB|byte3|UTF-8EncodedCharacterData,iflength>0.UTF-8编码的字符串中的字符必须是Unicode规范里定义的并在RFC3629里重申的符合规范的UTF-8。尤其是这些数据不能包含介于U+D800到U+DFFF之间的编码。如果服务端或客户端收到了控制包包含不合规的UTF编码,就必须关闭网络连接[MQTT-1.5.3-1]。
UTF-8编码字符串不能包含空字符U+0000。如果收到控制包包含U+0000,服务端或客户端必须关闭网络连接[MQTT-1.5.3-2]。
数据不应该包含如下编码。如果收到控制包包含下列任一代码,应该关闭网络连接。
U+0001..U+001F控制字符U+007F..U+009F控制字符Unicode规范定义的非字符代码(例如U+0FFFF)
UTF-8编码序列0xEF0xBB0xBF总是被解读为U+FEFF(“零宽度不中断空格”)不论它是否出现在一个字符串中,而且接收方不能跳过或忽略[MQTT-1.5.3-3]。
例如,字符串A是一个LATINCAPITAL字符A,后面是U+2A6D4(代表一个CJKIDEOGRAPHEXTENSIONB字符),编码如下
Figure1.2UTF-8encodedstringnonormativeexample|bit|7|6|5|4|3|2|1|0|byte1|StringLengthMSB(0x00)||0|0|0|0|0|0|0|0|byte2|StringLengthLSB(0x05)||0|0|0|0|0|1|0|1|byte3|'A'(0x41)||0|1|0|0|0|0|0|1|byte4|(0xF0)||1|1|1|1|0|0|0|0|byte5|(0xAA)||1|0|1|0|1|0|1|0|byte6|(0x9B)||1|0|0|1|1|0|1|1|byte7|(0x94)||1|0|0|1|0|1|0|01.6编写惯例在本规范中黄色高亮的文本表示一致性描述。每个一致性描述都会被分配一个[MQTT-x.x.x-y]的引用。
MQTT通过交换一些预定义的MQTT控制包来工作。这一节描述这些包的格式。一个MQTT控制包包含三部分,按照下图的顺序Figure2.1-StructureofanMQTTControlPacket.
Figure2.1-StructureofanMQTTControlPacket|固定包头,存在于所有MQTT控制包|可变包头,存在于某些MQTT控制包|载荷,存在于某些MQTT控制包2.2固定包头每一个MQTT控制包都包含一个固定包头。Figure2.2-Fixedheaderformmat阐明了包头的格式。
Figure2.2-Fixedheaderformat|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype|FlagsspecifictoeachMQTTControlPackettype|byte2|RemainingLength2.2.1MQTT控制包类型位置:字节1,位7-4表现为4位无符号值,如下表Table2.1-Controlpackettypes.
Table2.1-Controlpackettypes|Name|Value|Directionofflow|Description|Reserved|0|Forbidden|Reserved|CONNECT|1|ClienttoServer||CONNACK|2|ServertoClient|Connectacknowledgment|PUBLISH|3|ClienttoServerorServertoClient|Publishmessage|PUBACK|4|ClienttoServerorServertoClient|Publishacknowledgment|PUBREC|5|ClienttoServerorServertoClient|Publishreceived(assureddeliverypart1)|PUBREL|6|ClienttoServerorServertoClient|Publishrelease(assureddeliverypart2)|PUBCOMP|7|ClienttoServerorServertoClient|Publishcomplete(assureddeliverypart3)|SUBSCRIBE|8|ClienttoServer|Clientsubscriberequest|SUBACK|9|ServertoClient|Subscribeacknowledgment|UNSUBSCRIBE|10|ClienttoServer|Unsubscriberequest|UNSUBACK|11|ServertoClient|Unsubscribeacknowledgment|PINGREQ|12|ClienttoServer|PINGrequest|PINGRESP|13|ServertoClient|PINGresponse|DISCONNECT|14|ClienttoServer|Clientisdisconnecting|Reserved|15|Forbidden|ReservedClienttoServerorServertoClient
固定包头字节1中剩下的位[3-0]包含了每个MQTT控制包类型的特殊标识,如下表Table2,2-FlagBits。表中被标识为“预留”的标识位也必须赋值[MQTT-2.2.2-1]。如果收到不可用的标识,接收方必须关闭网络连接。
Table2.2-FlagBits|ControlPackage|Fixedheaderflags|bit3|bit2|bit1|bit0|CONNECT|Reserved|0|0|0|0|CONNACK|Reserved|0|0|0|0|PUBLISH|UsedinMQTT3.1.1|DUP1|QoS2|QoS2|RETAIN3|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|0DUP2=重复发送PUBLISH控制包QoS2=PUBLISH质量服务RETAIN3=PUBLISH保留标识关于PUBLISH控制包的DUP,QoS,以及RETAIN标识,在3.3.1节有详细描述。
位置:从第二个字节开始
剩余长度是指当前包中的剩余字节,包括可变包头的数据以及载荷。剩余长度不包含用来编码剩余长度的字节。
剩余长度使用了一种可变长度的结构来编码,这种结构使用单一字节表示0-127的值。大于127的值如下处理。每个字节的低7位用来编码数据,最高位用来表示是否还有后续字节。因此每个字节可以编码128个值,再加上一个标识位。剩余长度最多可以用四个字节来表示。
非规范注释
例如十进制的数字64可以被编码成一个单独的字节,十进制为64,八进制为0x40。十进制数字321(=65+2×128)被编码为两个字节,低位在前。第一个字节是65+128=193。注意最高位的128表示后面至少还有一个字节。第二个字节是2。(翻译注:321=1100000100000010,第一个字节是“标识符后面还有一个字节”+65,第二个字节是“标识符后面没有字节了”+256)
这将允许应用发送最多256M大小的控制包。这个数字用16进制表示为:0xFF,0xFF,0xFF,0x7F。Table2.4展示了随着字节数的增多,剩余长度可表示的值。
Table2.4SizeofRemainingLengthfield|Digits|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)非规范注释
编码一个非负整数(X)为可变长度编码结构的算法如下:
doencodedByte=XMOD128X=XDIV128//iftherearemoredatatoencode,setthetopbitofthisbyteif(X>0)encodedByte=encodedByteOR128endif'output'encodedBytewhile(X>0)MOD是取模操作(C语言的%),DIV是整除操作(C语言的/),OR是按位或操作(C语言的|)。
解码RemainingLength的算法如下:
multiplier=1value=0doencodedByte='nextbytefromstream'value+=(encodedByteAND127)*multipliermultiplier*=128if(multiplier>128*128*128)throwError(MalformedRemainingLength)while((encodedByteAND128)!=0)AND是按位与操作(C语言的&)。
算法运行完之后,变量value就是剩余长度的值。
某些类型的MQTT控制包包含一个可变包头结构。位于固定包头和载荷之间。可变包头的内容取决于包的类型。可变包头中的包标识符字段在大多类型的包中比较常见。
Figure2.3-PacketIdentifierbytes|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB很多类型的控制包的可变包头结构都包含了2字节的唯一标识字段。这些控制包是PUBLISH(QoS>0),PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE,SUBACK,UNSUBSCRIBE,UNSUBACK。
SUBSCRIBE,UNSUBSCRIBE,PUBLISH(QoS>0的时候)控制包必须包含非零的唯一标识[MQTT-2.3.1-1]。每次客户端发送上述控制包的时候,必须分配一个未使用过的唯一标识[MQTT-2.3.1-2]。如果一个客户端重新发送一个特别的控制包,必须使用相同的唯一标识符。唯一标识会在客户端收到相应的确认包之后变为可用。例如PUBLIST在QoS1的时候对应PUBACK;在QoS2时对应PUBCOMP。对于SUBSCRIBE和UNSUBSCRIBE对应SUBACK和UNSUBACK[MQTT-2.3.1-3]。服务端发送QoS>0的PUBLISH时,上述内容同样适用。
QoS为0的PUBLISH包不允许包含唯一标识。
PUBACK,PUBREC,PUBREL包的唯一标识必须和对应的PUBLISH相同[MQTT-2.3.1-6]。同样的SUBACK和UNSUBACK的唯一标识必须与对应的SUBSCRIBE和UNSUBSCRIBE包相同[MQTT-2.3.1-7]。
控制包所需的标识符如下表所列,Table2.5-ControlPacketsthatcontainaPacketIdentifier。
Table2.5-ControlPacketsthatcontainaPacketIdentifier|ControlPacket|PacketIdentifierfield|CONNECT|NO|CONNACK|NO|PUBLISH|YES(IfQoS>0)|PUBACK|YES|PUBREC|YES|PUBREL|YES|PUBCOMP|YES|SUBSCRIBE|YES|SUBACK|YES|UNSUBSCRIBE|YES|UNSUBACK|YES|PINGREQ|NO|PINGRESP|NO|DISCONNECT|NO客户端和服务端各自独立分配唯一标识。因此,一对客户端和服务端交换数据的时候可以使用相同的唯一标识。
有这种可能,客户端发送一个PUBLISH包,唯一标识位0x1234,在收到相应的PUBACK之前,接着先收到了一个服务端发来的不同的PUBLISH包,唯一标识同样是0x1234。
ClientServerPUBLISHPacketIdentifier=0x1234-><-PUBLISHPacketIdentifier=0x1234PUBACKPacketIdentifier=0x1234-><-PUBACKPacketIdentifier=0x12342.4载荷一些MQTT控制包的最后一部分包含载荷,第三章会有详细描述。例如PUBLISH包相当于应用消息。Table2.6-ControlPacketsthatcontainaPayload显示了控制包对载荷的需求。
Table2.6-ControlPacketsthatcontainaPayload|ControlPacket|Payload|CONNECT|Required|CONNACK|None|PUBLISH|Optional|PUBACK|None|PUBREC|None|PUBREL|None|PUBCOMP|None|SUBSCRIBE|Required|SUBACK|Required|UNSUBSCRIBE|Required|UNSUBACK|None|PINGREQ|None|PINGRESP|None|DISCONNECT|None3MQTT控制包3.1CONNECT-客户端请求连接服务器客户端和服务端建立网络连接后,第一个从客户端发送给服务端的包必须是CONNECT包[MQTT-3.1.0-1]。
每个网络连接客户端只能发送一次CONNECT包。服务端必须把客户端发来的第二个CONNECT包当作违反协议处理,并断开与客户端的连接[MQTT-3.1.0-2]。错误处理详见4.8节。
载荷包含一个或多个编码字段。用来指定客户端的唯一标识,话题,信息,用户名和密码。除了客户端唯一标识,其他都是可选项,是否存在取决于可变包头里的标识。
Figure3.1-CONNECTPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(1)|Reserved||0|0|0|1|0|0|0|0|byte2…|RemainingLengthRemainingLength
剩余长度是指可变包头长度(10字节)加上载荷的长度。编码方式的描述见2.2.3节。
CONNECT包的可变包头由四个字段按照如下顺序构成:协议名字,协议等,连接标识,保持连接
Figure3.2-ProtocolNamebytes||Description|7|6|5|4|3|2|1|0|ProtocolName||byte1|LengthMSB(0)|0|0|0|0|0|0|0|0|byte2|LengthLSB(4)|0|0|0|0|0|1|0|0|byte3|‘M’|0|1|0|0|1|1|0|1|byte4|‘Q’|0|1|0|1|0|0|0|1|byte5|‘T’|0|1|0|1|0|1|0|0|byte6|‘T’|0|1|0|1|0|1|0|0协议名字是一个UTF-8编码的字符串“MQTT”,全大写。这个字符串的偏移量和长度不会在未来版本的MQTT规范中有所改变。
如果协议名字不正确,服务端可以选择断开连接,也可以选择基于其他规范继续保持连接。后一种情况,服务端就不能按照本规范处理CONNECT包了。
数据包过滤器,例如防火墙,可以用协议名来鉴别MQTT传输。
Figure3.3-ProtocolLevelbyte||Description|7|6|5|4|3|2|1|0|Protocol|Level|byte7|Level(4)|0|0|0|0|0|1|0|08位无符号值表示客户端的版本等级。3.1.1版本的协议等级是4(0x04)。如果协议等级不被服务端支持,服务端必须响应一个包含代码0x01(不接受的协议等级)CONNACK包,然后断开和客户端的连接[MQTT-3.1.2-2]。
连接标识包含了一些参数来指定MQTT的连接行为,它也能表示负载中的字段是否存在。
Figure3.4-ConnectFlagbits|Bit|7|6|5|4|3|2|1|0||UserNameFlag|PasswordFlag|WillRetain|WillQoS|WillFlag|CleanSession|Reserved|byte8|X|X|X|X|X|X|X|0服务端必须验证CONNECT控制包的预留字段是否为0,如果不为0断开与客户端的连接[MQTT-3.1.2-3].
位置:连接标识字节的bit1。
这个bit指明了会话状态的处理方式。
客户端和服务端可以存储会话状态,以便能够在一系列的网络连接中可靠的传递消息。这个bit用来控制会话状态的生命周期。
如果CleanSession被设置为1,客户端和服务端必须断开之前的会话启动一个新的会话。只要网络连接存在会话就存在。一个会话的状态数据一定不能被随后的会话复用[MQTT-3.1.2-6]。
客户端会话状态的构成:
服务端会话状态的构成:
在服务端,保留消息不属于会话状态,它们不必在会话结束的时候被删除。
状态的存储细节和限制详见4.1节。
当CleanSession被设置为1,客户端和服务端不需要自动处理状态的删除。
为了确保在故障的情况下状态一致,客户端应该将CleanSession设置为1重复尝试连接,直到连接成功。
通常,客户端应该一直使用CleanSession0或一直使用CleanSession1来建立连接,而不要切换来切换去。选择哪个由应用程序决定。使用CleanSession1的客户端不会收到旧的应用消息,而且每次连接都要重新订阅感兴趣的话题。客户端使用CleanSession0会收到上次短线之后的QoS1和QoS2的消息。因此,为了确保消息不会再断线时丢失,使用QoS1或QoS2并把CleanSession设为0。
当客户端使用CleanSession0建立了连接,在断开连接后,就要求服务端保存它的MQTT会话状态。如果客户端打算在晚些时候重连服务端,那么应该将CleanSession设置为0。当客户端认为没有进一步使用会话的必要了,它应该最后再连接一次服务器,将CleanSession设置为1,然后断开连接。
位置:连接标识的bit2
如果WillFlag被设置为1,这意味着,如果连接请求被接受,服务端必须存储一个WillMessage,并和网络连接关联起来。之后在网络连接断开的时候必须发布WillMessage,除非服务端收到DISCONNECT包删掉了WillMessage[MQTT-3.1.2-8]。
WillMessage会在某些情况下发布,包括但不限于:
如果WillFlag被设置为1,连接标识中的WillQoS和WillRetain字段将会被服务端用到,而且WillTopic和WillMessage字段必定会出现在载荷中[MQTT-3.1.2-9]。
WillMessage必须从服务端存储的会话状态中移除,一旦被发不过,或者服务端收到了客户端的DISCONNECT包[MQTT-3.1.2-10]。
如果WillFlag被设置为0,连接标识中的WillQoS和WillRetain字段必须设置为零,并且WillTopic和WillMessage字段不能够出现在载荷中[MQTT-3.1.2-11]。
如果WillFlag被设置为0,WillMessage将不会再网络连接结束的时候发布[MQTT-3.1.2-12]。
服务端应该尽快发布WillMessage。例如服务端关闭或故障,只能推迟WillMessage的发布,直到服务端重启。如果这种情况发生,就会出现从服务器故障到WillMessage被发布之间的延迟。
位置:连接标识的bit4和3
这两个bit表示发布WillMessage时使用QoS的等级。
如果WillFlag设置为0,那么WillQoS也必须设置为0[MQTT-3.1.2-13].
如果WillFlag设置为1,那么WillQoS的值可是是0(0x00),1(0x01),2(0x02)。一定不会是3(0x03)[MQTT-3.1.2-14]。
位置:连接标识的bit5。
这个bit表示WillMessage在发布之后是否需要保留。
如果WillFlag设置为0,那么WillRetain必须是0[MQTT-3.1.2-15]。
如果WillFlag设置为1:
位置:连接标识的bit7。
如果UserNameFlag设置为0,那么用户名不必出现在载荷中[MQTT-3.1.2-18]。
如果UserNameFlag设置为1,那么用户名必须出现在载荷中[MQTT-3.1.2-19]。
位置:连接标识的bit6。
如果PasswordFlag设置为0,那么密码不必出现在载荷中[MQTT-3.1.2-20]。
如果PasswordFlag设置为1,那么密码必须出现在载荷中[MQTT-3.1.2-21]。
如果UserNameFlag设置为0,那么PasswordFlag必须设置为0[MQTT-3.1.2-22]。
如果KeepAlive的值非0,而且服务端在一个半KeepAlive的周期内没有收到客户端的控制包,服务端必须作为网络故障断开网络连接[MQTT-3.1.2-24]。
KeepAlive的值为0,就关闭了维持的机制。这意味着,在这种情况下,服务端不会断开静默的客户端。
注意,服务端在任何时候都可以断开它认为不活跃或没有响应的客户端,而不需要遵照客户端提供的KeepAlive。
KeepAlive的实际值是由应用程序指定的;典型的是几分钟。最大值是18小时12分钟15秒。
Figure3.6-Variableheadernonnormativeexample||Description|7|6|5|4|3|2|1|0|ProtocolName|byte1|LengthMSB(0)|0|0|0|0|0|0|0|0|byte2|LengthLSB(4)|0|0|0|0|0|1|0|0|byte3|‘M’|0|1|0|0|1|1|0|1|byte4|‘Q’|0|1|0|1|0|0|0|1|byte5|‘T’|0|1|0|1|0|1|0|0|byte6|‘T’|0|1|0|1|0|1|0|0|ProtocolLevel|byte7|Level(4)|0|0|0|0|0|1|0|0|ConnectFlags|byte8|UserNameFlag(1)|1|1|0|0|1|1|1|0||PasswordFlag(1)||WillRetain(0)||WillQoS(01)||WillFlag(1)||CleanSession(1)||Reserved(0)|KeepAlive|byte9|KeepAliveMSB(0)|0|0|0|0|0|0|0|0|byte10|KeepAliveLSB(10)|0|0|0|0|1|0|1|03.1.3载荷CONNECT包的载荷可以包含一个或多个带有长度前缀的字段,这取决于可变包头的标识。这些字段,如果存在,必须按照这样的顺序,客户端唯一标识,WillTopic,WillMessage,UserName,Password[MQTT-3.1.3-1]。
客户端标识(ClientId)可以帮助服务端识别客户端。每一个客户端连接服务端都带有唯一的ClientId。客户端和服务端都必须用ClientId来标识他们所持有的相应的MQTT会话的状态[MQTT-3.1.3-2]。
客户端标识(ClientId)必须存在,而且必须是CONNECT包载荷的第一个字段[MQTT-3.1.3-3]。
ClientId必须是1.5.3节定义的UTF-8编码的字符串[MQTT-3.1.3-4]。
服务端必须能够接纳的ClientId是长度为1到23的UTF-8编码的字节,而且只包含字符“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”[MQTT-3.1.3-5]。
服务端也可以接纳大于23个字节编码的ClientId。服务端也可以允许ClientId包含上述字符之外的字符。
服务端可以允许客户端提供0字节长度的ClientId,但是如果这样,服务端必须把这也当作一个特例,作为客户端的唯一ClientId。然后就当作客户端已经提供了唯一ClientId,接着处理CONNECT包[MQTT-3.1.3-6]。
如果客户端提供了一个0字节的CLientId,客户端必须设置CleanSession为1[MQTT-3.1.3-7]。
如果客户端提供了一个0字节的ClientId,而且CleanSession设置为0,那么服务端必须响应一个CONNACK包,带有代码0x02(标识被拒绝),然后关闭网络连接[MQTT-3.1.3-8].
如果服务端拒绝了ClientId,必须返回一个CONNACK包,带有代码0x02(标识被拒绝),然后关闭网络连接[MQTT-3.1.3-9]。
客户端实现应该提供一个便利的方法生成随机ClientId。当CleanSession设置为0的时候,这种随机的方法应该尽量避免。
如果WillFlag设置为1,WillTopic是载荷的下一个字段。WillTopic必须是1.5.3节定义的UTF-8编码的字符串[MQTT-3.1.3-10]。
如果WillFlag设置为1,WillMessage是载荷的iayige字段。WillMessage定义了3.1.2.5节中描述的将要发布给WillTopic的应用消息。这个字段的前两个字节表示长度,后面是一段字节序列。长度代表字节序列的字节数,不包括长度本身的两个字节。
当WillMessage被发布给WillTopic的时候,只是包括字节序列那部分数据,不包含代表长度的头两个字节。
如果PasswordFlag设置为1,这将是载荷的下一个字段。Password字段包含0-65535字节的二进制数据,数据前面有两个字节来定义二进制数据占用的字节数(不包括这两个字节本身)。
Figure3.7-Passwordbytes|Bit|7|6|5|4|3|2|1|0|byte1|DatalengthMSB|byte2|DatalengthLSB|byte3….|Data,iflength>0.3.1.4响应注意,服务端可能在同一个TCP端口或其他网络终端会支持多种协议(包括本协议的早期版本)。如果服务端确认协议是MQTT3.1.1,那么它必须确认下列连接请求。
如果验证成功,服务端执行下列步骤。
客户端可以在发送CONNECT包之后立马发送其他控制包;客户端不需要等待CONNACK包。如果服务端拒绝了CONNECT,不要处理客服端在CONNECT包之后发送的任何数据[MQTT-3.1.4-5]。
客户端通常等待CONNACK包,然而,如果客户端能够灵活地在收到CONNACK包之前就发送控制包,将会简化客户端的实现,因为不需要关心连接状态。客户端要明白的是在没有收到CONNACK包之前发送的数据,如果服务端拒绝连接的话,将不会被处理。
CONNACK包是服务端发送的用来相应客户端CONNECT包的一种数据包。从服务端发往客户端的第一个包一定是CONNACK包[MQTT-3.2.0-1]。
固定包头的结构如下表所示Figure3.8-CONNACKPacketfixedheader。
Figure3.8-CONNACKPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPacketType(2)|Reserved||0|0|1|0|0|0|0|0|byte2|RemainingLength(2)||0|0|0|0|0|0|1|0RemainingLength
这个是可变包头的长度。对于CONNACK包来说这个值是2。
可变包头的格式如下表Figure3.9-CONNACKPacketvariableheader。
Figure3.9-CONNACKPacketvariableheader||Description|7|6|5|4|3|2|1|0|ConnectAcknowledgeFlags|Reserved|SP1|byte1||0|0|0|0|0|0|0|X|ConnectReturncode|byte2||X|X|X|X|X|X|X|X3.2.2.1连接确认标识字节1是“连接确认标识”。位7-1是保留位必须设置为0。
位0(SP1)是SessionPresent标识
位置:连接确认标识的位0。
如果服务端接受了一个CleanSession设置为1的连接,服务端必须将CONNACK包中的SessionPresent设置为0,并且CONNACK包的返回码也设置为0。
如果服务端接受了一个CleanSession设置为0的连接,SessionPresent的值取决于服务端是否已经存储了客户端Id对应的绘画状态。如果服务端已经存储了会话状态,CONNACK包中的SessionPresent必须设置为1[MQTT-3.2.2-2]。如果服务端没有存储会话状态,CONNACK包的SessionPresent必须设置为0。另外CONNACK包中的返回码必须设为0[MQTT-3.2.2-3]。
SessionPresent标识使得客户端能够建立连接,不论客户端和服务端在是否已经存储了会话状态上达成共识。
一旦会话的初始设置完成,存储会话状态的客户端会期望服务端也存储了会话状态。万一SessionPresent的值不符合预期,客户端可以选择是继续处理这个会话还是断开连接。客户端可以通过断开连接,把CleanSession设置为1重新连接,然后再断开连接,来决定客户端和服务端的会话状态。
可变包头的第二个字节。
无符号单字节连接返回码字段的值如下表所列Table3.1-ConnectReturncodevalues。如果服务端收到了一个格式良好的CONNECT包,但是服务端由于某种原因不能处理,那么服务端应该尝试发送一个带有非零返回码的CONNACK包。如果服务端发送了一个包含非零返回码的CONNACK包,必须关闭网络连接[MQTT-3.2.2-5]。
Table3.1-COnnectReturncodevalues|Value|ReturnCodeResponse|Description|0|0x00ConnectionAccepted|Connectionaccepted|1|0x01ConnectionRefused,unacceptableprotocolversion|TheServerdoesnotsupporttheleveloftheMQTTprotocolrequestedbytheClient|2|0x02ConnectionRefused,identifierrejected|TheClientidentifieriscorrectUTF-8butnotallowedbytheServer|3|0x03ConnectionRefused,Serverunavailable|TheNetworkConnectionhasbeenmadebuttheMQTTserviceisunavailable|4|0x04ConnectionRefused,badusernameorpassword|Thedataintheusernameorpasswordismalformed|5|0x05ConnectionRefused,notauthorized|TheClientisnotauthorizedtoconnect|6-255||Reservedforfutureuse如果上表中的返回码都不适用,那么服务端必须直接关闭网络连接,不发送CONNACK包[MQTT-3.2.2-6]。
CONNACK包没有载荷。
PUBLISH控制包可以从服务端发送给客户端也可以从客户端发送给服务端,来运送应用消息。
Figure3.10-PUBLISHPacketfixedheader展现了固定包头的格式:
Figure3.10-PUBLISHPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(3)|DUPflag|QoSlevel|RETAIN||0|0|1|1|X|X|X|X|byte2|RemainingLength3.3.1.1DUP位置:byte1,bit3。
如果DUP标识被设置为0,标识这是服务端或客户端第一次尝试发送MQTTPUBLISH包。如果DUP标识被设置为1,标识这可能是在重复发送早前尝试发送过的数据包。
DUP标识必须被设置为1,当客户端或是服务端试图重新发送PUBLISH包的时候[MQTT-3.3.1-1]。所有QoS为0的消息DUP标识必须也设置为0[MQTT-3.3.1-2]。
服务端收到带有DUP值的PUBLISH包,当服务端发送这个PUBLISH包给订阅者的时候,DUP的值不会传播。发送的PUBLISH包的DUP标识独立于收到的PUBLISH包的DUP标识,它的值只由是否是重复发送决定[MQTT-3.3.1-3]。
接收方在收到DUP标识设置为1的控制包时,不能当作已经收到较早的控制包。
重要的是,DUP标识只作用于控制包本身,而不是它包含的应用程序消息。使用QoS1时,有可能为客户获得发布包DUP标志设置为0,包含一个已经收到过的应用程序的重复信息,但是有不同的数据包标识。2.3.1节提供了更多关于包标识符的信息。
位置:byte1,bits2-1。
这个字段表明了应用消息传送的保障水平。QoS等级如下表所列Table3.2-QoSdefinitions。
Table3.2-QoSdefinitions|QoSvalue|Bit2|bit1|Description|0|0|0|Atmostoncedelivery|1|0|1|Atleastoncedelivery|2|1|0|Exactlyoncedelivery|-|-1|1|Reserved–mustnotbeusedPUBLISH包必定不能将Qos的两个bit都设为1(即Qosvalue不能设置为3,二进制11)。如果服务端或客户端收到的PUBLISH包中Qos的两个bit都设置为1的话,必须关闭网络连接
位置:byte1,bit0。
如果RETAIN标识被设置为1,在一个从客户端发送到服务端的PUBLISH包中,服务端必须存储应用消息和QoS,以便可以发送给之后订阅这个话题的订阅者[MQTT-3.3.1-5]。当一个新的订阅发生,最后一个保留的消息,如果有的话,而且匹配订阅话题,必须发送给订阅者[MQTT-3.3.1-6]。如果服务端收到一个QoS0并且RETAIN标识设置为1的消息,它必须清空之前为这个话题保存的所有消息。服务端应该存储新的QoS0的消息作为这个话题新的保留消息,但是也可以选择在任何时候清空保留消息——如果这样做了,那这个话题就没有保留消息了[MQTT-3.3.1-7]。存储状态详见4.1节。
如果消息是作为客户端新的订阅的结果从服务端发送PUBLISH包给客户端,服务端必须将RETAIN标识设置为1[MQTT-3.3.1-8]。当PUBLISH包发送给客户端,必须设置RETAIN为0,因为不管标识如何设置,它都是已订阅的消息。
RETAIN标识被设置为1,而且载荷包含0个字节的PUBLISH包也会被服务端像平常一样处理,发送给匹配话题的客户端。而且所有这个话题下的保留消息都会被清除,这个话题接下来的订阅者都不会收到保留消息[MQTT-3.3.1-10]."平常"意味着现存客户端收到的消息RETAIN标识都没有设置。0字节的保留消息一定不会作为保留消息存储在服务端[MQTT-3.3.1-11].
如果RETAIN标识位0,在客户端发送给服务端的PUBLISH包中,服务端一定不能存储这个消息,也一定不能删除或替换任何已存在的保留消息[MQTT-3.3.1-12].
在发布者会不定期发送状态消息的地方,保留消息非常有用。新的订阅者会受到最近的状态。
RemainingLength
可变包头的长度加上载荷的长度
可变包头按顺序包含以下字段:话题名,包唯一标识。
话题名指载荷数据发布的信息通道。
话题名必须是PUBLISH包可变包头的第一个字段。必须是UTF-8编码字符串[MQTT-3.3.2-1]就像1.5.3节定义的。
PUBLISH中的话题名一定不能包含通配符[MQTT-3.3.2-2]。
根据4.7节定义的的匹配过程,服务端发送给客户端的PUBLISH包中的话题名必须匹配订阅话题过滤器[MQTT-3.3.2-3]。但是,因为服务端允许重写话题名,所以可能话题名和最初的PUBLISH包不同。
包唯一标识只在QoS等级为1或2的PUBLISH包中存在。2.3.1节提供了包标唯一标识的更多信息。
Figure3.11-PublishPacketvariableheadernonnormativeexample展示了一个PUBLISH包可变包头的例子,Table3.3-PublishPacketnonnormativeexample是简化的描述。
Table3.3-PublishPacketnonnormativeexample|Field|Value|TopicName|a/b|PacketIdentifier|10Figure3.11-PublishPacketvariableheadernonnormativeexample||Description|7|6|5|4|3|2|1|0|TopicName|byte1|LengthMSB(0)|0|0|0|0|0|0|0|0|byte2|LengthLSB(3)|0|0|0|0|0|0|1|1|byte3|‘a’(0x61)|0|1|1|0|0|0|0|1|byte4|‘/’(0x2F)|0|0|1|0|1|1|1|1|byte5|‘b’(0x62)|0|1|1|0|0|0|1|0|PacketIdentifier|byte6|PacketIdentifierMSB(0)|0|0|0|0|0|0|0|0|byte7|PacketIdentifierLSB(10)|0|0|0|0|1|0|1|03.3.3载荷载荷包含了发布的应用消息。内容和格式由应用决定。载荷的长度可以由固定包头中的RemainingLength减去可变包头长度得到,也适用于载荷长度为零的PUBLISH包。
PUBLISH包的接收方必须根据Table3.4-ExpectedPublishPacketresponse,由PUBLISH包的QoS来决定如何响应[MQTT-3.3.4-1]。
Table3.4-ExpectedPublishPacketresponse|QoSLevel|ExpectedResponse|QoS0|None|QoS1|PUBACKPacket|QoS2|PUBRECPacket3.3.5行为客户端使用PUBLISH包发送应用消息给服务端,为了分发给匹配订阅的客户端。
服务端使用PUBLISH包发送应用消息给每一个匹配订阅的客户端。
当客户端通过带有通配符的话题过滤器订阅的时候,订阅之间可能会有重叠,以至于发布的消息会匹配多个过滤器。这种情况下,服务端必须使用客户端所有匹配的订阅中的最大的QoS来派发消息[MQTT-3.3.5-1]。而且,服务端可能会发送多个消息副本,每个匹配的订阅使用相应的QoS发送一次。
当收到PUBLISH包的时候,接收者的行为依赖于4.3节描述的QoS等级。
PUBACK包用来响应QoS等级为1的PUBLISH包。
Figure3.12-PUBACKPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(4)|Reserved||0|1|0|0|0|0|0|0|byte2|RemainingLength(2)||0|1|0|0|0|0|1|0RemainingLength
可变包头的长度,对PUBACK包来说这个值是2。
包含需要确认的那个PUBLISH包的包唯一标识。
Figure3.13-PUBACKPacketvariableheader|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB3.4.3载荷PUBACK包没有载荷。
完整的描述见4.3.2节。
PUBREC包用来响应QoS2的PUBLISH包。这是QoS2协议交换的第二个包。
Figure3.14–PUBRECPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(5)|Reserved||0|1|0|1|0|0|0|0|byte2|RemainingLength(2)||0|0|0|0|0|0|1|0RemainingLength
可变包头的长度。对PUBREC包来说值为2。
Figure3.15–PUBRECPacketvariableheader|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB3.5.3载荷PUBREC包没有载荷。
完整的描述见4.3.3节。
PUBREL包用来响应PUBREC包。是QoS2协议交换的第三部分。
Figure3.16–PUBRELPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(6)|Reserved||0|1|1|0|0|0|1|0|byte2|RemainingLength(2)||0|0|0|0|0|0|1|0PUBREL控制包的固定包头的位3,2,1,0是保留位,而且必须被分别设置为0,0,1,0。服务端必须将其他值作为畸形,并且关闭网络连接[MQTT-3.6.1-1]。
可变包头包含需要确认的PUBREC包相同的包唯一和标识。
Figure3.17–PUBRELPacketvariableheader|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB3.6.3载荷PUBREL包没有载荷
PUBCOMP包用来响应PUBREL包。这是QoS2协议交换的第四个也是最后一个包。
Figure3.18–PUBCOMPPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(7)|Reserved||0|1|1|1|0|0|0|0|byte2|RemainingLength(2)||0|0|0|0|0|0|1|0RemainingLength
这是可变包头的禅固定,对于PUBCOMP包来说值为2。
可变包头包含需要确认的PUBREL包相同的包唯一和标识。
Figure3.19–PUBCOMPPacketvariableheader|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB3.7.3载荷PUBCOMP包没有载荷
完整的表述见4.3.3节。
SUBSCRIBE包从客户端发送到服务端创建一个或多个订阅。每个订阅注册客户端感兴趣的一个或多个话题。服务端向客户端发送PUBLISH包来分发匹配订阅的应用消息。SUBSCRIBE包也(为每个订阅)指定服务端发送给客户端的应用消息的最大QoS。
Figure3.20–SUBSCRIBEPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(8)|Reserved||1|0|0|0|0|0|1|0|byte2|RemainingLengthSUBSCRIBE控制包的固定包头的3,2,1,0位是保留位,而且必须设置为0,0,1,0。服务端必须把其他值作为畸形对待,然后关闭网络连接[MQTT-3.8.1-1]。
可变包头的长度(2个字节)再加上载荷的长度。
可变包头包含包唯一标识。2.3.1节提供了包唯一标识的更多信息。
Figure3.21展示了带有包唯一标识10的可变包头。
Figure3.21-VariableheaderwithaPacketIdentifierof10,Nonnormativeexample||Description|7|6|5|4|3|2|1|0|PacketIdentifier|byte1|PacketIdentifierMSB(0)|0|0|0|0|0|0|0|0|byte2|PacketIdentifierLSB(10)|0|0|0|0|1|0|1|03.8.3载荷SUBSCRIBE包的载荷包含了话题过滤器的列表,指示客户端想要订阅的主题。SUBSCRIBE包载荷中的主体过滤器必须是1.5.3节定义的UTF-8编码字符串[MQTT-3.8.3-1]。服务端应该支持4.7.1节中定义的包含通配符的话题过滤器。如果选择不支持带有通配符的话题过滤器,就必须拒绝所有带有通配符过滤器的订阅[MQTT-3.8.3-2]。每个过滤器后面都带有一个字节的QoS。这指定了服务端发送给客户端应用消息的最大QoS等级。
SUBSCRIBE包的载荷至少包含一个成对的话题过滤器/QoS。没有载荷的SUBSCRIBE包是违反协议的[MQTT-3.8.3-3]。错误处理见4.8节。
在UTF-8编码的话题名之后是一个字节的最大QoS字段,这些话题过滤器/QoS对一个接一个地连续的被封装。
Figure3.22–SUBSCRIBEPacketpayloadformat|Description|7|6|5|4|3|2|1|0|TopicFilter|byte1|LengthMSB|byte2|LengthLSB|bytes3..N|TopicFilter|RequestedQoS||Reserved|QoS|byteN+1|0|0|0|0|0|0|X|XQoS字节的高6位在当前版本的协议中没有使用。这是为未来预留的。如果载荷中预留的位非零,或者QoS不是0,1,2,那么服务端必须把SUBSCRIBE包当作畸形关闭网络连接[MQTT-3.8.3-4]。
Figure3.23-Payloadbyteformatnonnormativeexample显示了SUBSCRIBE包的载荷,简短描述见Table3.5-Payloadnonnormativeexample。
Table3.5-Payloadnonnormativeexample|TopicName|“a/b”|RequestedQoS|0x01|TopicName|“c/d”|RequestedQoS|0x02Figure3.23-Payloadbyteformatnonnormativeexample||Description|7|6|5|4|3|2|1|0|TopicFilter|byte1|LengthMSB(0)|0|0|0|0|0|0|0|0|byte2|LengthLSB(3)|0|0|0|0|0|0|1|1|byte3|‘a’(0x61)|0|1|1|0|0|0|0|1|byte4|‘/’(0x2F)|0|0|1|0|1|1|1|1|byte5|‘b’(0x62)|0|1|1|0|0|0|1|0|RequestedQoS|byte6|RequestedQoS(1)|0|0|0|0|0|0|0|1|TopicFilter|byte7|LengthMSB(0)|0|0|0|0|0|0|0|0|byte8|LengthLSB(3)|0|0|0|0|0|0|1|1|byte9|‘c’(0x63)|0|1|1|0|0|0|1|1|byte10|‘/’(0x2F)|0|0|1|0|1|1|1|1|byte11|‘d’(0x64)|0|1|1|0|0|1|0|0|RequestedQoS|byte12|RequestedQoS(2)|0|0|0|0|0|0|1|03.8.4响应当服务端收到客户端发来的SUBSCRIBE包,服务端必须响应一个SUBACK包[MQTT-3.8.4-1]。SUBACK包必须包含与相应SUBSCRIBE包相同的包唯一标识[MQTT-3.8.4-2]。
服务端在发送SUBACK包之前,就可以开始发送订阅的PUBLISH包。
如果服务端收到一个SUBSCRIBE包,其中包含的话题过滤器与一个已经存在的话题过滤器完全相同,服务端必须用新的订阅替换已经存在的订阅。新的订阅中的话题过滤器与之前订阅中的完全一样,但是最大QoS的值可能不同。所有保存的符合该话题过滤器的消息必须被重发,但是发布流一定不能被打断[MQTT-3.8.4-3]。
如果话题过滤器与已存在订阅的过滤器都不相同,新的订阅会被创建,并且所有的匹配的保留消息都会被发送。
如果服务端收到的SUBSCRIBE包包含多个话题过滤器,必须像顺序的收到多个SUBSCRIBE包一样处理它,然后再把所有的响应合并为一个SUBACK响应[MQTT-3.8.4-4]。
从服务端发送到客户端的SUBACK包中,每一个话题过滤器/QoS对都有一个对应的返回码。返回码必须是这次订阅的最大QoS,否则就订阅失败[MQTT-3.8.4-5]。服务端可能会授予一个较低等级的最大QoS,相比较于订阅者要求的。订阅响应的载荷消息的QoS必须是原始发布消息的最小QoS,而且是服务端授予的最大QoS。服务端可以给订阅者发布多个消息拷贝,来应对原始消息使用QoS1发布,最大QoS为QoS0的情况[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包包含了返回码的列表,指定了SUBSCRIBE包中的每个订阅的最大QoS等级。
Figure3.24–SUBACKPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(9)|Reserved||1|0|0|1|0|0|0|0|byte2|RemainingLengthRemainingLength
可变包头的长度(2字节)再加上载荷的长度。
可变包头包含了需要确认的SUBSCRIBE包的包唯一标识。Figure3.25-variableheaderformat展示了可变包头的格式。
Figure3.25–SUBACKPacketvariableheader|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB3.9.3载荷载荷包含了返回码的列表。每个返回码对应SUBSCRIBE包中需要被确认的一个话题过滤器。SUBACK包中的返回码的顺序必须匹配SUBSCRIBE包中的话题过滤器的顺序[MQTT-3.9.3-1]。
Figure3.26-Payloadformat展示了载荷中编码在一个字节中的返回码字段。
Figure3.26–SUBACKPacketpayloadformat|Bit|7|6|5|4|3|2|1|0||ReturnCode|byte1|X|0|0|0|0|0|X|X允许的返回码:
0x00-成功-最大QoS为00x01-成功-最大QoS为10x02-成功-最大QoS为20x80-失败
除了0x00,0x01,0x02,0x80之外的SUBACK返回码是保留的,而且不允许使用[MQTT-3.9.3-2]。
Figure3.27-Payloadbyteformatnonnormativeexample展示了SUBACK包的载荷,简化描述见Table3.6-Payloadnonnormativeexample。
Table3.6-Payloadnonnormativeexample|Success-MaximumQoS0|0|Success-MaximumQoS2|2|Failure|128Figure3.27-Payloadbyteformatnonnormativeexample||Description|7|6|5|4|3|2|1|0|byte1|Success-MaximumQoS0|0|0|0|0|0|0|0|0|byte2|Success-MaximumQoS2|0|0|0|0|0|0|1|0|byte3|Failure|1|0|0|0|0|0|0|03.10UNSUBSCRIBE-退订话题UNSUBSCRIBE包从客户端发往服务端,用来退订话题。
Figure3.28–UNSUBSCRIBEPacketFixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(10)|Reserved||1|0|1|0|0|0|1|0|byte2|RemainingLengthUNSUBSCRIBE控制包固定包头的3,2,1,0位是预留的,而且必须分别被设置为0,0,1,0。服务端必须把其他值作为畸形,并且关闭网络连接[MQTT-3.10.1-1]。
可变包头包含了包唯一标识。2.3.1节提供了包唯一标识的更多信息。
Figure3.29–UNSUBSCRIBEPacketvariableheader|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB3.10.3载荷UNSUBSCRIBE包包含了客户端希望退订的话题过滤器列表。UNSUBSCRIBE包中的被连续打包的话题过滤器必须是1.5.3节定义的UTF-8编码字符串[MQTT-3.10.3-1]。
UNSUBSCRIBE包的载荷必须包含至少一个话题过滤器。没有载荷的UNSUBSCRIBE包是违反协议的[MQTT-3.10.3-2]。关于错误处理详见4.8节。
Figure3.30-Payloadbyteformatnonnormativeexample显示了UNSUBSCRIBE包的载荷,简化描述见Table3.7-Payloadnonnormativeexample。
Table3.7-Payloadnonnormativeexample|TopicFilter|“a/b”|TopicFilter|“c/d”Figure3.30-Payloadbyteformatnonnormativeexample||Description|7|6|5|4|3|2|1|0|TopicFilter|byte1|LengthMSB(0)|0|0|0|0|0|0|0|0|byte2|LengthLSB(3)|0|0|0|0|0|0|1|1|byte3|‘a’(0x61)|0|1|1|0|0|0|0|1|byte4|‘/’(0x2F)|0|0|1|0|1|1|1|1|byte5|‘b’(0x62)|0|1|1|0|0|0|1|0|TopicFilter|byte6|LengthMSB(0)|0|0|0|0|0|0|0|0|byte7|LengthLSB(3)|0|0|0|0|0|0|1|1|byte8|‘c’(0x63)|0|1|1|0|0|0|1|1|byte9|‘/’(0x2F)|0|0|1|0|1|1|1|1|byte10|‘d’(0x64)|0|1|1|0|0|1|0|03.10.4响应UNSUBSCRIBE包中的话题过滤器(无论是否包含通配符),都必须逐个字符的与客户端在服务器中的现有订阅集合做比较。如果过滤器匹配成功,那么删除相应的订阅,否则就不处理[MQTT-3.10.4-1]。
如果服务端删除了订阅:
服务端必须发送UNSUBACK包来响应UNSUBSCRIBE。UNSUBACK包必须和UNSUBSCRIBE包有相同的包唯一标识[MQTT-3.10.4-4]。即使没有话题订阅被删除,服务端也必须响应一个UNSUBACK[MQTT-3.10.4-5]。
如果服务端收到一个UNSUBSCRIBE包,包含多个话题过滤器,服务端必须像按顺序收到多个UNSUBSCRIBE包一样来处理,但是只发送一个UNSUBACK包来响应[MQTT-3.10.4-6]。
UNSUBACK包从服务端发往客户端来确认收到UNSUBSCRIBE包。
Figure3.31–UNSUBACKPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(11)|Reserved||1|0|1|1|0|0|0|0|byte2|RemainingLength(2)||0|0|0|0|0|0|1|0RemainingLength
可变包头的长度,对UNSUBACK包来说值为2。
可变包头包含了需要确认的UNSUBSCRIBE包的包唯一标识。
Figure3.32–UNSUBACKPacketvariableheader|Bit|7|6|5|4|3|2|1|0|byte1|PacketIdentifierMSB|byte2|PacketIdentifierLSB3.11.3载荷UNSUBACK包没有载荷
PINGREQ包从客户端发往服务端,可以用来:
这个包在KeepAlive处理中用到,更多的细节见3.1.2.10节。
Figure3.33–PINGREQPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(12)|Reserved||1|1|0|0|0|0|0|0|byte2|RemainingLength(0)||0|0|0|0|0|0|0|03.12.2可变包头PINGREQ包没有可变包头。
PINGREQ包没有载荷。
服务端必须发送PINGRESP包响应PINGREQ包[MQTT-3.12.4-1]。
PINGRESP包从服务端发送给客户端来响应PINGREQ包。它代表服务端是存活的。
|Figure3.34–PINGRESPPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(13)|Reserved||1|1|0|0|0|0|0|0|byte2|RemainingLength(0)||0|0|0|0|0|0|0|03.13.2可变包头PINGRESP包没有可变包头。
PINGRESP包没有载荷。
DISCONNECT包是客户端发给服务端的最后一个控制包。它表示客户端正在干净利索的断开连接。
|Figure3.35–DISCONNECTPacketfixedheader|Bit|7|6|5|4|3|2|1|0|byte1|MQTTControlPackettype(14)|Reserved||1|1|0|0|0|0|0|0|byte2|RemainingLength(0)||0|0|0|0|0|0|0|03.14.2可变包头DISCONNECT包没有可变包头。
DISCONNECT包没有载荷。
客服端发送DISCONNECT包之后:
服务端收到DISCONNECT包:
客户端和服务端为了提供QualityofService(QoS)的保障,存储Session状态是有必要的。客户端和服务端必须在整个Session期间都存储Session状态[MQTT-4.1.0-1]。只要有效的网络连接存在,Session就必须存在[MQTT-4.1.0-2]。
保留消息不是服务端Session状态的组成部分。服务端应该保留这些消息直到被客户端删除。
硬件或软件的崩溃都有可能使存储在客户端或服务端的Session状态丢失或损坏。
正常的客户端服务端操作可能会因为管理员行为,硬件故障,软件故障导致存储的状态丢失或损坏。管理员行为可能是对已定义条件的自动响应。这些行为可能是因为资源受限或其他操作理由。例如服务端可能基于外部知识决定一条过多条消息不再分发给当前或未来的客户端。
MQTT使用者应该评估MQTT客户端和服务单的存储功能的实现,以确保能够满足他们的需求。
例如,一个希望收集电表读数的用户可能决定使用QoS1的消息,因为他们需要保证读数在网络传输过程中不能丢失,他们确定供电是充足可靠的,所以客户端和服务端的数据存储在易失性的内存中丢失的风险也不会太大。
相反一个停车付款的应用提供者可能认为付款消息绝不能丢失,所以他们要求所有的数据在通过网络发送之前都要强制写入非易失性的内存中去。
MQTT协议需要一个底层传输层来提供有序,可靠的客户端和服务器之间的字节流。
用来搭载MQTT3.1的传出协议曾经是[RFC793]定义的TCP/IP。MQTT3.1.1也可以使用TCP/IP。以下也适用:
TCP端口8883和1883是IANA为MQTTTLS和非TLS通信注册的。
无连接的网络传输例如用户数据包协议(UDP)不适用,因为它们可能丢失或重排数据。
MQTT根据质量服务(QoS)等级分发应用消息。分发协议是对称的,客户端和服务端都既可以是发送者也可以是接收者。分发协议唯一关心的是应用消息的分发是从一个单一的发送者到一个单一的接收者。当服务端分发一个应用消息给多个客户端,每个客户端都被当作是独立的。向外分发应用消息给客户端的QoS等级可能与收到应用消息的QoS等级有所不同。
下面小节的非规范的流程图展示了可能的实现方式。
消息分发以来底层网络的性能。接收者不发送响应,发送者也不重新尝试。接收者只能收到一次消息,或者一次也收不到。
在QoS0的分发协议中,发送者
在QoS0的分发协议中,接收者
Figure4.1–QoS0protocolflowdiagram,nonnormativeexample
|SenderAction|ControlPacket|ReceiverAction|PUBLISHQoS0,DUP=0||||---------->||||DeliverApplicationMestoappropriateonwardrecipient(s)4.3.2QoS1:最少分发一次这种情况的质量服务确保消息至少一次抵达接收者。QoS1的PUBLISH包的可变包头包含包唯一标识,而且有PUBACK包确认。2.3.1节提供了更多包唯一标识的信息。
在QoS1的分发协议中,发送者
[MQTT-4.3.2-1]。
一旦发送者收到PUBACK包,包唯一标识就可以重用了。
注意,发送者在鞥带接收确认的时候,可以发送不同包唯一标识的PUBLISH包。
在QoS1的分发协议中,接收者
[MQTT-4.3.2-2]。
Figure4.2–QoS1protocolflowdiagram,nonnormativeexample|SenderAction|ControlPacket|Receiveraction|Storemessage|||SendPUBLISHQoS1,DUP0,
这是最高的服务质量,用在丢失和重复消息都不能被接受的情况。这种服务质量会增加开销。
QoS2消息的可变包头包含包唯一标识。2.3.1节提供了更多关于包唯一标识的信息。QoS2的PUBLISH包的接收者分两步确认接收。
在QoS2的分发协议中,发送者
[MQTT-4.3.3-1]。
当发送者收到PUBCOMP包之后,包唯一标识就可以重用了。
在QoS2的分发协议中,接收者
[MQTT-4.3.3-2]。
Figure4.3–QoS2protocolflowdiagram,nonnormativeexample|SenderAction|ControlPacket|ReceiverAction|Storemessage|||PUBLISHQoS2,DUP0
Figure4.3展示了接收者能处理QoS2的两种方法。流程中不同哦一点是在哪一步开始继续向下分发。选择方法A或方法B由实现决定。只要实现选择一种方法去处理,就不会影响QoS2流程哦可靠性。
当客户端将CleanSession设置为0进行重连,客户端和服务端都不许重发未确认的PUBLISH包(当QoS>0时),PUBREL包使用原始包唯一标识[MQTT-4.4.0-1]。这是客户端和服务端被要求重发消息的唯一情况。
控制包的重传在历史上是为了克服在古老哦TCP网络上丢失数据。现在这也是一个问题,MQTT3.1.1的实现有可能部署在这样的环境中。
当服务端获得了发来的应用消息的所有权,必须追加到那些符合订阅的客户端的Session状态中。匹配规则定义在4.7节[MQTT-4.5.0-1]。
在正常的环境下,客户端接收已创建订阅的消息。客户端也会收到一些没有订阅的消息。如果服务端自动指派了一个订阅给客户端就会出现何种情况。当UNSUBSCRIBE操作在处理中的时候,客户端也可能收到这个订阅的消息。客户端必须根据QoS规则确认所有收到的PUBLISH包,不论是否处理应用消息的内容[MQTT-4.5.0-2]。
在实现本章定义的协议流程的时候,客户端必须遵循如下规则:
服务端必须默认把每个话题都当作“有序话题”。可以提供管理上或其他机制允许的一个或多个话题作为“无序话题”[MQTT-4.6.0-5]。
当服务端处理一个被发布给有序话题的消息时,在分发消息给订阅者时,必须遵循上述规则。而且必须按照收到PUBLISH包的顺序把它们发送给消费者(相同的话题和QoS)[MQTT-4.6.0-6]。
上述规则保证了,当一个QoS1订阅的消息流被发布,订阅者收到的每个消息的最终拷贝都会按照原始发布的顺序,但是消息重复的可能性会导致较早的消息在重发后,会比后续的消息更晚收到。例如发布者可能按照1,2,3,4的顺序发送消息,订阅者可能按照1,2,3,2,3,4的顺序收到消息。
话题层级分隔符把层级结构引入到话题名称中。如果存在分隔符,话题名称会被分割为多个“话题层级”。
一个订阅话题过滤器可以包含指定的通配符,通配符允许一次订阅多个话题。
通配符可以被用在话题过滤器中,但不能用在话题名称里[MQTT-4.7.1-1]。
斜杠(‘/’U+002F)被用来分割话题书的每个层级,为话题名称提供一个分级结构。话题层级分割符的是有象征意义的,当订阅客户端指定的话题过滤器中遇到两个通配符的时候。话题层级分隔符可以唉话题名称或话题过滤器哦任何地方出现。相邻的层级分隔符标识一个零长度的话题层级。
井号(‘#’U+0023)匹配一个话题的任意数量的层级。多层级通配符代表父层级和任意数量的子层级。多层级通配符必须要么独自存在要么在话题层级分隔符之后。两种情况中都必须是话题过滤器的最后一个字符[MQTT-4.7.1-2]。
例如,如果客户端订阅“sport/tennis/player1/#”,使用下列话题名称的消息会被收到:
加号(‘+’U+002B)通配符只能匹配单层话题。
单层级通配符可以用在话题过滤器的任意层级,包括首层和末层。使用的时候必须占据整个层[MQTT-4.7.1-3]。可以在话题过滤器的多个层级使用,也可以和多层通配符联合使用。
例如,"sport/tennis/+"匹配"sport/tennis/player1"和"sport/tennis/player2",但是不匹配"sport/tennis/player1/ranking"。而且因为单层级通配符只能匹配一个层级,所以"sport/+"不能匹配"sport",但是可以匹配"sport/"。
服务端不能用以通配符(#或+)开头的话题过滤器去匹配以$开头的话题名字[MQTT-4.7.2-1]。服务端应该避免客户端使用这样的话题名称与其他客户端交换消息。服务端实现可以使用以$开头的话题名字做其他目的。
下列规则适用于话题名称和话题过滤器:
话题名称和话题过滤器的层级数没有限制,但是受到UTF-8编码字符串长度的限制。
当执行匹配的时候,服务端不能执行任何的话题名称和话题过滤器的规范行为或修改或替换非法字符[MQTT-4.7.3-4]。话题过滤器里每个非通配符的层级都必须逐个字符的匹配话题名称里的相应层级才算匹配成功。
UTF-8编码规则意味着话题名称和话题过滤器的比较既可以通过对比UTF-8编码后的字节,也可以通过对比解码后的Unicode字符
应用消息会被发送给每一个客户端订阅,只要客户端的话题过滤器和应用消息的话题名称匹配。话题资源既可以通过管理员在服务端预定义,也可以被服务端动态的创建,当服务端收到包含话题名称的第一个订阅或应用消息。服务端也可以对给定客户端使用安全组件在话题资源上有选择的进行操作。
除非另有说明,如果服务端或客户端遇到违反协议的情况,必须关闭收到违反协议的控制包的网络连接[MQTT-4.8.0-1]。
客户端或服务端实现可能遇到一个瞬时错误(例如缓冲区已满)阻碍成功处理MQTT包。
如果客户端或服务端正在处理外来的控制包时遇到瞬时错误必须关闭控制包对应的网络连接[MQTT-4.8.0-2]。如果服务端发现了瞬时错误不应该断开与其他客户端的连接或对其他客户端产生别的影响。
这一章仅仅是提供一个指引,而且是非规范性的。但是强烈推荐服务端实现提供TLS[RFC5246],使用TCP协议8883端口(IANA服务名称:secure-mqtt)。
有一些风险是解决方案的提供者应该考虑到的,例如:
MQTT解决方案经常部署在通信恶略的环境。这种情况下,实现通常需要提供如下机制:
作为一个传输协议,MQTT只关心消息的传输,而提供适当的安全特性是实现者的责任。通常使用TLS[RFC5246]可以实现。
除了技术上的安全,还应该考虑地理(e.g.U.S.-EUSafeHarbor[USEUSAFEHARB]),行业(e.g.PCIDSS[PCIDSS]),监管(e.g.Sarbanes-Oxley[SARBANES])的问题。
一个实现可能需要提供符合特定行业标准的安全框架,例如NIST网络安全框架[NISTCSF],PCI-DSS[PCIDSS],FIPS-140-2[FIPS1402]andNSASuiteB[NSAB].
使用NIST网络安全框架的指引可以在MQTT的补充补充出版资料中找到,《MQTT和NIST改善关键基础设施网络安全框架》[MQTTNIST]。行业使用证明,独立的验证和认证技术可以帮助达到要求。
高级加密标准[AES]和数据加密标准[DES]被广泛采用。
ISO29192[ISO29192]使加密可以在受限的低端设备上运行。
当实现或使用MQTT时,要考虑的安全问题有很多,而不仅仅限于下节涉及到的。下一节不应被视为一个“检查表”。
一个实现可能完成到下列某些或全部:
CONNECT包含有Username和Password字段。实现可以选择如何使用这些字段的内容。他们也可能提供自己的认证机制,使用额外的认证系统,例如LDAP[RFC4511]orOAuth[RFC6749]tokens,或者利用操作系统认证机制。
实现传送认证数据,不论用明文,还是混淆数据元素,亦或不要求认证,都应该意识到,这样会有中间人攻击或重播攻击的风险。5.4.5节介绍了确保数据私密性的方法。
客户端和服务端之间的虚拟私有网络(VPN)可以保证只会从认证过的客户端收到数据。
如果使用了TLS[RFC5246],从客户端发来的SSL证书可以让服务端用来认证客户端。
实现可能允许认证凭证通过应用消息从客户端发送到服务端。
MQTT协议不是对称信任,不提供客户端认证服务端的机制。
如果使用TLS[RFC5246],客户端可以用服务端发来的SSL证书来认证。如果实现提供一个ip地址的多个主机名的MQTT服务,应该注意RFC6066[RFC6066]第3节定义的TLS服务器名扩展。这允许客户端告知服务端尝试连接服务器的主机名。
实现可能允许认证凭证通过应用消息从服务端发送到客户端。
客户端和服务端之间的VPN可以保证客户端连接的是指定的服务端。
应用可以在他们的应用消息中独立地包含哈希值。这可以提供发布控制包在网路传输及其他情况下的完整性。
TLS[RFC5246]为通过网络传输的数据提供了哈希算法,来验证其完整性。
使用VPN网络连接客户端和服务端可以保证数据在VPN段传输的完整性。
TLS[RFC5246]可以提供数据加密。有效的TLS密码套件包含一个空的加密算法,不会加密数据。确保私密的客户端和服务端没有使用这些套件。
应用可能自己实现应用消息内容的加密。这可以保证应用消息在网络传输或其他情况下的私密性。但不会保护应用消息其他属性的私密性,例如话题名称。
客户端和服务端实现可以提供数据的加密存储,例如应用消息作为Session的一部分被存储的时候。
使用VPN网络连接客户端和服务端可以保证数据在VPN上传输的私密性。
应用设计者或许需要考虑合适的策略来实现端对端的不可抵赖。
使用TLS[RFC5246]的客户端和服务端实现,应该在初始化TLS[RFC5246]连接的时候,有能力确保SSL证书能够与客户端和服务端的主机名匹配。
使用TLS[RFC5246]的客户端和服务端实现,可以选择提供检测证书撤销列表(CRLS[RFC5280])和在线证书状态协议(OSCP)[RFC6960],来防止撤销证书的使用。
在物理部署的时候可能会用到防干扰的设备传输应用消息的指定数据。例如嵌入式的GPS来确保没有在未认证的地区使用。[IEEE802.1AR]是使用密码绑定标识符识别设备惟一性哦标准实现。
服务器实现可以监视客户端的行为,以便发现潜在的安全事件。例如:
服务端实现会点开违反安全规则的客户端连接。
服务端实现检测不受欢迎的行为,生成一个动态的基于Ip地址或客户端唯一标识的禁止访问列表。
部署可能会使用网络层级控制(可用的话)来实现基于IP地址或基于其他信息的访问控制。
如果客户端或服务端的SSL证书丢失或者被盗用,应该注销证书(利用CRLs[RFC5280]和/或OSCP[RFC6960])。
客户端和服务端证书,就像用户名和密码,如果丢失或怀疑被盗用,应该被吊销和/或重新颁发。
在持续的长连接的情况下:
受约束的设备和客户端在受约束的网络中,可以利用TLS会话重获[RFC5077],以便减少重建TLS[RFC5246]会话的成本。
连接到同一个服务器的客户端之间有信任传递的关系,同样具有发布话题的权利。
客户端的实现应该认识到,有些环境要求使用SOCKSv5[RFC1928]代理对外连接。有些MQTT实现或许会通过SOCKS使用安全隧道(即SSH)。选择使用SOCKS的实现应该支持匿名或用户名密码认证方式的SOCKS代理。在使用用户名密码的情况下,实现应该认识到SOCKS认证或许会用纯文本,所以应该避免使用相同的证书去连接一个MQTT服务端。
实现和解决方案的设计者或许希望把安全当多一组作用于MQTT协议的配置来考虑。一个分层的安全体系展现如下。
当使用干净的通信配置,MQTT协议运行在一个开放的网络中,没有附带适当的安全通信机制。
当使用安全网络通信配置,MQTT协议运行在一个物理或虚拟的网络中,有安全控制的VPN网络或物理安全网络。
当使用安全传输配置,MQTT协议运行在一个物理或虚拟的网络中,使用TLS[RFC5246]提供的认证机制,完整性和私密性。
TLS[RFC5246]客户端认证可以用来替代MQTT用户名密码字段提供的客户端认证。
MQTT协议期望被设计在工业级的应用配置中,每种工业级应用都会定义风险模型及其对应的安全机制。推荐的常用安全机制如下所列:
[NISTCSF]NISTCyberSecurityFramework[NIST7628]NISTIR7628GuidelinesforSmartGridCyberSecurity[FIPS1402]SecurityRequirementsforCryptographicModules(FIPSPUB140-2)[PCIDSS]PCI-DSSPaymentCardIndustryDataSecurityStandard[NSAB]NSASuiteBCryptography
如果MQTT在WebSocket[RFC6455]上传输,如下条件必须实现:
本协议要求IANA将WebSocketMQTT子协议注册在“WebSocketSubprotocolName”下,注册数据:
一个MQTT实现可以即符合MQTT客户端也符合MQTT服务端的实现。一个既能响应连接,也能向其他服务端发起连接的服务端必须既是一个MQTT客户端也是一个MQTT服务端[MQTT-7.0.0-1]。
一致的实现不能使用任何本规范之外的扩展,以便于其他一致的实现交互[MQTT-7.0.0-2]。
MQTT服务端只有在下列陈述都满足的情况下才算符合本规范:
一致性的服务端必须支持一个或多个能够提供有序,可靠,双向字节流的底层传输协议[MQTT-7.1.1-1]。但是一致性不依赖于任何特殊的传输层协议。服务端或许会支持4.2节所列的传输层协议,或者是其他符合要求的传输层协议[MQTT-7.1.1-2]。
MQTT客户端只有在下列陈述都满足的情况下才算符合本规范:
一致性的客户端必须支持一个或多个能够提供有序,可靠,双向字节流的底层传输协议[MQTT-7.1.2-1]。但是一致性不依赖于任何特殊的传输层协议。客户端或许会支持4.2节所列的传输层协议,或者是其他符合要求的传输层协议[MQTT-7.1.2-2]。