MQTT协议是物联网通信协议中最常用的通信协议,因此对这个协议的充分理解是非常重要的。但是也不用产生畏惧的心理,毕竟mqtt基础协议也是基于TCP协议的又一层封装。今天就是使用电脑连接服务器进行一下mqtt协议的字节剖析。
本文仅发布在CSDN 青云双青 和我的个人博客,其他地方出现均为搬运
一、说明
1、本次抓包分享仅仅是做简单的技术分享,形成一个大的框架,后续的可以自行继续探究。
2、本次分享仅仅是做一个相关的分享,为的是理解协议的实现,没有太多的实际意义。
3、由于mqtt协议用的时间很长了,但是一直没有去深究它的具体的实现,想真正探究一下。
4、本次讲解可能存在的问题以及解释不到的地方,希望可以包容,个人能力有限。
5、本笔记写于2022-02-07 By Gear Long.
二、鸣谢
这些链接是一些中文的资料,本次分析也是基于3.1.1版本进行的,最新版本已经更新到了5.0版本,但是5.0版本在安全性上就上升了一个维度,比较难就先不说了。
三、需要准备
- CH340串口调试模块一个
- ESP8266模块(烧录好AT固件)
烧录好可以进行MQTT通信的AT固件
- windows10 操作系统的电脑一台
- 安装最新版本的wireshark软件
wireshark 抓包利器,内置多种网络协议,可以分析多种网络通信协议。
- mqtt测试软件(推荐用mqttx)或者使用在线的mqtt测试平台
四、协议分析
以下为分析的内容:
CONNECT到服务器端
【固定报头】
CONNECT报文的固定的报头
byte1 MQTT 报文类型
0001 0000
byte2 剩余的长度
整理:10 ??
一共两个字节
剩余长度是暂时未知的,因此需要等待后面的全部数据得到后才能计算
剩余长度字段计算:
【剩余长度】 = 【可变报头的长度】 + 【有效载荷的长度】
然后将10进制转换成16进制
【可变报头】
可变报头是由四个部分组成的【协议名】 + 【协议级别】+ 【连接标志】+【保持连接】
1、【协议名】
协议名:协议名是表示协议名 MQTT 的UTF-8编码的字符串。MQTT规范的后续版本不会改变这个字符串的偏移和长度。
[协议名]
byte1 长度MSB(值0) 0000 0000
byte2 长度LSB(值4) 0000 0100
byte3 'M' 0100 1101
byte4 'Q' 0101 0001
byte5 'T' 0101 0100
byte6 'T' 0101 0100
整理:00 04 4d 51 54 54
一共6个字节
2、【协议级别】
客户端用8位的无符号值表示协议的修订版本。
[协议级别]
byte1 0000 0100
整理:04
一共是一个字节
3、【连接标志】
连接标志字节包含一些用于指定 MQTT 连接行为的参数。它还指出有效载荷中的字段是否存在。
[连接标志]
本次连接设定的有:(经过抓包后反推的)
User Name Flag + Password Flag + Clean Session (此出说明一下)
byte1 1100 0010
整理:c2
一共一个字节
4、【保持连接】
它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔。
[保持连接]
byte1 保持连接 Keep Alive MSB 0000 0000
byte2 保持连接 Keep Alive LSB 0011 1100
整理:00 3c
一共是两个字节
0x3c转换成十进制就是60s
若是设置成100s 那么对应的十六进制就是 0x64
一个示例
可变报头的转换结果:
00 04 4d 51 54 54 04 c2 00 3c
【有效载荷】
1、【客户端ID】
[客户端ID]
本次的客户端ID设置为:12345ABCD
byte1 数据长度MSB 0000 0000
byte2 数据长度LSB 0000 1001
byte3~ 数据内容 依据密码的实际内容 31 32 33 34 35 41 42 43 44
转换:00 09 31 32 33 34 35 41 42 43 44
一共11个字节
2、【用户名】
[用户名]
本次设置的用户名是:12345ABCD
byte1 数据长度MSB 0000 0000
byte2 数据长度LSB 0000 1000
byte3~ 数据内容 依据密码的实际内容 31 32 33 34 35 41 42 43
转换:00 08 31 32 33 34 35 41 42 43
一共10个字节
3、【用户密码】
有效载荷就是要载荷一些内容了,包括用户名、密码等。
和上面的连接标志对应。
[用户密码]
本次的密码设置为:123Abc
byte1 数据长度MSB 0000 0000
byte2 数据长度LSB 0000 0110
byte3~ 数据内容 依据密码的实际内容 31 32 33 41 62 63
转换:00 06 31 32 33 41 62 63
一共8个字节
全部整理
固定报头:
10 ?? = 10 27
?? = 10 + 11 +10 +8 = 39
十进制的39转换成为十六进制的0x27
可变报头:
00 04 4d 51 54 54 04 c2 00 3c (10)
有效载荷:
客户端:00 09 31 32 33 34 35 41 42 43 44 (11)
用户名:00 08 31 32 33 34 35 41 42 43 (10)
密码:00 06 31 32 33 41 62 63 (8)
总的数据:
10 27 00 04 4d 51 54 54 04 c2 00 3c 00 09 31 32 33 34 35 41 42 43 44 00 08 31 32 33 34 35 41 42 43 00 06 31 32 33 41 62 63
然后,回到开始的剩余长度的计算。
【WireShark的抓包结果】
抓的是第一个包
抓包结果和咱们的自己分析的结果一模一样
发送注意事项
- 选择到16进制发送就是HEX
- 不要自动换行
- 发送时大小写均可
- 发送完毕
【CONNECT服务器确认响应】
服务端发送 CONNACK 报文响应从客户端收到的 CONNECT 报文,服务端发送给客户端的第一个报文必须是 CONNACK.
抓包连接返回码
抓的是下面的返回的包,第二条的
确认连接请求的格式
[返回码]
byte1 MQTT控制报文类型 0010 0000
byte2 剩余长度(值为2) 0000 0010
byte3 0000 0000
byte4 0000 0000
整理:20 02 00 00
一共4个字节,查询返回码后是连接已经被服务器接受
PUBLISH 发布消息
在上面的连接到服务器的操作以后就可以继续进行下面的发布消息或者订阅消息的报文了。
【固定报头】
[固定报头]
QoS等级值为00 表示至多分发一次
byte1 MQTT控制报文类型(值为3) 0011 0000
byte2 剩余长度 ??
转换: 30 ??
一共两个字节
【服务质量等级QoS】
【可变报头】
可变报头按顺序包含主题名和报文标识符。
1、【主题名】
MQTT协议里面需要的部分,要发布的主题的名字
[主题名字]
本次发布主题的名字:/python/AQAQ
byte1 Length MSB (值为0) 0000 0000
byte2 Length LSB (值为12) 0000 1100
byte3~ 主题名字 2f 70 79 74 68 6f 6e 2f 41 51 41 51
转换:00 0c 2f 70 79 74 68 6f 6e 2f 41 51 41 51
一共14个字节
2、【报文标识符】
只有当 QoS 等级是 1 或 2 时,报文标识符(Packet Identifier)字段才能出现在 PUBLISH 报文中,本次设置QoS等级为0因此不存在报文标识符号。
一个示例:
【有效载荷】
有效载荷包含将被发布的应用消息
[发布消息体的内容]
本次发布消息的内容为:20220206ABC
byte1~ 32 30 32 32 30 32 30 36 41 42 43
整理:32 30 32 32 30 32 30 36 41 42 43
一共11个字节
全部整理:
固定报头:
30 ?? = 30 19
?? = 14 + 11 = 25
十进制的25转换成16进制的0x19
可变报头:
00 0c 2f 70 79 74 68 6f 6e 2f 41 51 41 51 (14)
有效载荷:
32 30 32 32 30 32 30 36 41 42 43 (11)
总的数据:
30 19 00 0c 2f 70 79 74 68 6f 6e 2f 41 51 41 51 32 30 32 32 30 32 30 36 41 42 43
【WireShark抓包结果】
wireshark抓包
第一条mqtt协议分析
【PUBLISH发布响应】
本次操作使用的是QoS 0的服务质量等级,因此无响应。
其余的质量等级测试可自行测试。
SUBSCRIBE 订阅消息
订阅消息,等待接收信息
【固定报头】
byte1 MQTT 控制报文类型(值为8) 1000 保留位值为2 0010
byte2 剩余长度 ??
整理:82 ??
一共2个字节
【可变报头】
可变报头包含客户端标识符
[可变报头]
抓包的可变报头字节和文章写的有出入,因此按照抓包的结果为准
byte1 报文标识符 MSB (0) 0000 0000
byte2 报文标识符 LSB (10) 0000 0001
整理:00 01
一共2个字节
一个示例:
【有效载荷】
SUBSCRIBE 报文的有效载荷包含了一个主题过滤器列表,它们表示客户端想要订阅的主题
[有效载荷]
本次订阅的主题为:/python/QAQA
byte1 长度 MSB 0000 0000
byte2 长度 LSB 0000 1100
byte3~ 主题过滤器列表 2f 70 79 74 68 6e 2f 51 41 51 41
byte~ 服务质量要求 设置为QoS0 0000 0000
整理:00 0c 2f 70 79 74 68 6e 2f 51 41 51 41 00
一共15个字节
全部整理:
固定报头:
82 ?? = 82 11
?? = 2 + 15 = 17
十进制的17 转换成16进制的0x11
可变报头:
00 01
有效载荷:
00 0c 2f 70 79 74 68 6e 2f 51 41 51 41 00 (15)
总的数据:
82 11 00 01 00 0c 2f 70 79 74 68 6e 2f 51 41 51 41 00
【Wireshark抓包结果】
找到订阅请求
可以看到抓包结果和分析的相同
【SUBSCRIBE订阅响应】
服务端发送 SUBACK 报文给客户端,用于确认它已收到并且正在处理 SUBSCRIBE 报文
[固定报头]
byte1 MQTT控制报文类型 1001 保留位 0000
byte2 剩余长度 0000 0011
整理:90 03
一共2个字节
[可变报头]
byte1 0000 0000
byte2 0000 0001
整理:00 01
一共2个字节
[报文有效载荷格式]
byte1 0000 0000
整理:00
查看规则为最大QoS 0
一共1个字节
总的数据:
90 03 00 01 00
抓包数据
服务器订阅响应抓包
PINGREQ 心跳请求
告知服务器,客户端还在活着,请求询问服务器,确认服务器是不是还在活着,确认网络畅通
【固定报头】
[固定报头]
byte1 MQTT控制报文类型(值12) 1100 0000
byte2 剩余长度 0000 0000
整理: c0 00
一共2个字节
【可变报头】
无
【有效载荷】
无
【PINGRESP响应】
服务器告诉客户端,我还活着
[响应的固定报头]
byte1 MQTT 控制报文类型 (13) 1101 0000
byte2 剩余长度0 0000 0000
整理:d0 00
一共2个字节
最后
可以使用wireshark抓包软件对mqtt协议进行深层次的了解
每次向服务器成功发送报文的话是会收到来自服务器的答复的