MQTT协议与阿里云IoT物联网平台

原文链接:https://juejin.im/post/5c186d9d5188255e9b61f4c3

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库下载地址:www.eclipse.org/paho/downlo…

下面开发实践基于Nodejs版mqtt,获取地址 www.npmjs.com/package/mqt…

1.3 MQTT报文

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消息发布收到确认
PUBREC 5 双向 IoT不支持
PUBREL 6 双向 IoT不支持
PUBCOMP 7 双向 IoT不支持
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平台建立连接

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}"

官方文档:help.aliyun.com/document_de…

设备端代码示例(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

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("===postData\n 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
有且仅有一次的传输
不支持

6. 设备掉线重连

设备与阿里云IoT的订阅关系在云端保持,除非设备主动unsubscribe,否则订阅关系不清理。设备重连后,依然保持之前的订阅关系,不需要重复订阅。

7. 传输层安全TLS1.2

设备和IoT平台之间的链路可以通过TLS v1.2加密。 如果使用TLS加密,需要下载根证书。 CONNECT参数中clientId的securemode=2

help.aliyun.com/document_de…

IoT物联网技术

转载于:https://juejin.im/post/5c186d9d5188255e9b61f4c3

展开阅读全文
博主设置当前文章不允许评论。

没有更多推荐了,返回首页