1.MQTT协议介绍
1.1 MQTT协议
MQTT(消息队列遥测传输) 是基于 TCP/IP 协议栈而构建的支持在各方之间异步通信的消息协议。MQTT在空间和时间上将消息发送者与接收者分离,因此可以在不可靠的网络环境中进行扩展。虽然叫做消息队列遥测传输,但它与消息队列毫无关系,而是使用了发布和订阅(Pub/Sub)的模型。
MQTT 是一种轻量级的、灵活的网络协议,致力于为 IoT 开发人员实现适当的平衡:
- 这个轻量级协议可在严重受限的设备硬件和高延迟/带宽有限的网络上实现。
- 它的灵活性使得为 IoT 设备和服务的多样化应用场景提供支持成为可能。
1.2 MQTT Client库
MQTT Client 库在很多语言中都有实现,包括 Embedded C、C、Java、JavaScript、Python、C++、C#、Go、iOS、Android等。Eclipse Paho的MQTT库下载地址:https://www.eclipse.org/paho/downloads.php
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5bJMI6AS-1576547662519)(https://cdn.nlark.com/lark/0/2018/png/15292/1543486508533-898d509b-7f51-414b-b251-ecf7d60ea3b9.png “”)]
下面开发实践基于Nodejs版mqtt,获取地址 https://www.npmjs.com/package/mqtt
1.3 MQTT报文
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYQuhB6m-1576547662520)(https://cdn.nlark.com/lark/0/2018/png/15292/1545100927916-203ccc7f-ab6c-4d55-b1af-790868bc67c9.png “”)]
1.3.1 固定报头Fixed header
Bit
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
byte 1
|
MQTT控制报文的类型
|
用于指定控制报文类型的标志位
|
||||||
byte 2,3,4,5
|
剩余长度,最大4个字节
|
控制报文类型
名字 | 值 | 报文流动方向 | 描述 |
---|---|---|---|
Reserved | 0 | 禁止 | 保留 |
CONNECT | 1 | Client -> Broker | device连接IoT平台 |
CONNACK | 2 | Broker -> Client | IoT平台确认连接结果 |
PUBLISH | 3 | 双向 | 发布消息 |
PUBACK | 4 | 双向 | QoS=1消息发布收到确认 |
SUBSCRIBE | 8 | Client -> Broker | device订阅IoT平台Topic |
SUBACK | 9 | Broker -> Client | IoT平台确认订阅结果 |
UNSUBSCRIBE | 10 | Client -> Broker | device取消订阅IoT平台Topic |
UNSUBACK | 11 | Broker -> Client | IoT平台确认取消订阅结果 |
PINGREQ | 12 | Client -> Broker | device发送心跳请求到IoT平台 |
PINGRESP | 13 | Broker -> Client | IoT平台响应device心跳 |
DISCONNECT | 14 | Client -> Broker | device断开IoT平台连接 |
Reserved | 15 | 禁止 | 保留 |
控制报文类型标志位
控制报文 | 固定报头标志 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|
PUBLISH | MQTT 3.1.1使用 | DUP | QoS | QoS | RETAIN |
剩余长度
字节数 | 最小值 | 最大值 |
---|---|---|
1 | 0 (0x00) | 127 (0x7F) |
2 | 128 (0x80, 0x01) | 16 383 (0xFF, 0x7F) |
3 | 16 384 (0x80, 0x80, 0x01) | 2 097 151 (0xFF, 0xFF, 0x7F) |
4 | 2 097 152 (0x80, 0x80, 0x80, 0x01) | 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F) |
注:阿里云IoT的单个payload最大256K
1.3.2 可变报头Variable header
某些MQTT控制报文包含一个可变报头部分。它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里。
报文标识符字节 Packet Identifier bytes
Bit | 7 – 0 |
---|---|
byte 1 | 报文标识符 MSB |
byte 2 | 报文标识符 LSB |
控制报文 | 报文标识符 |
---|---|
PUBLISH | 需要(如果QoS =1,2) |
PUBACK | 需要 |
PUBREC | 需要 |
PUBREL | 需要 |
PUBCOMP | 需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
1.3.3 有效载荷Payload
以下MQTT控制报文在报文的最后部分包含一个有效载荷。对于PUBLISH来说有效载荷就是业务消息。
控制报文 | 有效载荷 |
---|---|
CONNECT | 需要 |
PUBLISH | 可选 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
2.与阿里云IoT平台建立连接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ZS8jZui-1576547662521)(https://cdn.nlark.com/lark/0/2018/png/15292/1545100387365-193c5672-a670-4b6d-a792-6fcb8f1329e8.png “”)]
2.1 CONNECT
阿里云IoT物联网平台的MQTT协议不支持will消息,CONNECT 消息内容参数如下:
参数
|
说明
|
cleanSession
|
此标志指定连接是否是持久性的。
0为持久会话,QoS=1消息不会丢失;
1为非持久会话,清理离线消息。
|
clientId
|
客户端标识符
|
username
|
代理的身份验证和授权凭证。
|
password
|
代理的身份验证和授权凭证。
|
keepAlive
|
心跳时间,
IoT平台约定心跳范围 30s~1200s |
其中clientId,username,password由设备三元组(productKey,deviceName,deviceSecret)按照规则生成,具体规则如下:
clientId
|
id+”|securemode=3,signmethod=hmacsha1,timestamp=”+timestamp+”|”
|
id
:表示客户端ID,64字符内。其中 ||内为扩展参数。 securemode:安全模式;2为TLS加密,3为非加密
signmethod:签名算法类型。 timestamp:当前时间毫秒值。
|
username
|
deviceName+”&”+productKey
|
|
password
|
sign_hmac(deviceSecret,content)
|
sign_hmac为clientId中的signmethod算法类型
content为如下拼接字符串: ”
clientId${ id} deviceName${deviceName} productKey${productKey} timestamp${timestamp}” |
官方文档:https://help.aliyun.com/document_detail/73742.html
设备端代码示例(Nodejs版) client.js
/**
"dependencies": { "mqtt": "2.18.8" }
*/
const crypto = require('crypto');
const mqtt = require('mqtt');
//设备身份三元组+区域
const deviceConfig = {
productKey: "替换",
deviceName: "替换",
deviceSecret: "替换",
regionId: "cn-shanghai"
};
//根据三元组生成mqtt连接参数
const options = initMqttOptions(deviceConfig);
const url = `tcp://${deviceConfig.productKey}.iot-as-mqtt.${deviceConfig.regionId}.aliyuncs.com:1883`;
//2.建立连接
const client = mqtt.connect(url, options);
client.on('packetsend', function (packet){
console.log('send '+packet.cmd+' packet =>',packet)
})
client.on('packetreceive', function (packet){
console.log('receive '+packet.cmd+' packet =>',packet)
})
//IoT平台mqtt连接参数初始化
function initMqttOptions(deviceConfig) {
const params = {
productKey: deviceConfig.productKey,
deviceName: deviceConfig.deviceName,
timestamp: Date.now(),
clientId: Math.random().toString(36).substr(2),
}
//CONNECT参数
const options = {
keepalive: 60, //60s
clean: false, //cleanSession保持持久会话
protocolVersion: 4 //MQTT v3.1.1
}
//1.生成clientId,username,password
options.password = signHmacSha1(params, deviceConfig.deviceSecret);
options.clientId = `${params.clientId}|securemode=3,signmethod=hmacsha1,timestamp=${params.timestamp}|`;
options.username = `${params.deviceName}&${params.productKey}`;
return options;
}
/*
生成基于HmacSha1的password
参考文档:https://help.aliyun.com/document_detail/73742.html?#h2-url-1
*/
function signHmacSha1(params, deviceSecret) {
let keys = Object.keys(params).sort();
// 按字典序排序
keys = keys.sort();
const list = [];
keys.map((key) => {
list.push(`${key}${params[key]}`);
});
const contentStr = list.join('');
return crypto.createHmac('sha1', deviceSecret)
.update(contentStr)
.digest('hex');
}
2.2 CONNACK
receive connack packet => Packet {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
sessionPresent: false,
returnCode: 0
}
2.4 PINGRESP
send pingreq packet => { cmd: 'pingreq' }
2.5 PINGRESP
receive pingresp packet => Packet {
cmd: 'pingresp',
retain: false,
qos: 0,
dup: false,
length: 0,
topic: null,
payload: null
}
2.6 DISCONNECT
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZSGNnsZZ-1576547662522)(https://cdn.nlark.com/lark/0/2018/png/15292/1545103111650-1aa28138-a253-4787-bbb9-e696174ec880.png “”)]
3. 发布数据
3.1 PUBLISH
//3.属性数据上报
const topic = `/sys/${deviceConfig.productKey}/${deviceConfig.deviceName}/thing/event/property/post`;
setInterval(function() {
//发布数据到topic
client.publish(topic, getPostData(),{qos:1});
}, 5 * 1000);
function getPostData() {
const payloadJson = {
id: Date.now(),
params: {
temperature: Math.floor((Math.random() * 20) + 10),
humidity: Math.floor((Math.random() * 20) + 60)
},
method: "thing.event.property.post"
}
console.log("===postDatan topic=" + topic)
console.log(payloadJson)
return JSON.stringify(payloadJson);
}
send publish packet => { cmd: 'publish',
topic: '/sys/a1hQSwFledE/eud1jXfEgCsAiP2eId9Q/thing/event/property/post',
payload: '{"id":1543896481106,"params":{"temperature":23,"humidity":73},"method":"thing.event.property.post"}',
qos: 1,
retain: false,
messageId: 38850,
dup: false
}
3.2 PUBACK
receive puback packet => Packet {
cmd: 'puback',
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
messageId: 38850
}
4. 接收数据
4.1 SUBSCRIBE
//4.订阅主题,接收指令
const subTopic = `/${deviceConfig.productKey}/${deviceConfig.deviceName}/control`;
client.subscribe(subTopic)
client.on('message', function(topic, message) {
console.log("topic " + topic)
console.log("message " + message)
})
SUBSCRIBE消息体
send subscribe packet => { cmd: 'subscribe',
subscriptions:
[ { topic: '/a1hQSwFledE/eud1jXfEgCsAiP2eId9Q/control', qos: 0 } ],
qos: 1,
retain: false,
dup: false,
messageId: 38851
}
4.2 SUBACK
SUBACK消息体
receive suback packet => Packet {
cmd: 'suback',
retain: false,
qos: 0,
dup: false,
length: 3,
topic: null,
payload: null,
granted: [ 128 ],
messageId: 38851
}
4.3 UNSUBSCRIBE
send unsubscribe packet => { cmd: 'unsubscribe',
qos: 1,
messageId: 34323,
unsubscriptions: [ '/a1hQSwFledE/eud1jXfEgCsAiP2eId9Q/control' ]
}
4.4 UNSUBACK
receive unsuback packet => Packet {
cmd: 'unsuback',
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
messageId: 34323
}
5. 服务质量QoS
服务质量
Quality of Service
|
描述
|
阿里云IoT
|
QoS=0
|
最多一次的传输,可能会收不到消息
|
支持
|
QoS=1
|
至少一次的传输,一定会收到消息,可能重复
|
支持
|
QoS=2
|
有且仅有一次的传输
|
不支持
|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NM8NSxCE-1576547662523)(https://cdn.nlark.com/lark/0/2018/png/15292/1545098670115-30f219ce-21ad-470e-a951-a6b00a268209.png “”)]
6. 设备掉线重连
设备与阿里云IoT的订阅关系在云端保持,除非设备主动unsubscribe,否则订阅关系不清理。设备重连后,依然保持之前的订阅关系,不需要重复订阅。
7. 传输层安全TLS1.2
设备和IoT平台之间的链路可以通过TLS v1.2加密。
如果使用TLS加密,需要下载根证书。
CONNECT参数中clientId的securemode=2
https://help.aliyun.com/document_detail/73742.html
本文章来源于互联网,如有侵权,请联系删除!原文地址:MQTT协议与阿里云IoT物联网平台
基于MQTT协议及EMQ搭建物联网平台 一、部署EMQX 我的EMQ是部署在docker中,首先在lunix中拉取emqx 镜像(使用下列命令直接拉取镜像并运行) $ docker run -d –name emqx -p 1883:1883 -p 8081…