近年來隨着 Web 前端的快速發展,瀏覽器新特性層出不窮,愈來愈多的應用能夠在瀏覽器端經過瀏覽器渲染引擎實現,Web 應用的即時通訊方式 WebSocket 也所以獲得了普遍的應用。javascript
WebSocket 是一種在單個 TCP 鏈接上進行全雙工通信的協議。WebSocket 通訊協議於2011年被 IETF 定爲標準 RFC 6455,並由 RFC 7936 補充規範。WebSocket API 也被 W3C 定爲標準。html
WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。 ^1前端
MQTT 協議第 6 章 詳細約定了 MQTT 在 WebSocket [RFC6455] 鏈接上傳輸須要知足的條件,協議內容不在此詳細贅述。java
兩款客戶端比較
Paho.mqtt.js
Paho 是 Eclipse 的一個 MQTT 客戶端項目,Paho JavaScript Client 是其中一個基於瀏覽器的庫,它使用 WebSockets 鏈接到 MQTT 服務器。相較於另外一個 JavaScript 鏈接庫來講,其功能較少,不推薦使用。git
MQTT.js
MQTT.js 是一個徹底開源的 MQTT 協議的客戶端庫,使用 JavaScript 編寫,可用於 Node.js 和瀏覽器。在 Node.js 端能夠經過全局安裝使用命令行鏈接,同時支持 MQTT/TCP、MQTT/TLS、MQTT/WebSocket 鏈接;值得一提的是 MQTT.js 還對微信小程序有較好的支持。github
本文將使用 MQTT.js 庫進行 WebSocket 的鏈接講解。web
安裝 MQTT.js
若是讀者機器上裝有 Node.js 運行環境,可直接使用 npm 命令安裝 MQTT.js。shell
在當前目錄安裝
npm install mqtt --save
CDN 引用
或免安裝直接使用 CDN 地址npm
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script> <script> // 將在全局初始化一個 mqtt 變量 console.log(mqtt) </script>
鏈接至 MQTT 服務器
本文將使用 EMQ X 提供的 免費公共 MQTT 服務器,該服務基於 EMQ X 的 MQTT 物聯網雲平臺 建立。服務器接入信息以下:小程序
- Broker: broker.emqx.io
- TCP Port: 1883
- Websocket Port: 8083
EMQ X 使用 8083 端口用於普通鏈接,8084 用於 SSL 上的 WebSocket 鏈接。
爲了簡單起見,讓咱們將訂閱者和發佈者放在同一個文件中:
const clientId = 'mqttjs_' + Math.random().toString(16).substr(2, 8) const host = 'ws://broker.emqx.io:8083/mqtt' const options = { keepalive: 60, clientId: clientId, protocolId: 'MQTT', protocolVersion: 4, clean: true, reconnectPeriod: 1000, connectTimeout: 30 * 1000, will: { topic: 'WillMsg', payload: 'Connection Closed abnormally..!', qos: 0, retain: false }, } console.log('Connecting mqtt client') const client = mqtt.connect(host, options) client.on('error', (err) => { console.log('Connection error: ', err) client.end() }) client.on('reconnect', () => { console.log('Reconnecting...') })
鏈接地址
上文示範的鏈接地址能夠拆分爲: ws:
// broker
. emqx.io
: 8083
/mqtt
即 協議
// 主機名
. 域名
: 端口
/ 路徑
初學者容易出現如下幾個錯誤:
- 鏈接地址沒有指明協議:WebSocket 做爲一種通訊協議,其使用
ws
(非加密)、wss
(SSL 加密) 做爲協議標識。MQTT.js 客戶端支持多種協議,鏈接地址需指明協議類型; - 鏈接地址沒有指明端口:MQTT 並未對 WebSocket 接入端口作出規定,EMQ X 上默認使用
8083
8084
分別做爲非加密鏈接、加密鏈接端口。而 WebSocket 協議默認端口同 HTTP 保持一致 (80/443),不填寫端口則代表使用 WebSocket 的默認端口鏈接;而使用標準 MQTT 鏈接時則無需指定端口,如 MQTT.js 在 Node.js 端可使用mqtt://localhost
鏈接至標準 MQTT 1883 端口,當鏈接地址是mqtts://localhost
則鏈接到 8884 端口; - 鏈接地址無路徑:MQTT-WebSoket 統一使用
/path
做爲鏈接路徑,鏈接時需指明,在 EMQ X 上使用的路徑爲/mqtt
; - 協議與端口不符:使用了
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 以前等待的時間,即鏈接超時時間;
- will:遺囑消息,當客戶端嚴重斷開鏈接時,Broker 將自動發送的消息。 通常格式爲:
- topic:要發佈的主題
- payload:要發佈的消息
- qos:QoS
- retain:保留標誌
訂閱/取消訂閱
鏈接成功以後才能訂閱,且訂閱的主題必須符合 MQTT 訂閱主題規則;
注意 JavaScript 的異步非阻塞特性,只有在 connect 事件後才能確保客戶端已成功鏈接,或經過 client.connected
判斷是否鏈接成功:
client.on('connect', () => { console.log('Client connected:' + clientId) // Subscribe client.subscribe('testtopic', { qos: 0 }) })
// Unsubscribe client.unubscribe('testtopic', () => { console.log('Unsubscribed') })
發佈/接收消息
發佈消息到某主題,發佈的主題必須符合 MQTT 發佈主題規則,不然將斷開鏈接。發佈以前無需訂閱該主題,但要確保客戶端已成功鏈接:
// Publish client.publish('testtopic', 'ws connection demo...!', { qos: 0, retain: false })
// Received client.on('message', (topic, message, packet) => { console.log('Received Message: ' + message.toString() + '\nOn topic: ' + topic) })
微信小程序
MQTT.js 庫對微信小程序特殊處理,使用 wxs
協議標識符。注意小程序開發規範中要求必須使用加密鏈接,鏈接地址應相似爲 wxs://broker.emqx.io:8084/mqtt
。
EMQ X 啓用 SSL/TLS 加密鏈接
EMQ 內置自簽名證書,默認已經啓動了加密的 WebSocket 鏈接,但大部分瀏覽器會報證書無效錯誤如 net::ERR_CERT_COMMON_NAME_INVALID
(Chrome、360 等 webkit 內核瀏覽器在開發者模式下, Console 選項卡 能夠查看大部分鏈接錯誤)。致使該錯誤的緣由是瀏覽器沒法驗證自簽名證書的有效性,讀者需從證書頒發機構購買可信任證書,並參考該篇文章中的相應部分進行配置操做:EMQ X MQTT 服務器啓用 SSL/TLS 安全鏈接。
這裏就總結啓用 SSL/TLS 證書須要具有的條件是:
- 將域名綁定到 MQTT 服務器公網地址:CA 機構簽發的證書籤名是針對域名的;
- 申請證書:向 CA 機構申請所用域名的證書,注意選擇一個可靠的 CA 機構且證書要區分泛域名與主機名;
- 使用加密鏈接的時候選擇
wss
協議,並 使用域名鏈接 :綁定域名-證書以後,必須使用域名而非 IP 地址進行鏈接,這樣瀏覽器纔會根據域名去校驗證書以在經過校驗後創建鏈接。
EMQ X 配置
打開 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 X 便可。
可使用你的證書與密鑰文件直接替換到 etc/certs/ 下。
在 Nginx 上配置反向代理與證書
使用 Nginx 來反向代理並加密 WebSocket 能夠減輕 EMQ X 服務器計算壓力,同時實現域名複用,同時經過 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 X 非加密 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」; }
其它資源
項目完整代碼請見:https://github.com/emqx/MQTT-Client-Examples/tree/master/mqtt-client-WebSocket
一款在線的 MQTT WebSocket 鏈接測試工具:https://www.emqx.io/cn/mqtt/mqtt-websocket-toolkit
版權聲明: 本文爲 EMQ 原創,轉載請註明出處。
原文連接:https://www.emqx.io/cn/blog/connect-to-mqtt-broker-with-websocket