MQTT協議與阿里雲IoT物聯網平臺

wongxming發表於2018-12-18

1.MQTT協議介紹

1.1 MQTT協議

MQTT(訊息佇列遙測傳輸) 是基於 TCP/IP 協議棧而構建的支援在各方之間非同步通訊的訊息協議。MQTT在空間和時間上將訊息傳送者與接收者分離,因此可以在不可靠的網路環境中進行擴充套件。雖然叫做訊息佇列遙測傳輸,但它與訊息佇列毫無關係,而是使用了釋出和訂閱(Pub/Sub)的模型。

MQTT 是一種輕量級的、靈活的網路協議,致力於為 IoT 開發人員實現適當的平衡:

  • 這個輕量級協議可在嚴重受限的裝置硬體和高延遲/頻寬有限的網路上實現。
  • 它的靈活性使得為 IoT 裝置和服務的多樣化應用場景提供支援成為可能。

image.png | left | 642x320

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…

image.png | left | 747x320

下面開發實踐基於Nodejs版mqtt,獲取地址 www.npmjs.com/package/mqt…

1.3 MQTT報文

image.png | left | 456x260

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平臺建立連線

image.png | left | 573x260

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

image.png | left | 590x300

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
有且僅有一次的傳輸
不支援

阿里雲IoT的QoS.png | left | 500x247

6. 裝置掉線重連

裝置與阿里雲IoT的訂閱關係在雲端保持,除非裝置主動unsubscribe,否則訂閱關係不清理。裝置重連後,依然保持之前的訂閱關係,不需要重複訂閱。

7. 傳輸層安全TLS1.2

裝置和IoT平臺之間的鏈路可以通過TLS v1.2加密。 如果使用TLS加密,需要下載根證照。 CONNECT引數中clientId的securemode=2

help.aliyun.com/document_de…

IoT物聯網技術

iot-tech-weixin.png | center | 225x224

相關文章