MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。v3.1.1版本协议仅仅包含14个协议帧,它格式简单、规范且易于实现,非常适合物联网场景使用。
本文开始梳理MQTT协议的报文格式。官方的协议文档语义严谨,除协议格式外,有专门的章节介绍协议交互操作规范以及指导实现协议的强制性规范,所以新手阅读比较费时。
本文仅仅整理了一下MQTT协议的报文格式,方便大家进行查阅。
MQTT控制报文由三部分组成,分别是固定报头、可变报头、有效载荷(根据需要可选),如下图所示:
固定报头由两个字节组成,第一个字节的7-4位为 协议类型,3-0位为标志位。 第二个字节表示剩余长度(包含可变报头和有效载荷)。
第一个字节的7-4位,一共4位,可表示16个数字,除0、15以为,剩余14个数字各表示一个控制报文类型,如图所示:
第一个字节的3-0位的标志位,作为保留位时,要求按照协议规定传固定值,其他按标志位定义的含义决定传值, 标志位表格如图所示:
DUP1 =控制报文的重复分发标志
QoS2 = PUBLISH报文的服务质量等级
RETAIN3 = PUBLISH报文的保留标志
剩余长度表示当前报文剩余部分的字节数,包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。
剩余长度字段使用变长编码方案,最小一个字节,最大4个字节。每个字节,低7位用于编码数据,最高位用0或者1指示是否有更多字节表示。
因此每个字节可以编码128个数值和一个延续位。
示例:
剩余长度127可以用以下表示
0111 1111 (即 0x7F,最高位0表示没有更多字段)
而,剩余长度128需要如下表示
1000 0000 0000 0001 (即0x80 0x01, 即移除两个字节的延续位, 变成 1 0000 0001 即10进制128)
所以,我们可以得出不同字节数可以表示的剩余长度的范围
附上变长长度编码解码算法
某些控制报文包含可变报头,它在固定报头(Fixed header)和有效载荷(Payload)之间。每个协议的可变报头都不一样。
其中大多数协议都会有的字段报文标识符。
可变报头在各个控制报文的详细内容中再展开讲解。
有效载荷是除控制报文格式以为的有效信息,CONNECT、PUBLISH、SUBSCRIBE等需要传递有效信息的协议帧都需要。
CONNECT 协议是客户端建立连接的第一个报文,通常都要带有鉴权的字段,一个CONNECT报文都会对应一个服务端的CONNACK报文。
可变报头需要10个字节。
byte1-6表示协议名,第一个字节(MSB)为字符串长度最高有效字节数,第二个字节(LSB)为字符串长度最低有效字节数, 因为目前主流的CPU都是大端序(BigEndian),MSB和LSB两个字节组成的big int 表示协议名称的长度,即'MQTT' 为4字节长度。 所以可变报头前6个字节内容如下
byte7表示协议级别, 此处固定为 0x04。(表示v3.1.1)
byte8表示连接标志,用1、0表示是否开启。
第0位为保留位,固定为0
第1位为会话清理标志, 1表示每次建立连接,要求服务端重新开启会话;0则表示服务端会话持久化。一般选用1。
第2位为遗嘱标志位,1表示启用遗嘱功能,0则表示关闭遗嘱功能。启用时,连接标志中的Will Qos(第3、4位),Will Retain (第5位)会被使用,有效载荷中必须包含 遗嘱topic(Will Topic)、遗嘱消息(Will Message)字段;未启用时,连接标志中的Will Qos(第3、4位),Will Retain (第5位)必须为0,。一般选用0。
第4位、第3位用来表示遗嘱Qos,遗嘱标志位关闭时,强制填00,遗嘱标志位启用时,可以为00(qos0)、01(qos1)、10(qos2)
第5位为遗嘱保留标志位,1表示启用遗嘱保留,0表示关闭遗嘱保留。 遗嘱标志位关闭时,强制为0。
第6位为密码标志位,1表示启用密码,有效载荷中必须包含密码字段;0表示不启用密码,有效载荷中不能包含密码字段。正常都传密码。
第7位为用户名标志位,1表示启用用户名,有效载荷中必须包含用户名字段;0表示不启用用户名,有效载荷中不能包含用户名字段。正常都传用户名。如果不启用用户名时,密码也不能启用。
byte9、byte10两个字节以MSB+LSB表示心跳间隔,单位为秒。客户端按照心跳间隔发送PINGREQ,服务端回PINGRESP,服务端在1.5倍心跳间隔没有收到PINGREQ时,将以keepalive timeout的理由断开连接。
注意:
关于遗嘱相关概念进行说明一下。
当客户端启用遗嘱功能时,在CONNECT协议包的可变报头的连接标志中,必须打开遗嘱标志(byte8的第2位),并在有效载荷中传递遗嘱主题和遗嘱消息。
当服务端判断客户端异常断开时,服务端会向遗嘱主题发送遗嘱消息。
遗嘱消息发布的条件,包括但不限于:
服务端检测到了一个I/O错误或者网络故障。
客户端在保持连接(Keep Alive)的时间内未能通讯。
客户端没有先发送DISCONNECT报文直接关闭了网络连接。
由于协议错误服务端关闭了网络连接。
当启用遗嘱时,在标志位中可以设置遗嘱消息的Qos,即可变报头的byte8的第3、4位。
当启用遗嘱保留标志(byte8的第5位)时,表示遗嘱消息在发布时需要保留。 说人话的意思就是,启动遗嘱保留时,服务端要持久化该遗嘱消息(仅仅保留最新一条),其他人后订阅该遗嘱主题时,能收到最后一次的遗嘱消息。
总结一下连接标志字节的常见的情形
后面的实例,我们主要演示0xC2的情形。
CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。
需要注意的是每一个字段的拼接方式都遵循 MSB+LSB+Content的格式。 即在字段内容前拼入两个字节表示的大端序bit int来表示字段的长度,Content内容必须是UTF-8格式。
我们用mosquito 工具,模拟连接报文
mosquitto_sub -d -h 127.0.0.1 -p 1883 -u username/1 -P password -i clientid/1 -t "topic"
得到CONNECT报文如下
CONNACK报文是确认连接报文。即CONNECT报文的响应报文,报文内容会返回连接成功标志。
第1个字节是 连接确认标志,位7-1是保留位且必须设置为0。 第0 (SP)位 是当前会话(Session Present)标志。
当服务端建立连接时,会话是新创建的时,SP返回0;会话是之前持久化的会话,且客户端传的清理会话标志没有开启时,SP返回1。
第2个字节为连接返回码。
最常见的返回码即 0x00 (正常连接)、0x05(连接未授权)
无
PUBLISH是发布消息协议报文,双向都可以使用。
PUBLISH协议包的固定头会跟标志位进行变化。
第3位为重传标志,qos0以上的PUBLISH是要求对方返回ack的,没有收到ack时,会被重传,重传时,DUP位为1。所以qos不能传1。
第2-1位为Qos标志,用两位表示qos 0、1、2,与CONNECT可变报头的连接标志一样。
第0位为保留消息标志,同样1表示启用,0表示不启用。这里的保留消息与前面提到遗嘱保留消息差不多,都是指,要求服务端收到PUBLISH时,持久化最新的一条消息,以便后来订阅主题的连接能够收到最近的一条消息。
总结一下PUBLISH包的固定报头的第一个字节的常见的情形
注意:
Qos是服务质量等级。
0:表示只发一次
1:表示至少发一次
2:表示只发一次
可变报头按顺序包含主题名和报文标识符。
主题名:拼接方式均为 MSB+LSB+Content,主题名不能包含通配符。
报文标识符: Qos为1,2时,才传报文标识符。 由MSB+LSB组成的int值表示, 报文标识符自增id,每次建立连接后从1开始。
有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度。包含零长度有效载荷的PUBLISH报文是合法的。
我们用mosquito 工具,模拟PUBLISH报文
mosquitto_pub -d -h 127.0.0.1 -p 1883 -u username/1 -P password -i clientid/1 -t "topic" -m "message"
PUBACK报文是对QoS 1等级的PUBLISH报文的响应。
可变报头仅有报文标识符。
格式为 MSB+LSB。
PUBACK报文没有有效载荷。
PUBREC报文是对QoS等级2的PUBLISH报文的响应。它是QoS 2等级协议交换的第二个报文。
可变报头包含等待确认的PUBLISH报文的报文标识符。
格式为 MSB+LSB。
PUBREC报文没有有效载荷。
PUBREL报文是对PUBREC报文的响应。它是QoS 2等级协议交换的第三个报文。
可变报头包含与等待确认的PUBREC报文相同的报文标识符。
格式为 MSB+LSB。
PUBREL报文没有有效载荷。
PUBCOMP报文是对PUBREL报文的响应。它是QoS 2等级协议交换的第四个也是最后一个报文。
可变报头包含与等待确认的PUBREL报文相同的报文标识符。
格式为 MSB+LSB。
PUBCOMP报文没有有效载荷。
客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。
可变报头仅有报文标识符。
格式为 MSB+LSB。
SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表,它们表示客户端想要订阅的主题。服务端支持主题过滤器通配订阅。每一个过滤器后面跟着一个字节,这个字节被叫做 服务质量要求(Requested QoS)。它给出了服务端向客户端发送应用消息所允许的最大QoS等级。
SUBSCRIBE报文的有效载荷必须包含至少一对主题过滤器 和 QoS等级字段组合。
每一组主题过滤器和QoS组合的拼接格式为:
MSB+LSB+Content+Qos
Qos用一个字节表示,可能的值为 0x00、0x01、0x02
我们使用mosquito工具模拟SUBSCRIBE报文
mosquitto_sub -d -h 127.0.0.1 -p 1883 -u username/1 -P password -i clientid/1 -t "topic"
服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文。
SUBACK报文包含一个返回码清单,它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。
可变报头包含等待确认的SUBSCRIBE报文的报文标识符。
格式为 MSB+LSB。
有效载荷包含一个返回码清单。每个返回码对应等待确认的SUBSCRIBE报文中的一个主题过滤器。返回码的顺序必须和SUBSCRIBE报文中主题过滤器的顺序相同
允许的返回码值:
0x00 - 最大QoS 0
0x01 - 成功 – 最大QoS 1
0x02 - 成功 – 最大 QoS 2
0x80 - Failure 失败
0x00, 0x01, 0x02, 0x80之外的SUBACK返回码是保留的,不能使用
客户端发送UNSUBSCRIBE报文给服务端,用于取消订阅主题。
可变报头仅有报文标识符。
格式为 MSB+LSB。
UNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。
每一个主题过滤器的拼接格式为:
MSB+LSB+Content
服务端发送UNSUBACK报文给客户端用于确认收到UNSUBSCRIBE报文。
可变报头仅有报文标识符。
格式为 MSB+LSB。
UNSUBACK报文没有有效载荷。
客户端发送PINGREQ报文给服务端的心跳包。
PINGREQ报文没有可变报头。
PINGREQ报文没有有效载荷。
客户端发送PINGREQ报文给服务端的心跳包。
PINGRESP报文没有可变报头。
PINGRESP报文没有有效载荷。
DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。
DISCONNECT报文没有可变报头。
DISCONNECT报文没有有效载荷。