Rice我叫加饭? · 2021年05月07日

教你动手写网络协议栈-MQTT报文解析6-解析

首发:Rice 嵌入式开发技术分享
作者:RiceDIY

教你动手写网络协议栈系列文章

1《教你动手写UDP协议栈-UDP协议栈格式》

2《教你动手写UDP协议栈-DHCP报文解析》

3《教你动手写UDP协议栈-OTA上位机》

4《教你动手写UDP协议栈-DNS报文解析》

5《教你动手写UDP协议栈-CoAP报文解析 》

6《教你动手写网络协议栈-MQTT报文解析-实践 》

7《教你动手写网络协议栈-MQTT报文解析-解析 》

概述

  • 在上一篇文章,直接在本地搭建了服务器和客户端,简单的实践了MQTT的用法。而这一篇来解析MQTT的报文格式。MQTT的报文字段很精简。但是解析起来还是有些复杂的。
  • 解析报文最好的工具是采用wireshark抓包,不过我发现,wireshark的2.xxx的版本无法进行回环抓包(即无法抓取127.0.0.1的数据报文)。通过一番度娘,发现新版本的wireshark用Npcap替换WinPcap,Npcap是基于WinPcap 4.1.3开发的,api兼容WinPcap。下载链接:https://www.wireshark.org/dow...
  • MQTT的标准可以参考如下网站:http://docs.oasis-open.org/mq...\_Toc398718030

MQTT报文

MQTT报文格式

  • MQTT的报文字段主要包含3部分,如下表:

image.png

MQTT固定报文头[Fixed header]

  • 每个MQTT报文都包含一个固定报文头,固定报文头部格式如下:

`  bit    7     6     5     4     3     2     1     0
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|Byte1|   MQTT控制报文类型    |指定控制报文类型的标志 |
+-----+-----------------------------------------------|
|Byte2|                   剩余长度                    |
+-----+-----------------------------------------------|
| ... |                   剩余长度                    |
+-----------------------------------------------------|
`

MQTT控制报文类型
  • MQTT的控制报文类型在固定报文头的第1个字节的4 ~ 7bit,共4位无符号值。这些值如下表描述:

image.png

MQTT控制报文标志
  • MQTT的控制报文标志在固定报文头的第1个字节的4 ~ 7bit,包含每个MQTT报文类型的特定的标志。
  • 注意:如果接收方收到非法的标志,接受者必须关闭网络连接。标志如下表:
  • 其中:
  • DUP:控制报文的重复分发标志
  • QoS:PUBLISH报文的服务质量等级
  • RETAIN:PUBLISH报文的保留标志

image.png

MQTT报文剩余长度
  • 剩余长度字段从固定报文头的第2个字节开始,最长可达4个字节,所以剩余长度访问是Byte[2 ~ 5]。
  • 剩余长度表示当前报文剩余部分的字节数,包含可变头部和Payload。
  • 上面的描述,那么怎么确定其长度用几个字节来描述呢?答案:取决于字节的最高位Bit7(默认都是在搞自己在前);如果Bit7为1,那么需要继续计算字节长度,如果Bit7为0,那么不需要继续计算字节长度。
  • 消息长度可以简单理解为128禁止的数据,4位长度最大可以表示:128 * 128 * 128 * 128 Byte = 256MB。
  • 需注意计算规则,低位在前,高位在后,字节最高位Bit7标记是否继续计算消息长度。其消息长度范围如下表:

image.png

  • 举例:
  • 第一字节最高位是1,需要继续向后计算,去掉标记位(0xC1%128),得到1000001=41
  • 第二字节最高位是1,需要继续向后计算,去掉标记位(0xC2%128),得到1000010=42
  • 第三字节最高位是0,不需要向后计算,其结果就是0x33=51
  • 因为低位在前,高位在后,即消息长度为:41 + 42 * 128 + 51 * 128 * 128=841001Byte = 821KB
  • 0xC1 = 11000001
  • 0xC2 = 11000010
  • 0x33 = 00110011
  1. 消息长度是0x60,其二进制是01100000b,字节最高位Bit7位0,所以不需要往后计算,其十进制是96(即消息长度为96个字节)。
  2. 消息长度是0xC1, 0xC2, 0x33。分别二进制为:

MQTT可变报文头[Variable header]

  • 在某些MQTT控制报文包含了一个可变报文头部分,它在固定报文头和payload之间,可变报头的内容根据报文类型的不同而不同,可变报头的报文标识符(Packet Identifier)字段存在与多个类型的报文里。可变报头其实就是MQTT开发中使用的Packet ID,通过Packet ID 进行一些操作确认。包含Packet ID的报文类型如下:

image.png

  • Packet ID默认是从1开始并自增,如果一个Packet ID被用完后,这个Packet ID可以被重用。对于PUBLISH(QoS 1)来说,如果发送端接收到PUBACK,那么这个Packet ID就用完了。对于PUBLISH(QoS 2),如果接收方收到PUBCOMP,那么这个Packet ID就用完了。对于SUBSCRIBE和UNSUBSCRIBE,Packet ID使用完成的标记是发送方收到了对应的SUBACK和UNSUBACK。

MQTT数据段[Payload]

  • MQTT中有些报文类型是包含Payload
  • 如PUBLISH的Payload指消息内容。
  • 如CONNECT的Payload指Client Identifier,Will Topic,Will Message,Username,Password等信息
  • 包含Payload的报文类型如下:

image.png

通过wireshark分析MQTT报文

  • 因为我们的服务器和客户端是在PC上搭建的,所以需要通过wireshark的回环抓包,来分析报文类型CONNECT和CONNACK。
  1. 打开wireshark,选择进行回环抓包

image.png

  1. 使用MQTT.fx创建一个客户端,点击连接,便可以抓到CONNECT和CONNACK的报文。

image.png

  1. 红色圈子的报文类型CONNECT的内容:

image.png

内容:  
10 25  
00 04 4d 51 54 54  
04  
c2  
00 3c  
00 08 63 6c 69 65 6e 74 30 31  
00 05 61 64 6d 69 6e  
00 08 31 32 33 34 35 36 37 38  
  1. 报文类型CONNECT内容分析:
  • 0x10:高四位0001,代表报文类型:CONNECT。
  • 0x25:二进制-0010 0101,Bit7为0,所以剩余长度只有一个字节长,即0x25十进制:37个字节
  • 0x00 0x04 0x4d 0x51 0x54 0x54:其中-0x00,0x04表示协议长度;0x4d 0x51 0x54 0x54对应 "M", "Q", "T", "T"。
  • 0x04:代表MQTT版本号:v3.1.1
  • 0xc2: 次字节含义如下表,该字节描述紧跟数据有 User Name、Password,没有遗嘱设置,选择了清理会话方式与服务器连接。

image.png

  • 00 3c:对应十进制为60,即保持连接(Keep Alive)60秒,以秒为单位,它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔。客户端负责保证控制报文发送的时间间隔不超过保持连接的值。如果没有任何其它的控制报文可以发送,客户端 必须发送一个PINGREQ 报文。客户端随时可以发送ping指令,服务器如果发现在KeepAalive时间内没有收到客户端的消息,会自动断开与客户端建立的连接。
  • 00 08 63 6c 69 65 6e 74 30 31:其中-0x00, 0x08表示clientID的长度8个字节;0x63,0x6c,0x69,0x65,0x6e,0x74,0x30,0x31:代表client01。即是我们在MQTT.fx创建客户端的时候设置clientID。
  • 00 05 61 64 6d 69 6e:其中-0x00,0x05表示User Name的的长度5个字节;0x61,0x64,0x6d,0x69,0x6e:代表User Name为admin,即是我们在MQTT.fx创建客户端的时候设置User Name。
  • 00 08 31 32 33 34 35 36 37 38:其中-0x00,0x08表示User Name的的长度8个字节;0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38:代表Password为12345678,即是我们在MQTT.fx创建客户端的时候设置Password。
  1. 红色圈子的报文类型CONNACK的内容:

image.png

内容:  
20  
02  
00  
00  
  1. 报文类型CONACK内容分析:
  • 20:高四位0010,代表报文类型:CONNACK。
  • 02:二进制-0000 0010,Bit为0,所以剩余长度只有一个字节长,即0x02十进制:2个字节。
  • 00:可变头部的第一个字节的第0位连接确认。

image.png

  • 00:可变头部的第二个字节。

image.png

推荐阅读

更多嵌入式技术干货请关注Rice 嵌入式开发技术分享
推荐阅读
关注数
1754
内容数
51
一个周末很无聊的嵌入式软件工程师,写写经验,写写总结。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息