文章目录

  • 一、本文章所涉及到的内容
  • 二、感性认识MQTT协议
  • 三、准备信息
    • (一)工具获取
    • (二)获取信息
      • 1、获取三元组信息
      • 2、获取发布topic和订阅topic
      • 3、客户端ID,用户名,哈希加密
      • 4、连接服务器所使用的域名和端口号
  • 四、MQTT报文分析
    • (一)MQTT报文
      • 1、报文类型
      • 2、报文结构
      • 3、剩余长度的计算(重中之重)
        • 3.1十进制和MQTT协议中十六进制数据转化(重要)
        • 3.2MQTT协议中十六进制数据转化为十进制(重要)
      • 4、报文等级
    • (二)MQTT报文逐条分析正式开始
      • 1、CONNECT – 连接服务端(C->S)----重要
        • 1.1固定报头
        • 1.2可变报头
        • 1.3有效载荷(重要)
      • 2、CONNACK报文 – 确认连接请求(S->C)
      • 3、DISCONNECT –断开连接(C->S)
      • 4、PINGREQ-心跳请求(C->S)
      • 5、PINGRESP – 心跳响应(S->C)
      • 6、SUBSCRIBE - 订阅主题(C->S)-----重要
        • 6.1固定报头
        • 6.2可变报头
        • 6.3有效载荷
      • 7、SUBACK – 订阅确认(S->C)
      • 8、UNSUBSCRIBE –取消订阅(C->S)
      • 9、UNSUBACK – 取消订阅确认(S->C)
      • 10、PUBLISH – 发布消息(C->S)------重要
        • 10.1使用订阅topic获取云端下发数据
        • 10.2固定报头
          • 10.2.1反推法求出数据长度和内容
        • 10.3可变报头
          • 10.3.1倒推法求topic
        • 10.4有效载荷
          • 10.4.1倒推法求数据内容(格式)
          • 10.4.2构造PUBLISH报文发送数据时的数据格式
  • 五、使用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/post2、订阅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转化为二进制数据:0010 0001
一个字节不够,高位置一:1010 0001-->转化为十六进制A1
161/128=1用十六进制表示是01
所以剩余长度用十六进制表示:A1 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,即十进制是10407:表示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 63connect报文:  固定报头+可变报头+有效载荷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 63DISCONNECT报文: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:01SUBSCRIBE 报文数据格式如下:
固定报头+可变报头+有效载荷数据长度+有效载荷+报文等级服务等级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 74topics后面跟的就全是数据了
{"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报文连接阿里云上传数据+订阅数据)相关推荐

  1. 中移M5310-A通过AT指令MQTT连接阿里云物联网平台并进行数据互传

    一.平台端操作 1.设备创建 进入阿里云物联网平台,选择物联网平台.然后开通公共实例,成功后依次创建产品.创建设备.设备创建成功后点击最右侧的查看->MQTT连接参数可以看到该设备接入该产品所需 ...

  2. 微信小程序连接物联网(三):微信小程序远程遥控宿舍开门 基于NodeMCU通过MQTT协议连接阿里云物联网平台

    索引 这是用微信小程序遥控开门的系列文章,具体微信小程序连接物联网的代码在第三章提及. 微信小程序连接物联网(一):初始化ESP8266 NodeMCU 微信小程序连接物联网(二):NodeMCU L ...

  3. 移远BC35-G配置网络连接阿里云MQTT发送数据

    移远BC35/BC26/BC28配置网络连接阿里云MQTT发送数据(一)精.防踩坑 硬件准备 接线部分 软件准备 烧录固件 QFlash 4.17烧录 阿里云准备 测试准备(AT指令) 连接阿里云 第 ...

  4. STM32+ESP8266+MQTT连接阿里云服务器(四、STM32连接阿里云平台)

    材料: STM32F103.ESP8266.串口 第三章介绍了利用串口调试助手+AT指令连接阿里云平台的,现在用代码去实现其功能,利用STM32的串口2跟WIFI模块进行数据通信即可.这里我实现的功能 ...

  5. NBIOT连接阿里云控制台(MQTT连接阿里云控制台)

    首先使用MQTT工具连接阿里云平台进行测试之后再使用NBIOT连接控制台,这里主要讲解MQTT连接阿里云的步骤 1.注册或登录阿里云账号 自行前往阿里云官网注册 2.进入物联网界面 首先点击阿里云旁边 ...

  6. STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云

    STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云 目录 STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云 一 ...

  7. MQTT学习笔记(1)--网络调试助手连接阿里云物联网

    初探 MQTT的网络调试助手通信测试 上手之前觉得很有难度,但是当你慢慢上手,你回过头来,你会发现哦,原来是这样的啊! 这里用的平台是阿里云的物理网平台所以有必要要介绍一下如何使用平台,如何申请,和白 ...

  8. ESP8266连接阿里云--MQTT协议

    ESP8266连接阿里云–MQTT协议   MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(Publish/Subscr ...

  9. IP报文在阿里云上的神奇之旅:同地域内云上通信

    作者:姚悠   阿里云专家服务团队 一个IP报文如何跨越万水千山达到目的地?本文将以阿里云为例,带领大家一起探索同地域内云上通信的全过程,完整展现云上同地域内各种场景的IP报文之旅,深入理解云网络技术 ...

最新文章

  1. 探索Bioconductor数据包
  2. mysql dml ddl优先级_MYSQL入门操作和常规DML、DDL、DQL使用
  3. 课程 | 《知识图谱》第二期重磅来袭!
  4. java发送QQ群邮件,简单两步使用node发送qq邮件
  5. 【离散数学】欧拉图与汉密尔顿图
  6. OSPF中的frame-relay(6) NBMA-broadcast
  7. HashMap中,比较key是否相等为什么要重写equal() 和hashCode()这两个方法?
  8. 利用Proteus 8.9运行stm32最简单的跑马灯程序
  9. [日更-2019.4.20、21] cm-14.1 Android系统启动过程分析(二)-Zygote进程启动过程
  10. 使用Everything清理垃圾文件
  11. 代理服务器和反向代理服务器
  12. 如何找出当前活动桌面背景图像的位置/路径(Ubuntu 18.04,GNOME)?
  13. you-get和youtube-dl下载全网视频
  14. 英文版win10如何全屏玩英文版warcraft3
  15. 客户端timewait
  16. DevOps工具链介绍
  17. 某大学:今年考研报名人数上涨40%
  18. git push error: 无法推送一些引用到XXX 错误
  19. 数据分析学习总结笔记15:时间序列分析及Python实现
  20. 交互式界面设计——移动产品的需求分析

热门文章

  1. 华为c8812开机一直android,华为C8812:搭载原生Android 4.0系统
  2. Win10磁盘管理的简单卷、带区卷、跨区卷、镜像卷学习测试
  3. jdk,neo4j社区版本下载与安装
  4. echart 三维可视化地图_使用 ECharts GL 实现三维可视化 - 入门款
  5. solidworks模板_SOLIDWORKS Costing可以定制模板和标准模板一起使用
  6. wgt文件怎么安装到手机_APP更新无法正确安装wgt
  7. 【Unity 3D】使用EasyAR实现单图识别的AR增强现实功能(附实现步骤)
  8. opencv4中未定义标识符CV_CAP_PROP_FPS;CV_CAP_PROP_FRAME_COUNT;CV_CAP_PROP_POS_FRAMES问题
  9. Ubuntu找不到Wi-Fi适配器No Wi-Fi Adapter Found修复笔记
  10. linux学习笔记(五)编译内核模块生成ko驱动文件