一、MQTT 介绍MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。v3.1.1版本协议仅仅包含14个协议帧它格式简单、规范且易于实现非常适合物联网场景使用。二、MQTT报文结构2.1、报文三部分组成1、固定报头2、可变报头3、有效载荷根据需要可选如下图所示2.2、固定报头Fixed header固定报头由两个字节组成第一个字节(byte1)7-4位为 协议类型3-0位为标志位。第二个字节(byte2)表示剩余长度包含可变报头和有效载荷2.2.1、报文类 型固定头第一个字节7-4位第一个字节的7-4位一共4位可表示16个数字除0、15以为剩余14个数字各表示一个控制报文类型如图所示2.2.2、报文类型标志位固定头第一个字节3-0位第一个字节的3-0位的标志位定义如图所示DUP¹ 控制报文的重复分发标志QoS² PUBLISH报文的服务质量等级RETAIN³ PUBLISH报文的保留标志2.2.3、固定头第一个字节示例2.2.4、剩余长度固定头第二个字节剩余长度表示当前报文剩余部分的字节数包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。剩余长度字段使用变长编码方案最小一个字节最大4个字节。每个字节低7位用于编码数据最高位用0或者1指示是否有更多字节表示。因此每个字节可以编码128个数值和一个延续位。例如剩余长度127 0111 1111 即 0x7F最高位0表示没有更多字段剩余长度128 1000 0000 0000 0001 即0x80 0x010111 1111 1 1000 0000 即10进制128所以我们可以得出不同字节数可以表示的剩余长度的范围2.3、可变报头Variable header可变报头存在于部分MQTT数据包 中数据包类型决定了可变报头是否存在及其具体内容。可变报头的内容根据报文类型的不同而不同但大多数协议都会有的字段是报文标识符Packet Identifier。例如在CONNECT报文中可变报头包含协议名、协议级别、连接标志和保持连接等字段。2.4 、有效载荷Payload有效载荷存在于部分MQTT数据包中表示客户端收到的具体内容。例如在CONNECT报文中有效载荷包含一个或多个以长度为前缀的字段如客户端标识符、遗嘱主题、遗嘱消息、用户名和密码等。这些字段是否包含以及出现的顺序由可变报头中的标志位决定。三、MQTT控制报文3.1、CONNECT 报文CONNECT 协议是客户端建立连接的第一个报文通常都要带有鉴权的字段一个CONNECT报文都会对应一个服务端的CONNACK报文。3.1.1、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) 10 xx3.1.2 、可变报头Variable header可变报头需要10个字节。3.1.2.1、协议名byte1-6 表示协议名第一个字节MSB为字符串长度最高有效字节数第二个字节LSB为字符串长度最低有效字节数 因为目前主流的CPU都是大端序BigEndianMSB和LSB两个字节组成的big int 表示协议名称的长度即MQTT 为4字节长度。 所以可变报头前6个字节内容如下#16进制表示(HSBLSB’MQTT’) 00 04 4D 51 54 543.1.2.2、 协议版本byte7表示协议级别 此处固定为 0x04。表示v3.1.13.1.2.3 、连接标志byte8 表示连接标志用1、0表示是否开启 。第0位为保留位固定为0。第1位为会话清理标志 1表示每次建立连接要求服务端重新开启会话0则表示服务端会话持久化。一般选用1。第2位为遗嘱标志位1表示启用遗嘱功能0则表示关闭遗嘱功能。启用时连接标志中的Will Qos第3、4位Will Retain 第5位会被使用有效载荷中必须包含 遗嘱topicWill Topic、遗嘱消息Will Message字段未启用时连接标志中的Will Qos第3、4位Will Retain 第5位必须为0。一般选用0。第4位、第3位用来表示遗嘱Qos遗嘱标志位关闭时强制填00遗嘱标志位启用时可以为00qos0、01qos1、10qos2。第5位为遗嘱保留标志位1表示启用遗嘱保留0表示关闭遗嘱保留。 遗嘱标志位关闭时强制为0。第6位为密码标志位1表示启用密码有效载荷中必须包含密码字段0表示不启用密码有效载荷中不能包含密码字段。正常都传密码。第7位为用户名标志位1表示启用用户名有效载荷中必须包含用户名字段0表示不启用用户名有效载荷中不能包含用户名字段。正常都传用户名。如果不启用用户名时密码也不能启用。连接标志字节的常见的情形3.1.2.4 、心跳间隔byte9、byte10 两个字节以MSBLSB表示心跳间隔单位为秒。客户端按照心跳间隔发送PINGREQ服务端回PINGRESP服务端在1.5倍心跳间隔没有收到PINGREQ时将以keepalive timeout 的理由断开连接。3.1.2.5、遗嘱发布的条件3.1.2.6、可变报头示例连接标志为0xC2的情形#16进制表示 一个CONNECT包的可变报头 00 04 4D 51 54 54 04 C2 00 3C 前6个字节基本上是固定的MSBLSBMQTT 第7个字节表示版本号 0x04 4表示 v3.1.1 第8个字节为连接标志字节 0xC2会话清理开启不启用遗嘱 第9、10字节表示心跳间隔 60秒3.1.3 有效载荷PayloadCONNECT报文的有效载荷payload包含一个或多个以长度为前缀的字段可变报头中的标志决定是否包含这些字段。如果包含的话必须按这个顺序出现客户端标识符遗嘱主题遗嘱消息用户名密码。 需要注意的是每一个字段的拼接方式都遵循 MSBLSBContent的格式。 即在字段内容前拼入两个字节表示的大端序bit int来表示字段的长度Content 内容必须是UTF-8格式。3.1.4 报文示例用mosquito 工具模拟连接报文mosquitto_sub -d -h 127.0.0.1 -p 1883 -u username/1 -P password -i clientid/1 -t topic得到CONNECT报文如下#16进制表示 102C00044D51545404C2003C000A836C99656E7469642F31000A757365726E616D652F31000870617373776F7264 #固定报头剩余长度44个字节 10 2C #可变报头 00 04 4D 51 54 54 04 C2 00 3C #余下为有效载荷字段 #客户端标识符000A 表示长度为10个字节文本内容为“clientid/1” 00 0A 63 6C 69 65 6E 74 69 64 2F 31 #遗嘱主题、遗嘱消息为启用所以没有 #用户名000A 示长度为10个字节文本内容为“username/1” 00 0A 75 73 65 72 6E 61 6D 65 2F 31 #密码0008 表示长度为8个字节文本内容为“password” 00 08 70 61 73 73 77 6F 72 643.2、CONACK 报文(应答)CONNACK报文是确认连接报文。即CONNECT报文的响应报文报文内容会返回连接成功标志。3.2.1 、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) 20 xx3.2.2 、可变报头Variable header第1个字节是 连接确认标志位7-1是保留位且必须设置为0。 第0 (SP)位 是当前会话Session Present标志。 当服务端建立连接时会话是新创建的时SP返回0会话是之前持久化的会话且客户端传的清理会话标志没有开启时SP返回1。第2个字节为连接返回码。最常见的返回码即 0x00 正常连接、0x05连接未授权3.2.3 、有效载荷Payload无3.2.4 、真实报文示例#16进制表示 #连接成功会话为新会话 20 02 00 00 #连接成功会话为旧会话 20 02 01 00 #连接未授权 20 02 00 053.3 、PUBLISH 报文PUBLISH是发布消息协议报文双向都可以使用。3.3.1 、固定报头Fixed headerPUBLISH协议包的固定头会跟标志位进行变化。第3位为重传标志qos0以上的PUBLISH是要求对方返回ack的没有收到ack时会被重传重传时DUP位为1。所以qos不能传1。第2-1位为Qos标志用两位表示qos 0、1、2与CONNECT可变报头的连接标志一样。Qos是服务质量等级。 0表示只发一次 1表示至少发一次 2表示只发一次第0位为保留消息标志同样1表示启用0表示不启用。这里的保留消息与前面提到遗嘱保留消息差不多都是指要求服务端收到PUBLISH时持久化最新的一条消息以便后来订阅主题的连接能够收到最近的一条消息。PUBLISH包的固定报头的第一个字节的常见的情形 3.3.2 、可变报头Variable header可变报头按顺序包含主题名和报文标识符。主题名拼接方式均为 MSBLSBContent主题名不能包含通配符。报文标识符 Qos为12时才传报文标识符。 由MSBLSB组成的int值表示 报文标识符自增id每次建立连接后从1开始。3.3.3、 有效载荷Payload有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。有效载荷的长度这样计算用固定报头中的剩余长度字段的值减去可变报头的长度。包含零长度有效载荷的PUBLISH报文是合法的。3.3.4、 报文示例我们用mosquito 工具模拟PUBLISH报文mosquitto_pub -d -h 127.0.0.1 -p 1883 -u username/1 -P password -i clientid/1 -t topic -m message#16进制表示 32100005746F70696300016D657373616765 #固定报头qos1消息非重传、非保留剩余长度16个字节 32 10 可变报头5个字节的主题“topic”报文标识符为1 00 05 74 6F 70 69 63 00 01 #有效载荷“message” 6D 65 73 73 61 67 653.4 、PUBACK 报文(QoS 1应答)PUBACK报文是对QoS 1等级的PUBLISH报文的响应。3.4.1 、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度 40 023.4.2 可变报头Variable header可变报头仅有报文标识符。 格式为 MSBLSB。3.4.3 有效载荷Payload无3.4.4 报文示例#16进制表示 40020001 #固定报头剩余长度2个字节 40 02 #可变报头报文标识符为1 00 013.5 、PUBREC 报文QoS 2应答PUBREC 报文是对QoS等级2的PUBLISH报文的响应。它是QoS 2等级协议交换的第二个报文。3.5.1 、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) 50 023.5.2 、可变报头Variable header可变报头包含等待确认的PUBLISH报文的报文标识符。格式为 MSBLSB。3.5.3 、有效载荷Payload无3.5.4、报文示例#16进制表示 50020001 #固定报头剩余长度2个字节 50 02 #可变报头报文标识符为1 00 013.6、PUBREL 报文QoS 2应答PUBREL报文是对PUBREC报文的响应。它是QoS 2等级协议交换的第三个报文。3.6.1、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) 60 023.6.2、 可变报头Variable header可变报头包含与等待确认的PUBREC报文相同的报文标识符。 格式为 MSBLSB。3.6.3 、有效载荷Payload无3.6.4 、报文示例#16进制表示 60020001 #固定报头剩余长度2个字节 60 02 #可变报头报文标识符为1 00 013.7、PUBCOMP 报文QoS 2 应答PUBCOMP报文是对PUBREL报文的响应。它是QoS 2等级协议交换的第四个也是最后一个报文。3.7.1、 固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) 70 023.7.2 、可变报头Variable header可变报头包含与等待确认的PUBREL报文相同的报文标识符。 格式为 MSBLSB。3.7.3 、有效载荷Payload无3.7.4、 报文示例#16进制表示 70020001 #固定报头剩余长度2个字节 70 02 #可变报头报文标识符为1 00 013.8、SUBSCRIBE 报文客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。3.8.1 、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) 82 xx3.8.2 、可变报头Variable header可变报头仅有报文标识符。 格式为 MSBLSB。3.8.3 、有效载荷PayloadSUBSCRIBE报文的有效载荷包含了一个主题过滤器列表它们表示客户端想要订阅的主题。服务端支持主题过滤器通配订阅。每一个过滤器后面跟着一个字节这个字节被叫做 服务质量要求Requested QoS。它给出了服务端向客户端发送应用消息所允许的最大QoS等级。SUBSCRIBE报文的有效载荷必须包含至少一对主题过滤器 和 QoS等级字段组合。每一组主题过滤器和QoS组合的拼接格式为MSBLSBContentQosQos用一个字节表示可能的值为 0x00、0x01、0x023.8.4 、报文示例我们使用mosquito工具模拟SUBSCRIBE报文mosquitto_sub -d -h 127.0.0.1 -p 1883 -u username/1 -P password -i clientid/1 -t topic#16进制表示 820a00010005746F70696300 #固定报头剩余长度10个字节 82 0A #可变报头报文标识符为1 00 01 #有效载荷长度为5个字节topicfilter为“topic”qos为0 00 05 74 6F 70 69 63 003.9 、SUBCK 报文(应答)服务端发送SUBACK报文给客户端用于确认它已收到并且正在处理SUBSCRIBE报文。SUBACK报文包含一个返回码清单它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。3.9.1、 固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) 90 xx3.9.2 可变报头Variable header可变报头包含等待确认的SUBSCRIBE报文的报文标识符。格式为 MSBLSB。3.9.3、有效载荷Payload有效载荷包含一个返回码清单。每个返回码对应等待确认的SUBSCRIBE报文中的一个主题过滤器。返回码的顺序必须和SUBSCRIBE报文中主题过滤器的顺序相同。允许的返回码值0x00 - 最大QoS 00x01 - 成功 – 最大QoS 10x02 - 成功 – 最大 QoS 20x80 - Failure 失败0x00, 0x01, 0x02, 0x80之外的SUBACK返回码是保留的不能使用3.9.4 、报文示例#16进制表示 90030000100 #固定报头剩余长度3个字节 90 03 #可变报头报文标识符为1 00 01 #有效载荷对应订阅的topic的qos 0 003.10 、UNSUBSCRIBE 报文客户端发送UNSUBSCRIBE报文给服务端用于取消订阅主题。3.10.1 、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) A2 xx3.10.2、 可变报头Variable header可变报头仅有报文标识符。 格式为 MSBLSB。3.10.3、 有效载荷PayloadUNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。每一个主题过滤器的拼接格式为 MSBLSBContent3.10.4 、报文示例#16进制表示 A20900100005746F706963 #固定报头剩余长度9个字节 A2 09 #可变报头报文标识符为2 00 10 #有效载荷长度为5个字节topicfilter为“topic” 00 05 74 6F 70 69 633.11 、UNSUBCK 报文(应答)服务端发送UNSUBACK报文给客户端用于确认收到UNSUBSCRIBE报文。3.11.1 、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) B0 023.11.2、 可变报头Variable header可变报头仅有报文标识符。 格式为 MSBLSB。3.11.3 、有效载荷Payload无3.11.4 、报文示例#16进制表示 B0020010 #固定报头剩余长度2个字节 B0 02 #可变报头报文标识符为1 00 013.12 、PINGREQ 报文客户端发送PINGREQ报文给服务端的心跳包。3.12.1、 固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) c0 003.12.2 、可变报头Variable header无3.12.3 、有效载荷Payload无3.12.4 、报文示例#16进制表示 C0 003.13、 PINGRESP 报文应答服务端对客户端心跳包应答。3.13.1 固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) D0 003.13.2、 可变报头Variable header无3.13.3、 有效载荷Payload无3.13.4、 报文示例#16进制表示 D0 003.14 、DISCONNECTDISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。3.14.1 、固定报头Fixed header#16进制表示(第一字节是报文类型和保留位第二个字节是剩余长度) E0 003.14.2 、可变报头Variable header无3.14.3、 有效载荷Payload无3.14.4 、报文示例#16进制表示 E0 00