最近学习MQTT协议和与Onenet平台的数据交互。MQTT中有几个方法,想借此机会将每个方法的流程梳理一下
一.MQTT数据包结构
在MQTT协议中,一个MQTT数据包由:固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成。MQTT数据包结构如下:
(1)固定头(Fixed header)。存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
(2)可变头(Variable header)。存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
(3)消息体(Payload)。存在于部分MQTT数据包中,表示客户端收到的具体内容。
二.CONNECT
2.1 Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT Packet Type | 0 | 0 | 0 | 0 | |||
byte2 - 5 | Remaining Length(该字段占用1-4个字节) |
MQTT数据包类型:位于Byte1的第7~4位
名字 | 值 | 流向 | 描述 |
CONNECT | 1 | C->S | 客户端请求与服务端建立连接 |
CONNACK | 2 | S->C | 服务端确认连接建立 |
PUBLISH | 3 | 双向 | 发布消息 |
PUBACK | 4 | 双向 | 收到发布消息确认 |
PUBREC | 5 | 双向 | 发布消息收到 |
PUBREL | 6 | 双向 | 发布消息释放 |
PUBCOMP | 7 | 双向 | 发布消息完成 |
SUBSCRIBE | 8 | C->S | 订阅请求 |
SUBACK | 9 | S->C | 订阅确认 |
UNSUBSCRIBE | 10 | C->S | 取消订阅 |
UNSUBACK | 11 | S->C | 取消订阅确认 |
PING | 12 | C->S | 客户端发送PING(连接保活)命令 |
PINGRSP | 13 | S->C | PING命令回复 |
DISCONNECT | 14 | C->S | 断开连接 |
从上面我们可以看到CONNECT应该是1
2.2 Variable Header
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1-2 | ProtocolName Length | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
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 |
Byte7 | Protocol Level | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
Byte8 | Connect Flag | User flag | Password flag | WillRetain Flag | WillQos Flag | WillFlag | CleanSession Flag | Reserve | |
Byte9-10 | KeepAlive |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Clean Session
0,表示如果订阅的客户机断线了,要保存为其要推送的消息(QoS为1和QoS为2),若其重新连接时,需将这些消息推送(若客户端长时间不连接,需要设置一个过期值)。
session需保持的内容包含:
- 客户端订阅的topic列表。
- 还未完成确认的Qos1、Qos2级别的publish消息
1,断线服务器即清理相关信息,重新连接上来之后,会再次订阅。
Will Flag
定义了客户端(没有主动发送DISCONNECT消息)出现网络异常导致连接中断的情况下,服务器需要做的一些措施。
简而言之,就是客户端预先定义好,在自己异常断开的情况下,所留下的最后遗愿(Last Will),也称之为遗嘱(Testament)。 这个遗嘱就是一个由客户端预先定义好的主题和对应消息,附加在CONNECT的可变头部中,在客户端连接出现异常的情况下,由服务器主动发布此消息。
只有在Will Flag位为1时,Will Qos和Will Retain才会被读取,此时消息体payload中要出现Will Topic和Will Message具体内容,否则,Will QoS和Will Retain值会被忽略掉。
Will Qos
两位表示,和PUBLISH消息固定头部的QoS level含义一样。这里先掠过,到PUBLISH消息再回过头来看看,会更明白些。
若标识了Will Flag值为1,那么Will QoS就会生效,否则会被忽略掉。
Will RETAIN
如果设置Will Flag,Will Retain标志就是有效的,否则它将被忽略。
当客户端意外断开服务器发布其Will Message之后,服务器是否应该继续保存。这个属性和PUBLISH固定头部的RETAIN标志含义一样,这里先掠过。
User name 和 password Flag:
用于授权,两者要么为0要么为1,否则都是无效。否则认为协议错误,平台将会断开连接。都为0,表示客户端可自由连接/订阅,都为1,表示连接/订阅需要授权。
2.3 PAYLOAD
| Description | 是否必须存在 | 格式 |
Field1 | Client Identifier | 是 | 2字节字串长度 + utf8字串 |
Field2 | UserName | 是 | 2字节字串长度 + utf8字串 |
Field3 | UserPassword | 是 | 2字节字串长度 + utf8字串 |
与鉴权相关的字段包含client id,username和password,支持鉴权方式。
设备ID + APIKey(项目ID也需要填写)
字段设置 | 消息示例 |
client_id设置为平台创建设备时的设备id username设置为“项目ID” password设置为“鉴权信息(auth_info)” | client_id=”123” username=”433223” password=Api Key |
项目ID:在平台添加项目时平台生成的ID
APIKey:在平台上创建产品时生成的APIKey
2.4 代码流程
MQTT_PacketConnect()是组Connect数据包的过程。按照上面说的各个不同字段意思进行组建。
三.CONNACK
接收到CONNECT消息之后,服务器应该返回一个CONNACK消息作为响应。
- 若客户端绕过CONNECT消息直接发送其它类型消息,服务器应关闭此非法连接 若客户端发送CONNECT之后未收到CONNACT,需要关闭当前连接,然后重新连接
- 相同Client ID客户端已连接到服务器,先前客户端必须断开连接后,服务器才能完成新的客户端CONNECT连接 客户端发送无效非法CONNECT消息,服务器需要关闭
3.1 Fixed Header
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT Packet Type | 0 | 0 | 0 | 0 | |||
byte2 - 5 | Remaining Length(该字段占用1-4个字节) |
3.2 Variable Header
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | Acknowledge Flags | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Sp |
byte 2 | Return Code | x | x | x | x | x | x | x | x |
返回码说明:
返回码 | 描述 |
0 | 成功 |
1 | 协议版本错误 |
2 | 非法的clientid |
3 | 服务不可用 |
4 | 用户名或密码错误 |
5 | 非法链接(比如token非法) |
失败:
如果connect包不符合协议内容约束,则直接断掉连接,而不需要发送connack包.
如果鉴权或授权失败,回复一个带非0错误码的connack包.
成功:
必须断掉重复的clientid.
执行cleansession 对应的操作.
必须回复一个connack,回复码为0.
开始消息传递,并加入keepalive的监视.
PS:客户端需要等到服务端的connack报文,才能发送后续的数据包.
四.PINGREQ
当和平台连接成功后,每隔一段时间clint要向server发送pingreq,证明还连接着。一共两个字节。
客户端会在一个心跳周期内发送一条PINGREQ消息到服务器端。
五.PINGRESP
服务器收到PINGREQ请求之后,会立即响应一个两个字节固定格式的PINGRESP消息
服务器一般若在1.5倍的心跳周期内接收不到客户端发送的PINGREQ,可考虑关闭客户端的连接描述符。此时的关闭连接的 行为和接收到客户端发送DISCONNECT消息的处理行为一致,但对客户端的订阅不会产生影响(不会清除客户端订阅数 据).
若客户端发送PINGREQ之后的一个心跳周期内接收不到PINGRESP消息,可考虑关闭TCP/IP套接字连接。