1、MQTT协议各种报文的介绍及运用
2、MQTT协议在连接阿里云使用过程中客户端ID,用户名的获取及使用哈希算法进行加密计算
3、网络调试助手TCP客户端手动测试每一条MQTT协议报文,了解整个流程
4、固定报头+可变报头+有效载荷的使用
5、固定报头中剩余长度的计算
6、客户端订阅云端消息(对订阅的报文数据使用倒推法求出发送数据的格式)
7、网络客户端通过PUBLISH报文向云端发布消息(开关值+温湿度,云端物模型进行显示)
有些遗憾是无法弥补的,而有些遗憾是可以弥补的。没法弥补的遗憾就不说了,它将永远成为遗憾。既然有些遗憾可以弥补,就不要让它继续成为遗憾了。
这是一个难得的假期,出来做点兼职,也没想过要挣多少钱,够自己吃喝就够了。主要呢是想找一个安静的地方在外面呆一会,利用业余时间出去到处走走看看,有时间的时候就安心学习一点知识,这还是蛮不错的。
1、大学期间的遗憾之一:ESP8266的AT指令
使用AT指令控制8266传输数据这个遗憾前几天已经弥补了,利用业余时间进行了8266的AT指令的学习,Onenet平台的使用,SM32的基础知识学习,也整理了相关的学习博客。刚学会的那几天还是非常兴奋的,但发现会用之后也就那样了,既然只是一个用来联网传输数据的工具,着重点自然也就落在了STM32的使用上,数据采集及基础知识的掌握和应用上,这将是后续的重点学习方向。
2、大学期间的遗憾之二:MQTT协议
MQTT协议的基础知识的学习,虽然知道MQTT协议在运行过程中有很多种报文,但是没去了解过这些报文,之前也不是不想了解吧,是太枯燥乏味直接跳过了,所以这个遗憾得弥补啊,不管以后如何,学习一下还是没有坏处的。
3、大学期间的遗憾之三:模电(头疼)
要利用这个假期学习一下模电,以后必然用到,最头疼的事。
1、在之前的学习中,使用合宙的AIR800连接阿里云用到了MQTT协议,ESP8266SDK开发也用到了MQTT协议。虽然之前用过了,但真不知道是个啥,一直迷迷糊糊的。
2、前段时间帮一个学妹修改毕业论文,其中就涉及到了我的两大遗憾,遗憾归遗憾,论文还是可以修改一下的。虽然对于这些东西细节的地方不是那么清楚,但是整个大体的思想框架还是知道的,这就足够了,所以我俩合作顺利把论文改完了,并通过了学校的论文外审,想想真是不容易啊。
也正是因为帮学妹修改论文这件事呢,所以使我下定了决心,后期抽时间弥补大学时留下的遗憾,对盲点适当的查漏补缺。有些东西可以不用,但是不能不会啊,要不然想用的时候不会是真的着急。
简单来说MQTT协议是基于Topic的订阅与推送,类似于用户与粉丝之间的关系。
发布和订阅的关系可以分为一对一,一对多,即只订阅(发布)一个主题,也可以同时订阅(发布)多个主题。两者之间可以是单向关系,比如说只发布消息,不订阅消息,或者是只订阅消息,不发布消息。两者也可以是双向关系,既订阅消息又发布消息。
举一个简单的例子:
比如说抖音用户张三(topic)和李四(topic),抖音这个平台(服务器)
1、张三在抖音平台关注了李四的账户
李四向抖音平台发布短视频的时候(即李四向服务器发布消息),由于之前张三已经是李四的粉丝(张三通过服务器订阅了李四),所有抖音平台会推送李四的短视频给张三,换句话说假若张三没有关注李四,平台肯定就不会第一时间给他推送了。
2、张三和李四互粉了
既然两个人互粉了,这样的情况下,在张三也向抖音平台发布短视频的时候,平台也会将张三的视频推送给李四(张三不仅可以收到李四视频的更新提醒,李四也会及时收到张三短视频的更新)
3、以上两点既包含了消息的发布,又包含了消息的订阅
4、所有数据的收和发均是通过topic进行的,在MQTT这个框架下张三和李四两者之间不会有任何直接的交流,所有的一切都是通过MQTT抖音这个服务器帮你转发(你先发到服务器,服务器看谁关注了你(订阅了你),就将其推送给谁)
MQTT运行框架
需要用到的工具很少:电脑的计算器、MQTT协议的官方PDF、还有网络调试助手
网络调试助手+MQTT官方pdf–获取密码0hod
首先登陆阿里云物联网云平台,然后复制好三元组(ProductKey(产品),DeviceName(设备名),DeviceSecret(设备密钥))
可以看到在新建的产品下面有两个设备,有一个device2设备,此设备目前还处于未激活状态
可以点击device2设备进去之后复制我们看到的三元组信息
将三元组信息复制并保存
ProductKey:a1ndPQHcX7f DeviceName:device2 DeviceSecret:1448e1c58996469449813ed68e3b0fed
去自己创建的产品下复制并保存发布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
用来连接云端时使用(也可以直接使用软件生成)
如果使用软件生成参考此博客
前面我们已经获取到三元组,在此处会有用到之前保存的信息 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
地域和可用区查询–点我
我使用的阿里云服务器IP地址(华东2)
1、域名模板 ${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com 分析如下: ${YourProductKey}替换为ProductKey(自己创建的产品名) ${YourRegionId}替换为地域代码 切换为我的产品名和地域代码后域名如下: 域名:a1ndPQHcX7f.iot-as-mqtt.cn-shanghai.aliyuncs.com 2、端口号:1883
C->S是客户端发布消息到服务器
S->C是服务器推送消息给客户端
其实每一条报文都具有:固定报头、可变报头、有效载荷
(1)固定报头
(2)可变报头
(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)
已知数据个数求剩余长度在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表示
知道剩余长度的十六进制数据,求数据的个数,这个计算方式在借助定遇到的报文推断PUBLISH负载的数据格式时会用到
例如: E8 07反拆的时候也一样, E8:二进制为(1110 1000),不要第一个字节(高字节标志位不要)变为0110 1000 0110 1000转化为十六进制68,即十进制是104 07:表示128*7=896 128*7+104=1000 因此可以反求出数据个数为1000
(1)等级0:我发送一条消息,不管你有没有回复收到,我都不再继续发
(2)等级1:我发送一条消息,一直等待你回复,直到你必须回复收到了才结束(至少听到一次你说收到了)
(3)等级3:我发送一条消息,你说收到了,我得再确认一遍你收到了吗,你又回复收到了,才算结束
等级0应用比较多,等级2一般不用,太费劲了。
客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是 CONNECT 报文
第一个字节十六进制为:10
第二个字节剩余长度值先这样表示:??
剩余长度=可变报头+有效载荷长度
固定报头:10 ??
协议名、协议等级、连接标志和保持连接时长
协议名: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
客户端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.1固定报头
可知固定报头十六进制数据可表示为:20 02
2.2可变报头
连接返回码常用的是第一个和最后一个
如果收到服务器的回复连接成功则
可变报头:00 00
2.3有效载荷
因此前面我们发送CONNECT报文时收到的CONNACK报文数据:20 02 00 00表示连接成功
发送CONNECT报文:报文正确并收到云端回复连接成功
CONNACK:20 02 00 00
我们有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报文数据即可断开与服务器的连接
连接之后查看保活时间(在connect报文中设置的是100s)用ping包判断网络是不是还存在
(1)固定报头
***********************
固定报头:C0 00
可变报头:无
有效载荷:无
PINGREQ报文:C0 00
***********************
主要用于检测连接网络状态是否还存在
***********************
固定报头:D0 00
可变报头:无
有效载荷:无
PINGRESP报文:D0 00
***********************
下面为连接之后查看保活时间(在connect报文中设置的是100s)用ping包判断网络是不是还存在
首先利用CONNECT建立了连接状态,然后发送PINGREQ(C0 00)报文查看是否还处于连接状态,最后收到了服务器发回来的PINGRESP(D0 00 )报文,表示连接还存在,并没有断开
客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅。 每个订阅注册客户端关心的一个或多个
主题。 为了将应用消息转发给与那些订阅匹配的主题, 服务端发送 PUBLISH 报文给客户端.SUBSCRIBE报文也(为每个订阅) 指定了最大的 QoS 等级, 服务端根据这个发送应用消息给客户端。
固定报头第一个字节:82
固定报头后面的字节:??表示不知道多少数据
剩余长度=可变报头+有效载荷
固定报头数据:82 ??
报文标识符意味着为了减少流量使用,给每一个topic(很长的话)进行了简短表示,即进行一个编号表示,通过几号成功,几号失败了,即可判断哪一条topic订阅成功或失败
可变报头数据:00 0A
即订阅了谁(订阅的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
发送定于主题报文后会收到服务器的回复表示订阅主题成功
(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
(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)表示取消订阅成功
固定报头:B0 02
后两个字节 00 0A(在取消订阅时上传的也是这一个)
即返回的数据为:B0 02 00 0A
表示取消订阅成功
PUBLISH 控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。
Publish报文,其实才开始我们不知道publish报文的数据格式是什么样子的,所以呢可以先订阅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
固定报头: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
可知数据第一个字节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
接下来是解析数据部分:
可变报头=主题名+报文标志符
注:只有当 QoS 等级是 1 或 2 时,报文标识符(Packet Identifier) 字段才能出现在 PUBLISH 报文中-即此处报文无标志符
大家肯定会有疑问是谁发过来的数据呢:
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
就是这个主题给我们发送的消息
分离出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”}
以上便为发送数据的格式
这样我们就知道以什么样子的数据格式进行上传数据了
发布(设备属性上报): /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报文发送之前可以先在云端设备的物模型处进行查看并无任何数据
我们接下来连接服务器(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协议基础知识的了解和运用就算结束了,还有一个湿度属性,可以再自行添加测试,如若发现文中有错误,欢迎评论指正。