网上已有相关讲解,对协议分析得很详细,记录学习一下备用,原blog地址见末尾。
MQTT 3
MQTT 3 (当前版本3.1.1)是目前使用的最为广泛的MQTT协议标准。尽管MQTT5标准已经发布,并且带来了一些令人振奋的新特性,但是在整个应用场景上,从后台服务到消息中间件再到客户端SDK等环节上的产品升级并没有都完成,再加上既有部署的维护,业界从版本3到5的过渡可能会持续相当长一段时间,所以,对于刚加入物联网行业的生力军来说,现在来学习MQTT 3依然是一件很有意义的事情。
MQTT协议的工作方式
在一个QMTT协议中有三个角色会参与到整个通信过程,发布者(publisher)、代理(broker)和订阅者(subscriber)。有别于传统的客户端/服务器通讯协议,MQTT协议并不是端到端的,消息传递通过代理,包括会话(session)也不是建立在发布者和订阅者之间,而是建立在端和代理之间。代理解除了发布者和订阅者之间的耦合。
除了发布者和订阅者之间传递普通消息,代理还可以为发布者处理保留消息和遗愿消息,并可以更改服务质量(QoS)等级。
MQTT控制报文
MQTT协议工作在TCP之上,端和代理之间通过交换预先定义的控制报文来完成通信。MQTT报文有3个部分组成,并按下表顺序出现:
固定报头(fixed header) | 可变报头(variable header) | 荷载(payload) |
---|---|---|
所有报文都包含 | 部分报文包含 | 部分报文包含 |
所有的MQTT控制报文都有一个固定报头,期格式如下:
协议版本3定义了14种MQTT报文,用于建立/断开连接、发布消息、订阅消息和维护连接。固定报头的第一字节的4-7位的值指定了报文类型,其取值如下表。0和15为系统保留值;0-3位为标志位,依照报文类型有不同的含义,事实上,除了PUBLISH报文以外,其他报文的标志位均为系统保留。如果收到报文的标志位无效,代理应断开连接。
报文类型 | 值 | 描述 |
---|---|---|
CONNECT | 1 | 客户端向代理发起连接请求 |
CONNACK | 2 | 连接确认 |
PUBLISH | 3 | 发布消息 |
PUBACK | 4 | 发布确认 |
PUBREC | 5 | 发布收到(QoS2) |
PUBREL | 6 | 发布释放(QoS2) |
PUBCOMP | 7 | 发布完成(QoS2) |
SUBSCRIBE | 8 | 客户端向代理发起订阅请求 |
SUBACK | 9 | 订阅确认 |
UNSUBSCRIBE | 10 | 取消订阅 |
UNSUBACK | 11 | 取消订阅确认 |
PINGREQ | 12 | PING请求 |
PINGRESP | 13 | PING响应 |
DISCONNECT | 14 | 断开连接 |
固定报头的第二字节起表示报文的剩余长度。最大4个字节,每字节可以编码至127,并含有一位继续位,如继续位非0,则下一字节依然为剩余长度。由此,理论上一个控制报文最长可以到256MB。
一些报文在固定报头和荷载之间可以有一个可变报头。可变报头的内容根据报文类型不同而不同。最常见的可变报头是报文标识符(PacketIdentifier)。
一些报文可以在最后携带一个荷载。不同的报文可以无荷载,可选荷载,或必须带有荷载。
CONNECT报文
CONNECT是客户端连接到代理的第一个报文,如果在连接已经存在,代理收到该报文将会断开现有连接。
CONNECT报文的固定报头
CONNECT报文的可变报头
CONNECT报文的可变报头由4部分组成:
- 协议名。协议名是UTF-8编码的大写的MQTT。
- 协议级别。MQTT 3.1.1的协议级别为4.
- 连接标志位。定义连接行为的参数。见下表。
- Keep Alive。2字节,客户端和代理之间的无活动时间超过该值后,应关闭连接。如果该值置0表示客户端不要求代理启用KEEPALIVE功能。
连接标志位:
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
用户名 | 密码 | 保留遗愿 | 遗愿QoS | 遗愿QoS | 遗愿 | 清除会话 | 保留(0) |
清除会话标志位:
这个标志位定义了如何处理会话状态。如果设置为0,客户端和代理可以恢复上一次连接时的会话状态,如果上一次连接的会话状态不存在,代理将会为客户端建立一个新的会话。如果该位设置为1,则双方将清除掉上一次连接的会话状态并建立一个新的会话。
遗愿标志位:
如果遗愿标志为1,则遗愿消息会被存储在代理上,当连接关闭时,代理将发布这个消息,除非在客户端断开连接时把遗愿消息清除了。
遗愿QoS标志位:
指定了遗愿消息的服务质量等级。
保留遗愿消息标志位:
指定在发布遗愿消息的时候,是否把该消息作为保留消息存储在代理。
用户名标志位:
如果设置为1,则用户名必须出现在荷载中,反之,用户名不允许出现在荷载中。
密码标志位:
如果该位为1,则密码必须出现在荷载中;如果该位为0,则密码不允许出现在荷载中。如果用户名标志位为0,则该位必须也为0。
CONNECT报文的荷载
CONNECT报文的荷载由一个或者多个字段组成,这些字段是否出现由可变报头中的标志位决定。字段总是以长度开始。字段出现的顺序必须是:客户端标识符,遗愿主题,遗愿消息,用户名,密码。
CONNECT报文的响应
在代理在为MQTT协议开放的端口上接收到TCP连接请求并建立连接后应该会收到CONNECT报文,如果在一定时间内代理没有收到CONNECT报文,则应该关闭这个TCP连接。
在收到CONNECT报文后,代理应该检查报文格式是否符合协议标准。如果不符合协议标准,代理应关闭连接,且不发送CONNACK报文给客户端。
代理可以检查CONNECT报文的内容并执行响应的认证和鉴权。如果这些检查没有通过,代理应该向客户端发送一个带有非0返回码的CONNACK报文。
CONNACK报文
CONNACK是代理用来响应客户端CONNECT的报文。代理向客户端发送的第一个报文必须是CONNACT。CONNACK有一个固定报头,一个可变报头,但是不带有荷载。
CONNACK的固定报头
CONNACT报文只有固定报头和一个2字节的可变报头,所以它的剩余长度总是2。
CONNACK报文的可变报头
CONNACK报文的可变报头为定长2字节。第一字节的0位表示是否有会话存在。如果代理上已经有请求连接的客户端的会话,且连接请求的清除会话标识为0,则该位为1,否则该位为0。客户端可以根据这一位的值采取响应行为,比如(重新)订阅主题等。
CONNACK报文的可变报头的第二字节为返回码。如果CONNECT请求的格式正确,但是代理依然不能允许客户端连接,则返回码为一个非零值。如果连接成功,则返回0。
返回码的定义:
值 | 返回码含义 |
---|---|
0 | 成功,连接请求被接受。 |
1 | 拒绝连接,不可接受的协议版本。 |
2 | 拒绝连接,不被允许的身份识别符(Client Identifier)。 |
3 | 拒绝连接,服务器不可用。 |
4 | 拒绝连接,无效的用户名和密码。 |
5 | 拒绝连接,客户端无授权。 |
6-255 | 系统保留。 |
客户端接受到代理的CONNACK的返回码为0,则连接建立完成,双方可以开始通信。
清除会话、保留消息和QoS的组合
清除会话、保留消息等概念,在传统的客户端/服务器方式的通信中不一定会出现,这些概念有时候不太容易理解,特别是当他们被组合起来用的时候。
下面的表格汇总了当一个客户端连接上来时,它能收到消息的各种情况。
清除会话位 | 保留位 | 订阅QoS | 发布QoS | 可收到的消息 |
---|---|---|---|---|
Y | N | 0 | 0 | N |
Y | N | 0 | 1 | N |
Y | N | 1 | 0 | N |
Y | N | 1 | 1 | N |
N | N | 0 | 0 | N |
N | N | 0 | 1 | N |
N | N | 1 | 0 | N |
N | N | 1 | 1 | Y,会话全部消息 |
Y | Y | 0 | 0 | Y,最后一条消息 |
Y | Y | 0 | 1 | Y,最后一条消息 |
Y | Y | 1 | 0 | Y,最后一条消息 |
Y | Y | 1 | 1 | Y,最后一条消息 |
N | Y | 0 | 0 | Y,最后一条消息 |
N | Y | 0 | 1 | Y,最后一条消息 |
N | Y | 1 | 0 | Y,最后一条消息 |
N | Y | 1 | 1 | Y,会话全部消息 |