使用 WebSocket 客戶端鏈接 MQTT 服務器

簡介

近年來隨着 Web 前端的快速發展,瀏覽器新特性層出不窮,愈來愈多的應用能夠在瀏覽器端或經過瀏覽器渲染引擎實現,Web 應用的即時通訊方式 WebSocket 獲得了普遍的應用。html

WebSocket 是一種在單個 TCP 鏈接上進行全雙工通信的協議。WebSocket 通訊協議於2011年被 IETF 定爲標準 RFC 6455,並由 RFC 7936 補充規範。WebSocket API 也被 W3C 定爲標準。前端

WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。 —— 摘自 維基百科 WebSocketnode

MQTT 協議第 6 章詳細約定了 MQTT 在 WebSocket [RFC6455] 鏈接上傳輸須要知足的條件,協議內容EMQ君不在此累述。因爲協議實現細節較爲複雜,本文選取兩個經常使用的 JavaScript MQTT 客戶端進行鏈接測試。nginx

兩款客戶端比較

Paho.mqtt.js

Paho 是 Eclipse 的一個 MQTT 客戶端項目,Paho JavaScript Client 是其中一個基於瀏覽器的庫,它使用 WebSockets 鏈接到 MQTT 服務器。相較於另外一個 JavaScript 鏈接庫來講,其功能較少,不推薦使用。git

MQTT.js

MQTT.js 一個 MQTT 協議的客戶端庫,用 JavaScript 編寫,可用於 Node.js 和瀏覽器。在 Node.js 端能夠經過全局安裝使用命令行鏈接,同時還支持 MQTT ,MQTT TLS 證書鏈接;值得一提的是 MQTT.js 還對微信小程序有較好的支持。github

EMQ 君將以 MQTT.js 庫進行鏈接講解。web

安裝 MQTT.js

若是讀者機器上裝有 Node.js 運行環境,可以使用 npm 命令安裝 MQTT.jsnpm

在當前目錄安裝

npm i mqtt

 

全局安裝

將註冊 mqtt mqtt_pub mqtt_sub 命令到當前用戶,此處藉助 iot.eclipse.org 講解一下命令行的使用小程序

# 全局安裝
npm i mqtt -g
 # 使用命令行訂閱 
$ mqtt sub -t 'hello' -h 'iot.eclipse.org' -v
 > hello 09860 # 成功鏈接到服務器並訂閱了主題 hello, 命令行將阻塞等待消息 # 在另外一個終端上使用命令行發佈 
mqtt pub -t 'hello' -h 'iot.eclipse.org' -m 'from MQTT.js'

 

 # 命令行將進行 鏈接 -> 發佈 -> 斷開鏈接 操做,此時讀者會到訂閱命令行,應當收到來自 hello 主題的消息 > hello from MQTT.js 

npm 在當前目錄安裝仍然可使用 ./node_module/.bin/mqtt 命令來執行以上操做。後端

CDN 引用

MQTT.js 包能夠經過 http://unpkg.com 得到

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

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

 

鏈接至 MQTT 服務器

幾個公共的用於 WebSocket 測試鏈接服務器:

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

因爲須要展現客戶端認證部份內容,但上述服務器未提供客戶端認證服務,筆者特經過 ActorCloud 平臺註冊了一個設備進行接入鏈接。

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

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

// 鏈接選項
const options = { connectTimeout: 4000, // 超時時間 // 認證信息
      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

協議 // 主機名 . 域名 : 端口 / 路徑

初學者容易出現如下幾個錯誤:

  • 鏈接地址沒有指明協議:WebSocket 做爲一種通訊協議,其使用 ws(非加密)、wss(SSL 加密) 做爲協議標識。MQTT.js 客戶端支持多種協議,鏈接地址需指明協議類型;

  • 鏈接地址沒有指明端口:MQTT 並未對 WebSocket 接入端口作出規定,EMQ 上默認使用 8083 8084 分別做爲非加密鏈接、加密鏈接端口。而 WebSocket 協議默認端口同 HTTP 保持一致 (80/443),不填寫端口則代表使用 WebSocket 的默認端口鏈接;而使用標準 MQTT 鏈接時則無需指定端口,如 MQTT.js 在 Node.js 端可使用 mqtt://localhost 鏈接至標準 MQTT 8083 端口,當鏈接地址是 mqtts://localhost 則鏈接到 8884 端口;

  • 鏈接地址無路徑:MQTT-WebSoket 統一使用 /path 做爲鏈接路徑,鏈接時需指明;

  • 協議與端口不符:使用了 wss 鏈接卻鏈接到 8083 端口;

  • 在 HTTPS 下使用非加密的 WebSocket 鏈接: Google 等機構在推動 HTTPS 的同時也經過瀏覽器約束進行了安全限定,即 HTTPS 鏈接下瀏覽器會自動禁止使用非加密的 ws 協議發起鏈接請求;

  • 證書與鏈接地址不符: 篇幅較長,詳見下文 EMQ 啓用 SSL/TLS 加密鏈接

鏈接選項

上面代碼中, options 是客戶端鏈接選項,如下是主要參數說明,其他參數詳見https://www.npmjs.com/package/mqtt#connect

  • keepalive:心跳時間,默認 60秒,設置 0 爲禁用;

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

  • username:鏈接用戶名(若是有);

  • password:鏈接密碼(若是有);

  • clean:true,設置爲 false 以在離線時接收 QoS 1 和 2 消息;

  • reconnectPeriod:默認 1000 毫秒,兩次從新鏈接之間的間隔,客戶端 ID 重複、認證失敗等客戶端會從新鏈接;

  • connectTimeout:默認 30 * 1000毫秒,收到 CONNACK 以前等待的時間,即鏈接超時時間。

訂閱/取消訂閱

鏈接成功以後才能訂閱,且訂閱的主題必須符合 MQTT 訂閱主題規則;

注意 JavaScript 異步非阻塞特性,只有在 connect 事件後才能確保客戶端已成功鏈接,或經過 client.connected 判斷是否鏈接成功:

// 錯誤示例
client.on('connect', handleConnect) client.subscribe('hello') client.publish('hello', 'Hello EMQ') // 正確示例
 client.on('connect', (e) => { console.log('成功鏈接服務器') // 訂閱一個主題
    client.subscribe('hello', { qos: 1 }, (error) => { if (!error) { cosnole.log('訂閱成功') client.publish('hello', 'Hello EMQ', { qos: 1, rein: false }, (error) => { cosnole.log(error || '發佈成功') }) } }) // 訂閱多個主題
    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, )

 

發佈/接收消息

發佈消息到某主題,發佈的主題必須符合 MQTT 發佈主題規則,不然將斷開鏈接。發佈以前無需訂閱該主題,但要確保客戶端已成功鏈接:

// 監聽接收消息事件
client.on('message', (topic, message) => { console.log('收到來自', topic, '的消息', message.toString()) }) // 發佈消息
if (!client.connected) { console.log('客戶端未鏈接') return } client.publish('hello', 'hello EMQ', (error) => { console.log(error || '消息發佈成功') })

 

微信小程序

MQTT.js 庫對微信小程序特殊處理,使用 wxs 協議標識符。注意小程序開發規範中要求必須使用加密鏈接,鏈接地址應相似爲wxs://iot.actorcloud.io:8084/mqtt

EMQ 啓用 SSL/TLS 加密鏈接

EMQ 內置自簽名證書,默認已經啓動了加密的 WebSocket 鏈接,但大部分瀏覽器會報證書無效錯誤如net::ERR_CERT_COMMON_NAME_INVALID (Chrome、360 等 webkit 內核瀏覽器在開發者模式下, Console 選項卡 能夠查看大部分鏈接錯誤)。

準備工做

這篇文章 https流程和原理 中對證書認證進行了詳細的闡述,EMQ 君總結啓用 SSL/TLS 證書須要具有的條件是:

  • 將域名綁定到 EMQ 服務器公網地址:CA 機構簽發的證書籤名是針對域名的;

  • 申請證書:向 CA 機構申請所用域名的證書,注意選擇一個可靠的 CA 機構且證書要區分泛域名與主機名;

  • 使用加密鏈接的時候選擇 wss 協議,並使用域名鏈接:綁定域名-證書以後,必須使用域名而非 IP 地址進行鏈接,這樣瀏覽器纔會根據域名去校驗證書以在經過校驗後創建鏈接。

在 EMQ 上配置

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

# wss 監聽地址 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 服務器計算壓力,同時實現域名複用,同時經過 nginx 的負載均衡能夠分配多個後端服務實體。

# 建議 WebSocket 也綁定到 443 端口 
listen 443, 8084; server_name example.com; ssl on; ssl_certificate /etc/cert.crt; # 證書路徑 ssl_certificate_key /etc/cert.key; # 密鑰路徑 # upstream 服務器列表 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; } # 普通網站應用 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 額外請求頭 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 「upgrade」; }

 

其餘資源

MQTT.js 官方例子給出了詳細的鏈接與使用操做實例代碼,讀者可前往查看;

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



做者:EMQ
連接:https://www.jianshu.com/p/4fd95cae1a9c
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索