<>1.MQTT协议介绍

<>1.1 MQTT协议

MQTT <https://mqtt.org/>(消息队列遥测传输) 是基于 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
<https://www.eclipse.org/paho/downloads.php>



下面开发实践基于Nodejs版mqtt,获取地址 https://www.npmjs.com/package/mqtt
<https://www.npmjs.com/package/mqtt>

<>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}"
官方文档:https://help.aliyun.com/document_detail/73742.html
<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



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

https://help.aliyun.com/document_detail/73742.html
<https://help.aliyun.com/document_detail/73742.html>

<https://blog.csdn.net/klandor2008/article/details/85062687#80ifpi>IoT物联网技术