MQTT协议-报文分析及网络客户端报文测试(MQTT报文连接阿里云上传数据+订阅数据)
Posted 永相随1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MQTT协议-报文分析及网络客户端报文测试(MQTT报文连接阿里云上传数据+订阅数据)相关的知识,希望对你有一定的参考价值。
文章目录
- 一、本文章所涉及到的内容
- 二、感性认识MQTT协议
- 三、准备信息
- 四、MQTT报文分析
- 五、使用PUBLISH报文发布消息(应用)
一、本文章所涉及到的内容
1、MQTT协议各种报文的介绍及运用
2、MQTT协议在连接阿里云使用过程中客户端ID,用户名的获取及使用哈希算法进行加密计算
3、网络调试助手TCP客户端手动测试每一条MQTT协议报文,了解整个流程
4、固定报头+可变报头+有效载荷的使用
5、固定报头中剩余长度的计算
6、客户端订阅云端消息(对订阅的报文数据使用倒推法求出发送数据的格式)
7、网络客户端通过PUBLISH报文向云端发布消息(开关值+温湿度,云端物模型进行显示)
二、感性认识MQTT协议
简单来说MQTT协议是基于Topic的订阅与推送,类似于用户与粉丝之间的关系。
发布和订阅的关系可以分为一对一,一对多,即只订阅(发布)一个主题,也可以同时订阅(发布)多个主题。两者之间可以是单向关系,比如说只发布消息,不订阅消息,或者是只订阅消息,不发布消息。两者也可以是双向关系,既订阅消息又发布消息。
举一个简单的例子:
比如说抖音用户张三(topic)和李四(topic),抖音这个平台(服务器)
1、张三在抖音平台关注了李四的账户
李四向抖音平台发布短视频的时候(即李四向服务器发布消息),由于之前张三已经是李四的粉丝(张三通过服务器订阅了李四),所有抖音平台会推送李四的短视频给张三,换句话说假若张三没有关注李四,平台肯定就不会第一时间给他推送了。
2、张三和李四互粉了
既然两个人互粉了,这样的情况下,在张三也向抖音平台发布短视频的时候,平台也会将张三的视频推送给李四(张三不仅可以收到李四视频的更新提醒,李四也会及时收到张三短视频的更新)
3、以上两点既包含了消息的发布,又包含了消息的订阅
4、所有数据的收和发均是通过topic进行的,在MQTT这个框架下张三和李四两者之间不会有任何直接的交流,所有的一切都是通过MQTT抖音这个服务器帮你转发(你先发到服务器,服务器看谁关注了你(订阅了你),就将其推送给谁)
MQTT运行框架
三、准备信息
(一)工具获取
需要用到的工具很少:电脑的计算器、MQTT协议的官方PDF、还有网络调试助手
网络调试助手+MQTT官方pdf–获取密码0hod
(二)获取信息
1、获取三元组信息
首先登陆阿里云物联网云平台,然后复制好三元组(ProductKey(产品),DeviceName(设备名),DeviceSecret(设备密钥))
可以看到在新建的产品下面有两个设备,有一个device2设备,此设备目前还处于未激活状态
可以点击device2设备进去之后复制我们看到的三元组信息
将三元组信息复制并保存
ProductKey:a1ndPQHcX7f
DeviceName:device2
DeviceSecret:1448e1c58996469449813ed68e3b0fed
2、获取发布topic和订阅topic
去自己创建的产品下复制并保存发布topic和订阅topic
设备属性上报:
是云下设备向云端(服务器)发布消息的topic,比如说我们要上传本地的温湿度、开关状态以及其他信息到云端
属性设置:
是服务器下发控制指令给云下设备去设置开关的状态
1、发布topic(设备属性上报)
模板:/sys/a1ndPQHcX7f/${deviceName}/thing/event/property/post
${deviceName}替换为自己的设备名device2
/sys/a1ndPQHcX7f/device2/thing/event/property/post
2、订阅topic(设备属性设置)
模板:/sys/a1ndPQHcX7f/${deviceName}/thing/service/property/set
同上替换为自己的设备名后topic如下
/sys/a1ndPQHcX7f/device2/thing/service/property/set
3、客户端ID,用户名,哈希加密
用来连接云端时使用(也可以直接使用软件生成)
如果使用软件生成参考此博客
前面我们已经获取到三元组,在此处会有用到之前保存的信息
ProductKey:a1ndPQHcX7f
DeviceName:device2
DeviceSecret:1448e1c58996469449813ed68e3b0fed
(1)客户端ID:
*|securemode=3,signmethod=hmacsha1| *是用DeviceName替换
替换结果如下:
device2|securemode=3,signmethod=hmacsha1|
(2)用户名:
*&# *是DeviceName替换,#是ProductKey替换
替换结果如下:
device2&a1ndPQHcX7f
(3)密码:
密码用加密网站计算(选择HmacSHA1):http://encode.chahuo.com/
用DeviceSecret作为密钥对
clientId*deviceName*productKey# 进行哈希加密 *是DeviceName,#ProductKey
先进行替换:
clientIddevice2deviceNamedevice2productKeya1ndPQHcX7f
客户端ID和用户名我们已经知道,接下来用网页进行哈希算法加密计算密钥(进去网页后先点击HmacSHA1,再将密钥和加密的字符串复制过去,再点击HmacSHA1即可生成密钥)
生成的密钥为:5caeef5b9251940696b02dc80b615c3f739f46bc
再次整理如下:
clientID:device2|securemode=3,signmethod=hmacsha1|
用户名:device2&a1ndPQHcX7f
密钥:5caeef5b9251940696b02dc80b615c3f739f46bc
4、连接服务器所使用的域名和端口号
我使用的阿里云服务器IP地址(华东2)
1、域名模板
${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com
分析如下:
${YourProductKey}替换为ProductKey(自己创建的产品名)
${YourRegionId}替换为地域代码
切换为我的产品名和地域代码后域名如下:
域名:a1ndPQHcX7f.iot-as-mqtt.cn-shanghai.aliyuncs.com
2、端口号:1883
四、MQTT报文分析
(一)MQTT报文
1、报文类型
C->S是客户端发布消息到服务器
S->C是服务器推送消息给客户端
2、报文结构
其实每一条报文都具有:固定报头、可变报头、有效载荷
(1)固定报头
(2)可变报头
(3)有效载荷
3、剩余长度的计算(重中之重)
剩余长度=可变报文+有效载荷
假设如下
类型 固定 可变 负载
个数 10个 10个
剩余长度=可变+负载=20个--->十六进制:14
固定报头第二个字节是十六进制14
一个字节0XFF最多255个,可变+负载难道只能250多个字符吗?如果多一点是不是也可以呢?肯定可以的,一个字节不够就用2个字节,还不够就用3个字节,但是最多4个字节(MQTT协议规定如下)
对于剩余长度的计算还是非常重要的:
实际计算方法如下:
1个字节的话是0-127(0x7f)
十进制100—>0x64 可以
十进制200->0xc8不行(正常情况下是可以的,但是在mqtt协议中不可以)
600->用两个字节十六进制258可以吗?但是mqtt不是这样的
一个字节有Bit7-Bit0,但是Bit7这个最高位不表示数据,可以将其看做标志位,因此造成一个字节最多0x7f(十进制127)
3.1十进制和MQTT协议中十六进制数据转化(重要)
已知数据个数求剩余长度在MQTT协议中用十六进制如何表示
例1:200
十进制200(正常十六进制是0xC8),但是在mqtt中不能认为十六进制C8就是是十进制200,原因如下:
如果现在是200,一个字节肯定不够用,可以看做128进1,即bit7标志位处置1,表示还需要第二个字节来帮助完成
200%128=72中72的十六进制是0x48化为二进制数据(0100 1000)
由于一个字节不够用最高位需要置一,故二进制数据(1100 1000)转化为十六进制是C8
200/128=1用十六进制用01表示
故在剩余长度中200用C8 01来表示,其中C8是48即十进制72,01即十进制128
剩余长度:C8 01表示
********************************************************************
例2:161
剩余长度如果是161则用一个字节表示不过来,还需要第二个字节做辅助
161%128=33,
其中33转化为十六进制为21
其中33转化为二进制数据:0001 0001
一个字节不够,高位置一:1001 0001-->转化为十六进制91
161/128=1用十六进制表示是01
所以剩余长度用十六进制表示:91 01
***********************************************************
例3:1000
1000%128=104的十六进制是68二进制为0110 1000
由于不够用高位标志位置一变为 1110 1000换算成16进制即E8
1000/128=7 ---用07表示
即1000在MQTT协议中用十六进制E8 07表示
3.2MQTT协议中十六进制数据转化为十进制(重要)
知道剩余长度的十六进制数据,求数据的个数,这个计算方式在借助定遇到的报文推断PUBLISH负载的数据格式时会用到
例如:
E8 07反拆的时候也一样,
E8:二进制为(1110 1000),不要第一个字节(高字节标志位不要)变为0110 1000
0110 1000转化为十六进制68,即十进制是104
07:表示128*7=896
128*7+104=1000
因此可以反求出数据个数为1000
4、报文等级
(1)等级0:我发送一条消息,不管你有没有回复收到,我都不再继续发
(2)等级1:我发送一条消息,一直等待你回复,直到你必须回复收到了才结束(至少听到一次你说收到了)
(3)等级3:我发送一条消息,你说收到了,我得再确认一遍你收到了吗,你又回复收到了,才算结束
等级0应用比较多,等级2一般不用,太费劲了。
(二)MQTT报文逐条分析正式开始
1、CONNECT – 连接服务端(C->S)----重要
客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是 CONNECT 报文
1.1固定报头
第一个字节十六进制为:10
第二个字节剩余长度值先这样表示:??
剩余长度=可变报头+有效载荷长度
固定报头:10 ??
1.2可变报头
协议名、协议等级、连接标志和保持连接时长
协议名:00 04 4D 51 54 54
协议级别:04
连接标志:C2
保持连接100秒:00 64
可变报头
协议名:00 04 4D 51 54 54
协议等级 04
连接标志:C2
保持连接100秒:00 64
可变报头共10个字节
固定报头:
10 ??
可变报头十六进制数据:
00 04 4D 51 54 54 04 C2 00 64
1.3有效载荷(重要)
客户端id、用户名、密钥–十六进制表示
固定报头:10 ??
可变报头:00 04 4D 51 54 54 04 C2 00 64
固定报头+可变报头
10 ?? 00 04 4D 51 54 54 04 C2 00 64
将下列信息转化为十六进制表示
客户端ID:
device2|securemode=3,signmethod=hmacsha1|
用户名:
device2&a1ndPQHcX7f
密码:
5caeef5b9251940696b02dc80b615c3f739f46bc
说一个简单方法:使用网络客户端进行数据转化
例如:将客户端ID转化为16进制
原始数据:device2|securemode=3,signmethod=hmacsha1|
device2|securemode=3,signmethod=hmacsha1|转化为十六进制数据为:
64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C
同理可以将用户名和密钥转化为十六进制数据
客户端ID:
device2|securemode=3,signmethod=hmacsha1|
十六进制数据:42个字节-->转化为十六进制:2A
64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 0A
用户名:
device2&a1ndPQHcX7f
十六进制数据:19个字节-->十六进制13
64 65 76 69 63 65 32 26 61 31 6E 64 50 51 48 63 58 37 66
密码:
5caeef5b9251940696b02dc80b615c3f739f46bc
十六进制数据:40个字节--->十六进制28
35 63 61 65 65 66 35 62 39 32 35 31 39 34 30 36 39 36 62 30 32 64 63 38 30 62 36 31 35 63 33 66 37 33 39 66 34 36 62 63
我们如果把客户端ID、用户名、密钥的十六进制依次衔接起来的话,就相当于没有标点符号,服务器也没法进行判断谁是谁,因此我们为了能够让服务器知道哪些数据代表谁,咱们给它加上标点符号(即数据长度)
比如说客户端ID转化为十六进制后是42个字节,十六进制用2A表示;用户名有19个字节,十六进制用13表示;密钥有40个字节,十六进制用28表示
即有效载荷表示形式如下:
00 2A (客户端ID十六进制数据)00 13 (用户名十六进制数据)00 28 (密钥十六进制数据)
这样的话加上数据长度就相当于有了标点符号
有效载荷为:
00 2A 64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 0A 00 13 64 65 76 69 63 65 32 26 61 31 6E 64 50 51 48 63 58 37 66 00 28 35 63 61 65 65 66 35 62 39 32 35 31 39 34 30 36 39 36 62 30 32 64 63 38 30 62 36 31 35 63 33 66 37 33 39 66 34 36 62 63
CONNECT报文合并版
固定报头:10 ??
可变报头:00 04 4D 51 54 54 04 C2 00 64
固定报头+可变报头
10 ?? 00 04 4D 51 54 54 04 C2 00 64
有效载荷:
00 2A 64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 0A 00 13 64 65 76 69 63 65 32 26 61 31 6E 64 50 51 48 63 58 37 66 00 28 35 63 61 65 65 66 35 62 39 32 35 31 39 34 30 36 39 36 62 30 32 64 63 38 30 62 36 31 35 63 33 66 37 33 39 66 34 36 62 63
connect报文: 固定报头+可变报头+有效载荷
10 ?? 00 04 4D 51 54 54 04 C2 00 64 00 2A 64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 0A 00 13 64 65 76 69 63 65 32 26 61 31 6E 64 50 51 48 63 58 37 66 00 28 35 63 61 65 65 66 35 62 39 32 35 31 39 34 30 36 39 36 62 30 32 64 63 38 30 62 36 31 35 63 33 66 37 33 39 66 34 36 62 63
在connect报文中还有两个??还没有替换,表示剩余长度=可变报头+有效载荷(即从??开始后面所有的数据共有多少个,可以复制到网络助手出查看并转化为十六进制数据去替换??)
将??后面的数据全部复制到网络端口可以看到共有114个数据(即剩余长度=可变报头+有效载荷=114用十六进制表示72,因此??处用72代替)
CONNECT报文最终版
固定报头+可变报头+有效载荷
10 72 00 04 4D 51 54 54 04 C2 00 64 2A 64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 0A 13 64 65 76 69 63 65 32 26 61 31 6E 64 50 51 48 63 58 37 66 28 35 63 61 65 65 66 35 62 39 32 35 31 39 34 30 36 39 36 62 30 32 64 63 38 30 62 36 31 35 63 33 66 37 33 39 66 34 36 62 63
接下来就可以用网络助手建立TCP客户端连接服务器了
首先建立TCP客户端,然后将事先准备好的域名和端口号复制过去,然后点击连接按钮,再将CONNECT报文复制到发送数据的框里点击发送(左下角一定要打上对勾以十六进制形式发送)
在连接诶的状态下,点击发送按钮后发现突然断开连接,这表明刚才的转化十六进制过程中出现了小的错误(在字符串转换为十六进制情况下加了多余的空格或者回车键导致转化后的数据有误,所以直接被服务器踢下线,断开连接–此时只能重复前面的十六进制转化步骤-----有效载荷部分)—错误容易出现在有效载荷转化十六进制部分,如果失败可以多试几次
重新计算有效载荷,发现是客户端ID部分出现了错误,这里最重要的而是细心,最好多计算几遍,一不留神就会出错
重新整合的connect报文内容:
10 74 00 04 4D 51 54 54 04 C2 00 64 00 29 64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 00 13 64 65 76 69 63 65 32 26 61 31 6E 64 50 51 48 63 58 37 66 00 28 35 63 61 65 65 66 35 62 39 32 35 31 39 34 30 36 39 36 62 30 32 64 63 38 30 62 36 31 35 63 33 66 37 33 39 66 34 36 62 63
再次连接服务器,发送以上数据时,收到了云端下发回来的消息,表示连接成功—也即下一条报文CONNACK报文数据(同时connect报文连接成功也可以在云端看到设备由未激活状态变为设备在线状态)
CONNECT报文就是实现连接云端时设备的,连接成功即可显示云端设备在线
***********************
发送CONNECT报文:报文正确并收到云端回复连接成功(CONNACK):20 02 00 00
发送CONNECT报文:报文有错的话拒绝连接返回报文为 (CONNACK):20 02 00 04
2、CONNACK报文 – 确认连接请求(S->C)
2.1固定报头
可知固定报头十六进制数据可表示为:20 02
2.2可变报头
连接返回码常用的是第一个和最后一个
如果收到服务器的回复连接成功则
可变报头:00 00
2.3有效载荷
因此前面我们发送CONNECT报文时收到的CONNACK报文数据:20 02 00 00表示连接成功
发送CONNECT报文:报文正确并收到云端回复连接成功
CONNACK:20 02 00 00
3、DISCONNECT –断开连接(C->S)
我们有CONNECT(连接报文),就肯定有DISCONNECT(断开连接报文)
(1)固定报头
固定报头十六进制数据:E0 00
由此图可知DISCONNECT报文只有固定报头,没有可变报头和有效载荷
***********************
DISCONNECT报文:
固定报头:E0 00
可变报头:无
有效负载:无
断开连接发送数据:E0 00
***********************
在测试断开连接之前一定先在建立连接,在云端可以看到设备上下线
CONNECT报文:
10 74 00 04 4D 51 54 54 04 C2 00 64 00 29 64 65 76 69 63 65 32 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 00 13 64 65 76 69 63 65 32 26 61 31 6E 64 50 51 48 63 58 37 66 00 28 35 63 61 65 65 66 35 62 39 32 35 31 39 34 30 36 39 36 62 30 32 64 63 38 30 62 36 31 35 63 33 66 37 33 39 66 34 36 62 63
DISCONNECT报文:E0 00
我们在发送CONNECT报文连接成功后,即可发送断开连接报文来断开和服务器的连接
(1)首先发送CONNECT报文,收到云端返回的CONNACK:20 02 00 00表示连接服务器成功
(2)建立连接后,发送DISCONNECT报文数据即可断开与服务器的连接
4、PINGREQ-心跳请求(C->S)
连接之后查看保活时间(在connect报文中设置的是100s)用ping包判断网络是不是还存在
(1)固定报头
***********************
固定报头:C0 00
可变报头:无
有效载荷:无
PINGREQ报文:C0 00
***********************
主要用于检测连接网络状态是否还存在
5、PINGRESP – 心跳响应(S->C)
***********************
固定报头:D0 00
可变报头:无
有效载荷:无
PINGRESP报文:D0 00
***********************
下面为连接之后查看保活时间(在connect报文中设置的是100s)用ping包判断网络是不是还存在
首先利用CONNECT建立了连接状态,然后发送PINGREQ(C0 00)报文查看是否还处于连接状态,最后收到了服务器发回来的PINGRESP(D0 00 )报文,表示连接还存在,并没有断开
6、SUBSCRIBE - 订阅主题(C->S)-----重要
客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅。 每个订阅注册客户端关心的一个或多个
主题。 为了将应用消息转发给与那些订阅匹配的主题, 服务端发送 PUBLISH 报文给客户端.SUBSCRIBE报文也(为每个订阅) 指定了最大的 QoS 等级, 服务端根据这个发送应用消息给客户端。
6.1固定报头
固定报头第一个字节:82
固定报头后面的字节:??表示不知道多少数据
剩余长度=可变报头+有效载荷
固定报头数据:82 ??
6.2可变报头
报文标识符意味着为了减少流量使用,给每一个topic(很长的话)进行了简短表示,即进行一个编号表示,通过几号成功,几号失败了,即可判断哪一条topic订阅成功或失败
可变报头数据:00 0A
6.3有效载荷
即订阅了谁(订阅的topic转化为十六进制的形式)
我们在前面已经知道我们需要订阅的tpoic为:
订阅topic:/sys/a1ndPQHcX7f/device2/thing/service/property/set
转化为十六进制数据:51个字节--->数据长度表示标点符号(00 33)
2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74
固定报头+可变报头+有效载荷
(1)固定报头:82 ??
(2)可变报头:00 0A
(3)有效载荷:2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74
(4)服务等级0:00
(4)服务等级1:01
SUBSCRIBE 报文数据格式如下:
固定报头+可变报头+有效载荷数据长度+有效载荷+报文等级
服务等级0时SUBSCRIBE 报文
82 ?? 00 0A 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00
服务等级1时SUBSCRIBE 报文
82 ?? 00 0A 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 01
接下来就要计算??用什么十六进制数据代替了
还是按照上面计算剩余长度的方法计算(将??后面的十六进制数据全部复制到网络助手,进行自动计算个数-再转化为十六进制表示)
发现一共56个字节,直接转化为十六进制数据:38
即??用16进制38表示
订阅报文:
82 38 00 0A 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00
发送定于主题报文后会收到服务器的回复表示订阅主题成功
7、SUBACK – 订阅确认(S->C)
(1)固定报头
固定报头:90 ??
(2)可变报头
此处的可变报头和订阅topic时发送订阅报文的可变报头相同
订阅时发送的报文(其中第三四个字节是可变报头)
82 38 00 0A 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00
订阅时发送的可变报头是 00 0A
所以收到回复的可变报头也是 00 0A
固定报头:90 ??
可变报头:00 0A
(3)有效载荷
订阅成功返回的是:01
即有效载荷:01
按理说:
如果订阅时发送的报文等级是00 则收到数据的最后一个字节也是00
如果订阅时发送的报文等级是01 则收到数据的最后一个字节也是01
但是:
对于阿里云不管你的等级设置为00还是01,最终收到的回复等级均为01
固定报头:90 ??
可变报头:00 0A
有效载荷:01
报文:90 ?? 00 0A 01
剩余长度是3,所以??用03表示
即收到的报文数据为:实际意义表示订阅主题成功
90 03 00 0A 01
8、UNSUBSCRIBE –取消订阅(C->S)
(1)固定报头
固定报头:A2 ??
(2)可变报头+有效载荷
其实在上传取消订阅主题时根据才开始发送的订阅主题修改即可,只需要将第一个字符82改为A2,最后一个字节00删去,在修改一下第二个字节数据(删去最后一个字节后,剩余字节长度减去1即38修改为37)
订阅发布时的报文:
82 38 00 0A 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00
取消订阅的报文
A2 37 00 0A 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74
再发布订阅主题后,发送取消订阅主题的消息会收到服务器的回复报文(即UNSUBACK – 取消订阅确认:B0 02 00 AA)表示取消订阅成功
9、UNSUBACK – 取消订阅确认(S->C)
固定报头:B0 02
后两个字节 00 0A(在取消订阅时上传的也是这一个)
即返回的数据为:B0 02 00 0A
表示取消订阅成功
10、PUBLISH – 发布消息(C->S)------重要
PUBLISH 控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。
Publish报文,其实才开始我们不知道publish报文的数据格式是什么样子的,所以呢可以先订阅topic看一下云端下发数据的格式,看看云端是如何下发指令的
我们接下来由订阅到的十六进制数据去反推数据格式
10.1使用订阅topic获取云端下发数据
1、先连接服务器,再发送订阅报文成功订阅阿里云的主题
2、云端下发数据步骤
在线调试时可以设置开关的数值并进行下发
由于已经使用订阅报文订阅了主题,所以收到了云端下发的数据(十六进制)
订阅topic收到的十六进制数据如下
30 9A 01 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 73 65 74 22 2C 22 69 64 22 3A 22 34 31 30 37 34 39 36 34 33 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
10.2固定报头
固定报头:30 ?? ??
30 9A 01 00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 73 65 74 22 2C 22 69 64 22 3A 22 34 31 30 37 34 39 36 34 33 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
10.2.1反推法求出数据长度和内容
可知数据第一个字节30是固定报头,接下来的一个字节是剩余长度
分析如下:
9A = 1001 1010,又由于最高位标志位置一了,所以说明一个字节表示不过来,因此第二个和第三个字节均表示剩余长度:9A 01
9A = 1001 1010由于最高位是标志位,因此最高位换为0以后数据为下:
二进制0001 1010=十进制数据26
01=128*1=128
剩余长度=128+26=154(即从第四个字节到最后共154个字节)
去掉前三个字节后数据:(得以验证剩余长度的确是154)
00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 73 65 74 22 2C 22 69 64 22 3A 22 34 31 30 37 34 39 36 34 33 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
接下来是解析数据部分:
10.3可变报头
可变报头=主题名+报文标志符
注:只有当 QoS 等级是 1 或 2 时,报文标识符(Packet Identifier) 字段才能出现在 PUBLISH 报文中-即此处报文无标志符
10.3.1倒推法求topic
大家肯定会有疑问是谁发过来的数据呢:
topic是十六进制00 33(十进制为51个字节)的内容,从上面报文中数51个字节即为topic
剩余长度:
00 33 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 73 65 74 22 2C 22 69 64 22 3A 22 34 31 30 37 34 39 36 34 33 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
剩余长度的第一个第二个字节表示topic的数据长度(十进制51个字节)
我们从上面剩余长度的第三个字节开始数51个字节,复制出来为:
分离出来的topic十六进制内容(共51个字节)
2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74
我们将分离出来的topic十六进制数据复制到网络助手的发送框(此时一定是十六进制的形式,与对勾的时候复制过去)
此时再把对勾去掉,我们分离出来的十六进制数据就变成了字符串的形式:
/sys/a1ndPQHcX7f/device2/thing/service/property/set
就是这个主题给我们发送的消息
10.4有效载荷
10.4.1倒推法求数据内容(格式)
分离出topic之后数据剩余为:(话说剩余的这些就是我们收到的真正数据了)
7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 73 65 74 22 2C 22 69 64 22 3A 22 34 31 30 37 34 39 36 34 33 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
将剩余的数据同上复制到网络助手中转化为字符串形式:
将对勾去掉即可获得字符串形式数据:
{“method”:“thing.service.property.set”,“id”:“410749643”,“params”:{“PowerSwitch”:1},“version”:“1.0.0”}
以上便为发送数据的格式
这样我们就知道以什么样子的数据格式进行上传数据了
10.4.2构造PUBLISH报文发送数据时的数据格式
发布(设备属性上报):
/sys/a1ndPQHcX7f/device2/thing/event/property/post
订阅(设备属性设置):
/sys/a1ndPQHcX7f/device2/thing/service/property/set
订阅到的有用数据:
{"method":"thing.service.property.set","id":"410749643","params":{"PowerSwitch":1},"version":"1.0.0"}
我们是订阅了主题/sys/a1ndPQHcX7f/device2/thing/service/property/set获取到的数据
因此上面method为“thing.service.property.set”
如果我们要通过/sys/a1ndPQHcX7f/device2/thing/event/property/post主题
进行上报数据,则我们需要将method修改为“thing.event.property.post”
所以需要上传的数据格式为:其中{“PowerSwitch”:1}便为下发的开关数值的键值对
{"method":"thing.event.property.post","id":"410749643","params":{"PowerSwitch":1},"version":"1.0.0"}
到此为止我们便知道PUBLISH发布消息时需要上传的数据时有效载荷的数据格式了,接下来我们开始完成数据的上传
固定报头:30 ?? ??
可变报头(发布topic):
/sys/a1ndPQHcX7f/device2/thing/event/property/post
转化为十六进制数据:50个字节-->十六进制00 32
2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74
整合后部分数据如下
30 ?? ?? 00 32 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74
topics后面跟的就全是数据了
{"method":"thing.event.property.post","id":"803381017","params":{"PowerSwitch":1},"version":"1.0.0"}
字符串转化为十六进制数据:
7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 38 30 33 33 38 31 30 31 37 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
整合数据如下:
30 ?? ?? 00 32 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 38 30 33 33 38 31 30 31 37 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
数据整和完毕,只需要计算一下剩余长度即可
??后面的数据共152个字节
152%128=24-->1 1000最高位需要置一
1001 1000->十六进制数据为98
152/128=1->十六进制数据为01
剩余长度为98 01
用94 01替换??即可完成PUBLISH报文的拼接
最终的publish报文
30 98 01 00 32 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 38 30 33 33 38 31 30 31 37 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
五、使用PUBLISH报文发布消息(应用)
(一)上传开关的数值并在物模型上显示
在PUBLISH报文发送之前可以先在云端设备的物模型处进行查看并无任何数据
我们接下来连接服务器(CONNECT报文)并发送数据(PUBLISH报文)
先发送CONNECT报文
在发送PUBLISH报文
再到云端查看物模型的状态,可以看出开关状态已经上传
(二)新增加温度属性上报,云端显示
同时上传温度属性和开关属性
在PUBLISH报文中我们只需要修改最后的数据部分即可
PUBLISH报文
固定报头+可变报头
30 ?? ?? 00 32 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74
想要上传的数据为:温度+开关状态
在数据中新加CurrentTemperature属性,数据类型为浮点型
修改后的数据为:(里面的标点符号一定是英文标点,否则数据有误云端无法正常显示)
{"method":"thing.event.property.post","id":"803381017","params":{"PowerSwitch":1,“CurrentTemperature”:25.63},"version":"1.0.0"}
上面的数据,新加的属性的引号和逗号误用了中文字符,找错误找了好久才找到,特别容易出错
上面转化为十六进制数据:
7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 38 30 33 33 38 31 30 31 37 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 2C 22 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 22 3A 32 35 2E 36 33 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
整合后数据
30 ?? ?? 00 32 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 38 30 33 33 38 31 30 31 37 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 2C 22 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 22 3A 32 35 2E 36 33 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
接下来结算剩余长度:??后面的数据长度为179
179%128=51=11 0011最高位需要置一
1011 0011=十六进制为:B3
183/128=1=十六进制为:01
?? ??=B3 01
最终报文数据为:
30 B3 01 00 32 2F 73 79 73 2F 61 31 6E 64 50 51 48 63 58 37 66 2F 64 65 76 69 63 65 32 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 38 30 33 33 38 31 30 31 37 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 31 2C 22 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 22 3A 32 35 2E 36 33 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
发送PUBLISH报文数据:
云端可以看到温度属性正是我们所上传的属性值
{“PowerSwitch”:1,“CurrentTemperature”:25.63}
到这里MQTT协议基础知识的了解和运用就算结束了,还有一个湿度属性,可以再自行添加测试,如若发现文中有错误,欢迎评论指正。
以上是关于MQTT协议-报文分析及网络客户端报文测试(MQTT报文连接阿里云上传数据+订阅数据)的主要内容,如果未能解决你的问题,请参考以下文章
物联网之MQTT3.1.1和MQTT5协议 (13) PINGREQ报文
20210311 全网唯一,物联网MQTT协议报文结构分析以及基于C#代码的报文组装实现