使用 WebSocket 客戶端連接 MQTT 服務(wù)器

使用 WebSocket 客戶端連接 MQTT 服務(wù)器

[TOC]

簡介

近年來隨著 Web 前端的快速發(fā)展,瀏覽器新特性層出不窮,越來越多的應(yīng)用可以在瀏覽器端或通過瀏覽器渲染引擎實(shí)現(xiàn),Web 應(yīng)用的即時(shí)通信方式 WebSocket 得到了廣泛的應(yīng)用。

WebSocke 是一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。WebSocket 通信協(xié)議于2011年被 IETF 定為標(biāo)準(zhǔn) RFC 6455,并由 RFC 7936 補(bǔ)充規(guī)范。WebSocket API 也被 W3C 定為標(biāo)準(zhǔn)。

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。 —— 摘自 維基百科 WebSocket

MQTT 協(xié)議第 6 章詳細(xì)約定了 MQTT 在 WebSocket [RFC6455] 連接上傳輸需要滿足的條件,協(xié)議內(nèi)容筆者不在此累述有興趣的讀者可以自行查看。由于協(xié)議實(shí)現(xiàn)細(xì)節(jié)較為復(fù)雜,本文選取兩個(gè)常用的 JavaScript MQTT 客戶端進(jìn)行連接測試。

兩款客戶端比較

Paho.mqtt.js

Paho 是 Eclipse 的一個(gè) MQTT 客戶端項(xiàng)目,Paho JavaScript Client 是其中一個(gè)基于瀏覽器的庫,它使用 WebSockets 連接到 MQTT 服務(wù)器。相較于另一個(gè) JavaScript 連接庫來說,其功能較少,不推薦使用。

MQTT.js

MQTT.js 一個(gè) MQTT 協(xié)議的客戶端庫,用 JavaScript 編寫,可用于 Node.js 和瀏覽器。在 Node.js 端可以通過全局安裝使用命令行連接,同時(shí)還支持 MQTT ,MQTT TLS 證書連接;值得一提的是 MQTT.js 還對(duì)微信小程序有較好的支持。

EMQ 君將以 MQTT.js 庫進(jìn)行連接講解。

安裝 MQTT.js

如果讀者機(jī)器上裝有 Node.js 運(yùn)行環(huán)境,可使用 npm 命令安裝 MQTT.js

在當(dāng)前目錄安裝

npm i mqtt

全局安裝

將注冊(cè) mqtt mqtt_pub mqtt_sub 命令到當(dāng)前用戶,此處借助 iot.eclipse.org 講解一下命令行的使用

# 全局安裝
npm i mqtt -g

# 使用命令行訂閱
$ mqtt sub -t 'hello' -h 'iot.eclipse.org' -v
> hello 09860

# 成功連接到服務(wù)器并訂閱了主題 hello, 命令行將阻塞等待消息


# 在另一個(gè)終端上使用命令行發(fā)布
mqtt pub -t 'hello' -h 'iot.eclipse.org' -m 'from MQTT.js'

# 命令行將進(jìn)行 連接 -> 發(fā)布 -> 斷開連接 操作,此時(shí)讀者會(huì)到訂閱命令行,應(yīng)當(dāng)收到來自 hello 主題的消息

> hello from MQTT.js

npm 在當(dāng)前目錄安裝仍然可以使用 ./node_module/.bin/mqtt 命令來執(zhí)行以上操作。

CDN 引用

MQTT.js 包可以通過 http://unpkg.com 獲得

<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>

<script>
    // 將在全局初始化一個(gè) mqtt 變量
    console.log(mqtt)
</script>

連接至 MQTT 服務(wù)器

幾個(gè)公共的用于 WebSocket 測試連接服務(wù)器:

  • test.mosquitto.org - 使用端口 8080 未加密,8081 用于 SSL 上的 WebSocket;
  • iot.eclipse.org - 使用端口 80 未加密,443 用于 SSL 上的 WebSocket;
  • broker.hivemq.com - 使用端口 8000 未加密,不支持 SSL 上的 WebSocket。

由于需要展示客戶端認(rèn)證部分內(nèi)容,但上述服務(wù)器未提供客戶端認(rèn)證服務(wù),筆者特通過 ActorCloud 平臺(tái)注冊(cè)了一個(gè)設(shè)備進(jìn)行接入連接。

EMQ 使用 8083 端口用于普通連接,8084 用于 SSL 上的 WebSocket 連接。

// <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
// const mqtt = require('mqtt')
import mqtt from 'mqtt'

// 連接選項(xiàng)
const options = {
      connectTimeout: 4000, // 超時(shí)時(shí)間
      // 認(rèn)證信息
      clientId: 'emqx-connect-via-websocket',
      username: 'emqx-connect-via-websocket',
      password: 'emqx-connect-via-websocket',
}

const client = mqtt.connect('wss://iot.actorcloud.io:8084/mqtt', options)

client.on('reconnect', (error) => {
    console.log('正在重連:', error)
})

client.on('error', (error) => {
    console.log('連接失敗:', error)
})

連接地址

上文示范的連接地址可以拆分為: wss: // iot . actorcloud.io : 8084 /mqtt

協(xié)議 // 主機(jī)名 . 域名 : 端口 / 路徑

初學(xué)者容易出現(xiàn)以下幾個(gè)錯(cuò)誤:

  • 連接地址沒有指明協(xié)議:WebSocket 作為一種通信協(xié)議,其使用 ws(非加密)、wss(SSL 加密) 作為協(xié)議標(biāo)識(shí)。MQTT.js 客戶端支持多種協(xié)議,連接地址需指明協(xié)議類型;

  • 連接地址沒有指明端口:MQTT 并未對(duì) WebSocket 接入端口做出規(guī)定,EMQ 上默認(rèn)使用 8083 8084 分別作為非加密連接、加密連接端口。而 WebSocket 協(xié)議默認(rèn)端口同 HTTP 保持一致 (80/443),不填寫端口則表明使用 WebSocket 的默認(rèn)端口連接;而使用標(biāo)準(zhǔn) MQTT 連接時(shí)則無需指定端口,如 MQTT.js 在 Node.js 端可以使用 mqtt://localhost 連接至標(biāo)準(zhǔn) MQTT 8083 端口,當(dāng)連接地址是 mqtts://localhost 則連接到 8884 端口;

  • 連接地址無路徑:MQTT-WebSoket 統(tǒng)一使用 /path 作為連接路徑,連接時(shí)需指明;

  • 協(xié)議與端口不符:使用了 wss 連接卻連接到 8083 端口;

  • 在 HTTPS 下使用非加密的 WebSocket 連接: Google 等機(jī)構(gòu)在推進(jìn) HTTPS 的同時(shí)也通過瀏覽器約束進(jìn)行了安全限定,即 HTTPS 連接下瀏覽器會(huì)自動(dòng)禁止使用非加密的 ws 協(xié)議發(fā)起連接請(qǐng)求;

  • 證書與連接地址不符: 篇幅較長,詳見下文 "EMQ WebSocket 配置證書連接"。

連接選項(xiàng)

上面代碼中, options 是客戶端連接選項(xiàng),以下是主要參數(shù)說明,其余參數(shù)詳見https://www.npmjs.com/package/mqtt#connect。

  • keepalive:心跳時(shí)間,默認(rèn) 60秒,設(shè)置 0 為禁用;

  • clientId: 客戶端 ID ,默認(rèn)通過 'mqttjs_' + Math.random().toString(16).substr(2, 8) 隨機(jī)生成;

  • username:連接用戶名(如果有);

  • password:連接密碼(如果有);

  • clean:true,設(shè)置為 false 以在離線時(shí)接收 QoS 1 和 2 消息;

  • reconnectPeriod:默認(rèn) 1000 毫秒,兩次重新連接之間的間隔,客戶端 ID 重復(fù)、認(rèn)證失敗等客戶端會(huì)重新連接;

  • connectTimeout:默認(rèn) 30 * 1000毫秒,收到 CONNACK 之前等待的時(shí)間,即連接超時(shí)時(shí)間。

訂閱/取消訂閱

連接成功之后才能訂閱,且訂閱的主題必須符合 MQTT 訂閱主題規(guī)則;

注意 JavaScript 異步非阻塞特性,只有在 connect 事件后才能確??蛻舳艘殉晒B接,或通過 client.connected 判斷是否連接成功:

// 錯(cuò)誤示例
client.on('connect', handleConnect)
client.subscribe('hello')
client.publish('hello', 'Hello EMQ')

// 正確示例

client.on('connect', (e) => {
    console.log('成功連接服務(wù)器')
    
    // 訂閱一個(gè)主題
    client.subscribe('hello', { qos: 1 }, (error) => {
        if (!error) {
            cosnole.log('訂閱成功')
            client.publish('hello', 'Hello EMQ', { qos: 1, rein: false }, (error) => {
                cosnole.log(error || '發(fā)布成功')
            })
        }
    })
    
    // 訂閱多個(gè)主題
    client.subscribe(['hello', 'one/two/three/#', '#'], { qos: 1 },  onSubscribeSuccess)
    
    // 訂閱不同 qos 的不同主題
    client.subscribe(
        [
            { hello: 1 }, 
            { 'one/two/three': 2 }, 
            { '#': 0 }
        ], 
        onSubscribeSuccess,
    )
})

// 取消訂閱
client.unubscribe(
    // topic, topic Array, topic Array-Onject
    'hello',
    onUnubscribeSuccess,
)

發(fā)布/接收消息

發(fā)布消息到某主題,發(fā)布的主題必須符合 MQTT 發(fā)布主題規(guī)則,否則將斷開連接。發(fā)布之前無需訂閱該主題,但要確??蛻舳艘殉晒B接:

// 監(jiān)聽接收消息事件
client.on('message', (topic, message) => {
    console.log('收到來自', topic, '的消息', message.toString())
})

// 發(fā)布消息
if (!client.connected) {
    console.log('客戶端未連接')
    return
}

client.publish('hello', 'hello EMQ', (error) => {
    console.log(error || '消息發(fā)布成功')
})

微信小程序

MQTT.js 庫對(duì)微信小程序特殊處理,使用 wxs 協(xié)議標(biāo)識(shí)符。注意小程序開發(fā)規(guī)范中要求必須使用加密連接,連接地址應(yīng)類似為wxs://iot.actorcloud.io:8084/mqtt

EMQ 啟用 SSL/TLS 加密連接

EMQ 內(nèi)置自簽名證書,默認(rèn)已經(jīng)啟動(dòng)了加密的 WebSocket 連接,但大部分瀏覽器會(huì)報(bào)證書無效錯(cuò)誤如net::ERR_CERT_COMMON_NAME_INVALID (Chrome、360 等 webkit 內(nèi)核瀏覽器在開發(fā)者模式下, Console 選項(xiàng)卡 可以查看大部分連接錯(cuò)誤)。

準(zhǔn)備工作

這篇文章 https流程和原理 中對(duì)證書認(rèn)證進(jìn)行了詳細(xì)的闡述,EMQ 君總結(jié)啟用 SSL/TLS 證書需要具備的條件是:

  • 將域名綁定到 EMQ 服務(wù)器公網(wǎng)地址:CA 機(jī)構(gòu)簽發(fā)的證書簽名是針對(duì)域名的;

  • 申請(qǐng)證書:向 CA 機(jī)構(gòu)申請(qǐng)所用域名的證書,注意選擇一個(gè)可靠的 CA 機(jī)構(gòu)且證書要區(qū)分泛域名與主機(jī)名;

  • 使用加密連接的時(shí)候選擇 wss 協(xié)議,并使用域名連接:綁定域名-證書之后,必須使用域名而非 IP 地址進(jìn)行連接,這樣瀏覽器才會(huì)根據(jù)域名去校驗(yàn)證書以在通過校驗(yàn)后建立連接。

在 EMQ 上配置

打開 etc/emqx.conf 配置文件,修改以下配置

# wss 監(jiān)聽地址
listener.wss.external = 8084

# 修改密鑰文件地址
listener.wss.external.keyfile = etc/certs/cert.key

# 修改證書文件地址
listener.wss.external.certfile = etc/certs/cert.pem

重啟 EMQ 即可。

可以使用你的證書與密鑰文件直接替換到 etc/certs/ 下。

在 nginx 上配置反向代理與證書

使用 nginx 來反向代理并加密 WebSocket 可以減輕 EMQ 服務(wù)器計(jì)算壓力,同時(shí)實(shí)現(xiàn)域名復(fù)用,同時(shí)通過 nginx 的負(fù)載均衡可以分配多個(gè)后端服務(wù)實(shí)體。


# 建議 WebSocket 也綁定到 443 端口
listen 443, 8084;
server_name example.com;

ssl on;

ssl_certificate /etc/cert.crt;  # 證書路徑
ssl_certificate_key /etc/cert.key; # 密鑰路徑


# upstream 服務(wù)器列表
upstream emq_server {
    server 10.10.1.1:8883 weight=1;
    server 10.10.1.2:8883 weight=1;
    server 10.10.1.3:8883 weight=1;
}

# 普通網(wǎng)站應(yīng)用
location / {
    root www;
    index index.html;
}

# 反向代理到 EMQ 非加密 WebSocket
location / {
    proxy_redirect off;
    # upstream
    proxy_pass http://emq_server;
    
    proxy_set_header Host $host;
    # 反向代理保留客戶端地址
    proxy_set_header X-Real_IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
    # WebSocket 額外請(qǐng)求頭
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection “upgrade”;
}

其他資源

MQTT.js 官方例子給出了詳細(xì)的連接與使用操作實(shí)例代碼,讀者可前往查看;

EMQ Dashboard 中的 WebSocket 工具、ActorCloud 測試工具 -> MQTT 客戶端 (需到 ActorCloud 商城開通),均使用 MQTT.js 構(gòu)建,讀者可體驗(yàn)參考。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、相關(guān)資料 1、MQTT官網(wǎng):http://mqtt.org/2、EMQX官網(wǎng):https://www.emqx...
    隗豪閱讀 11,312評(píng)論 1 5
  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢(mèng)敢當(dāng)閱讀 9,034評(píng)論 0 50
  • 此時(shí)風(fēng)仍在吹著 吹裂我心中的寸寸纖細(xì) 原來這就是心碎 原來都是一場誤會(huì) 我該如何面對(duì) 怎樣彌補(bǔ)這美麗而清純的罪
    范范子詩閱讀 138評(píng)論 0 0
  • ? 這幾天大家都慌了陣腳了吧?漲粉周還暈乎乎沒有開始~ 今日秋木大大夜宵送上,依舊三點(diǎn)來說 ???互推漲粉 大家互...
    L玲噠閱讀 292評(píng)論 0 0
  • 生活似一堵墻 時(shí)常把視線遮擋 望山不見木 望水不見浪 但你需要抉擇 尤其需要堅(jiān)強(qiáng) 自毀絕不是出路 繁華也裹著憂傷 ...
    揮之易閱讀 230評(píng)論 0 0

友情鏈接更多精彩內(nèi)容