在 MQTT 5.0 报文介绍 中,我们介绍了 MQTT 报文由固定报头、可变报头和有效载荷三个部分组成,以及可变字节整数、属性这类 MQTT 报文中的通用概念。现在,我们将按照实际的用途来进一步介绍各个类型的报文的组成。首先,我们将专注于用于建立 MQTT 连接的报文。
如果我们想要使用 MQTT 进行通信,第一步必然是建立一个 MQTT 连接,而建立 MQTT 连接需要用到两个控制报文,它们分别是 CONNECT 报文与 CONNACK 报文。CONNECT 报文是客户端与服务端建立网络连接后,向服务端发送的第一个控制报文,用来发起连接请求。服务端将返回 CONNACK 报文告知客户端连接结果。
报文示例
我们使用 MQTTX CLI 向 公共 MQTT 服务器 发起一个连接,在这个连接中,我们将协议版本设置 MQTT 5.0,Clean Start 设置为 1,Session Expiry Interval 设置为 300 秒,Keep Alive 设置为 60,用户名和密码分别设置为 admin 和 public,对应的 MQTTX CLI 命令为:
mqttx conn --hostname broker.emqx.io --mqtt-version 5 \ --session-expiry-interval 300 --keepalive 60 --username admin --password public
以下是使用 Wireshark 工具抓取到的 MQTTX CLI 发出的 CONNECT 报文,Linux 环境可以先使用 tcpdump 命令抓取报文,然后再导入至 Wireshark 查看:
10 2f 00 04 4d 51 54 54 05 c2 00 3c 05 11 00 00 01 2c 00 0e 6d 71 74 74 78 5f 30 63 36 36 38 64 30 64 00 05 61 64 6d 69 6e 00 06 70 75 62 6c 69 63
但这是一串不易理解的十六进制字节,除非它们被转换成以下格式:
同样我们也抓取到了公共 MQTT 服务器返回的 CONNACK 报文:
20 13 00 00 10 27 00 10 00 00 25 01 2a 01 29 01 22 ff ff 28 01
在解析这串报文数据之后我们可以看到,CONNACK 报文的 Reason Code 为 0,表示连接成功,后面的多个属性则给出了服务器支持的功能列表,比如支持的最大报文长度,是否支持保留消息等等:
当然,Wireshark 其实也已经为我们列出了报文中各个字段的值,通过下文对 CONNECT 和 CONNACK 报文结构的介绍,再结合 Wireshark 的抓包结果按图索骥,你将很快掌握这两个报文:
CONNECT 报文结构
固定报头
CONNECT 报文的固定报头中,位于首字节高 4 位的报文类型字段的值必须为 1(0b0001),首字节中低 4 位则固定全为 0。
所以,CONNECT 报文的第一个字节的值必然为 0x10
,我们可以以此来判断某个报文是否为 CONNECT 报文。
可变报头
CONNECT 报文的可变报头按顺序包含以下字段:
Protocol Name:这是一个 UTF-8 编码的字符串,用来表示协议名称。在 MQTT 中,UTF-8 编码的字符串的前两个字节统一用于指示后面实际的字符数据的长度。MQTT 3.1.1 和 MQTT 5.0 中协议名称固定为
MQTT
,所以对应的以十六进制字节表示的完整内容就是00 04 4d 51 54 54
,其中4d 51 54 54
就是MQTT
这个字符串对应的 ASCII 值。最早的 MQTT 3.1 中的协议名称是MQIsdp
,所以它对应的是00 06 4d 51 49 73 64 70
。Protocol Version:这是一个单个字节长度的无符号整数,用来表示协议版本。目前只有三个可取值,3 表示 MQTT 3.1,4 表示 MQTT 3.1.1,5 表示 MQTT 5.0。
Connect Flags:连接标识,它只有一个字节,但包含了多个用于控制连接行为或指示有效载荷中某些字段是否存在的参数。
User Name Flag:用于指示有效载荷是否包含用户名字段。
Password Flag:用于指示有效载荷是否包含密码字段。
Will Retain:用于指示遗嘱消息是否为保留消息。
Will QoS:用于指示遗嘱消息的 QoS。
Will Flag:用于指示有效载荷是否包含了遗嘱消息的相关字段。
Clean Start:用于指示当前连接是一个新的会话还是一个已存在会话的延续,这决定了服务端将直接新建会话还是尝试复用已存在的会话。
Reserved:这是一个保留位,它的值必须为 0。
Keep Alive:这是一个双字节长度的无符号整数,用来表示客户端发送两个相邻的控制报文的最大时间间隔。
Properties:下表列出了 CONNECT 报文的所有可用属性。
Identifier | Property Name | Type |
---|---|---|
0x11 | Session Expiry Interval | 四字节整数 |
0x21 | Receive Maximum | 双字节整数 |
0x27 | Maximum Packet Size | 四字节整数 |
0x22 | Topic Alias Maximum | 双字节整数 |
0x19 | Request Response Information | 单字节 |
0x17 | Request Problem Information | 单字节 |
0x26 | User Property | UTF-8 字符串对 |
0x15 | Authentication Method | UTF-8 编码的字符串 |
0x16 | Authentication Data | 二进制数据 |
有效载荷
CONNECT 报文有效载荷中的字段,除了 Client ID 以外,其他字段都是可选的,它们是否存在取决于可变报头的 Connect Flags 中对应标志位的值。但如果这些存在,就必须按照 Client ID、Will Properties、Will Topic、Will Payload、User Name、Password 的顺序出现。
CONNACK 报文结构
固定报文
固定报头中首字节的高 4 位值为 2(0b0010),表示这是一个 CONNACK 报文。
可变报头
CONNACK 报文的可变报头按顺序包含以下字段:
Connect Acknowledge Flags:连接确认标志。
Reserved (Bit 7 - 1):保留位,必须设置为 0.
Session Present (Bit 0):用于指示服务端是否正在使用已存在的会话与客户端恢复通信。仅在客户端在 CONNECT 连接中将 Clean Start 设置为 0 时,Session Present 可能为 1。
Reason Code:用于指示连接结果。下表列出了一些在 CONNACK 报文中常见的 Reason Code,完整列表可参阅 MQTT 5.0 Reason Code 速查表。
Value | Reason Code Name | Description |
---|---|---|
0x00 | Success | 连接被接受。 |
0x81 | Malformed Packet | 服务端无法按照协议规范正确解析 CONNECT 报文,例如保留位没有按照协议要求设置为 0。 |
0x82 | Protocol Error | CONNECT 报文可以被正确解析,但是内容不符合协议规范,比如 Will Topic 字段的值不是一个合法的 MQTT 主题。 |
0x84 | Unsupported Protocol Version | 服务端不支持客户端所请求的 MQTT 协议版本。 |
0x85 | Client Identifier not valid | 表示 Client ID 是有效的字符串,但是不被服务端接受,比如 Client ID 超出了服务端允许的最大长度。 |
0x86 | Bad User Name or Password | 客户端因为使用了错误的用户名或密码而被拒绝连接。 |
0x95 | Packet too large | CONNECT 报文超过了服务端允许的最大长度,可能是因为携带了较大的遗嘱消息。 |
0x8A | Banned | 表示客户端被禁止登录。例如服务端检测到客户端的异常连接行为,所以将这个客户端的 Client ID 或者 IP 地址加入到了黑名单列表中,又或者是后台管理人员手动封禁了这个客户端,当然以上这些通常需要视服务端的具体实现而定。 |
- Properties:下表列出了 CONNACK 报文的所有可用属性。
Identifier | Property Name | Type |
---|---|---|
0x11 | Session Expiry Interval | 四字节整数 |
0x21 | Receive Maximum | 双字节整数 |
0x24 | Maximum QoS | 单字节 |
0x25 | Retain Available | 单字节 |
0x27 | Maximum Packet Size | 四字节整数 |
0x12 | Assigned Client Identifier | UTF-8 编码的字符串 |
0x22 | Topic Alias Maximum | 双字节整数 |
0x1F | Reason String | UTF-8 编码的字符串 |
0x26 | User Property | UTF-8 字符串对 |
0x28 | Wildcard Subscription Available | 单字节 |
0x29 | Subscription Identifier Available | 单字节 |
0x2A | Shared Subscription Available | 单字节 |
0x13 | Server Keep Alive | 双字节整数 |
0x1A | Response Information | UTF-8 编码的字符串 |
0x1C | Server Reference | UTF-8 编码的字符串 |
0x15 | Authentication Method | UTF-8 编码的字符串 |
0x16 | Authentication Data | 二进制数据 |
有效载荷
CONNACK 报文不包含有效载荷。
总结
CONNECT 是客户端与服务端的网络连接建立后,客户端发送的第一个 MQTT 报文,CONNACK 作为 CONNECT 的响应报文通过原因码来指示连接结果。
客户端和服务端需要借助 CONNECT 和 CONNACK 报文来完成必要信息的交换,例如客户端使用的协议版本、Client ID、用户名、密码,以及服务端是否存在相应的会话、支持的最大报文长度和最大 QoS 等级等等。
以上就是对 MQTT CONNECT 和 CONNACK 报文的介绍,在后续的文章中,我们还会继续研究 PUBLISH、DISCONNECT 这些报文的结构和组成。