mosquitto 與 websocket 的結合

前言

mosquitto 作爲一個消息代理, 客戶端與 mosquitto 服務端的通信時基於 MQTT 協議的, 而現在的主流 web 應用時呈現在瀏覽器中, 這意味着用戶與服務端只能通過 HTTP 或者 HTTPS 這類瀏覽器能理解的協議傳輸, 所以後端還要建立一個代理層, 將 HTTP 協議傳輸的內容解析一下以 MQTT 協議發送到 mosquitto, 最後再由 mosquitto 發送到硬件端.

在瀏覽器支持的協議中, 還有一個適用於長連接的 WS 協議, 參考: 瀏覽器中常見網絡協議介紹. 如果客戶端直接通過 websocket 連接到 mosquitto 端, 那麼就不需要中間的後端代理層. 後端只需要一個推送服務和控制系統就可以實現對客戶端的監聽, 控制, 推送(當然, 如果業務量巨大, 業務邏輯複雜, 代理層還是有必要的, 因爲這樣不但可以爲 mosquitto 過濾一些不必要的業務, 而且可以做一些數據統計, 設計事件鉤子).

在編譯 mosquitto 和它的驗證插件 mosquitto-auth-plug 的時候, 我注意到 mosquitto 有一個監聽的 9001 端口, 事後查了一下, 發現 mosquitto 開放的這個端口是支持直接與客戶端進行 websocket 通信的.

啓用 mosquitto websocket 模式

其實純粹的 MQTT 服務器是沒有這個功能的, mosquitto 需要在編譯的時候設置 configure.mk

WITH_WEBSOCKETS := yes

所幸的是, eclipse 官方的 docker 鏡像 已經支持了 websocket 通信方式. 只需要在 mosquitto.conf 中啓用:

port 1883
listener 9001
protocol websockets

使用 paho-mqtt.js 通過 websocket 與 mosquitto 通信.

eclipse 提供了用於 瀏覽器客戶端利用 javascript 和 mosquitto 進行 websocket 通信的 paho-mqtt.js, 這是這個 js 庫的文檔: Paho.MQTT DOC.

客戶端與服務端的雙通道通信

我們假設有客戶端用戶爲 client, 服務端用戶爲 server. 爲他們分配的主題與權限爲:

 id | username |      topic         | rw 
----+----------+--------------------+----
  1 | client   | /p/client/upload   |  2
  2 | server   | /p/client/upload   |  2
  3 | client   | /p/client/download |  1
  4 | server   | /p/client/download |  2

那麼 server 和 client 均可以在 /p/client/upload 讀寫. 我們約定只有 client 在 /p/client/upload 寫, server 只讀 /p/client/upload, 這個 topic 是用於 client 向 server 發送消息.

對於 topic /p/client/download, server 可讀可寫, client 只讀, 這個 topic 是用於 client 接受 server 向下推送的消息, 具體的邏輯如下:
![圖片描述

注意這裏的 client 是對 /p/client/upload 具有可讀可寫權限的, 意味者它可以讀取來自 /p/client/upload 的信息, 但是實際上這裏沒有人往這個主題發佈消息(除非 client 自己往這裏發消息), 所以 client 擁有可讀權限也沒有關係.

客戶端的 javascript 通信例子

建議看一下這篇文章: Using MQTT Over WebSockets with Mosquitto, 它比較詳細地介紹了客戶端通過 websocket 於 mosquitto 服務器通信的方式.
比如對於 client.html:
先引入 paho-mqtt.js, 這裏我們先用 cloudflare.com CDN上的這段 js:

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.2/mqttws31.js"></script>

下面是創建客戶端 websocket 的例子:

var mqtt;
var host = 'mosquitto';
var port = 9001;

// onConnect 事件
function onConnect() {
    console.log('connected.');
    var raw_message = 'Hello World!';
    message = new Paho.MQTT.Message(raw_message);
    message.destinationName = '/p/client/upload';
    console.log('sending message: ' + raw_message );
    mqtt.send(message);

    // 訂閱 download topic
    var subOptions = {
        qos: 1,
        onSuccess: onSubscribe
    };
    mqtt.subscribe('/p/client/download', subOptions);
}

// 訂閱主題成功事件
function onSubscribe(context) {
    console.log('subscribe success');
    console.log(context);
}

// 連接失敗事件
function onFailure(message) {
    console.log('connect failed.');
}

// onMessageArrived 事件
function onMessageArrived(message) {
    console.log('new message arrived...');
    console.log(message.payloadString);
}


// 建立 MQTT websocket 連接
function MQTTconnect() {
    console.log('connecting to ' + host + ':' + port);
    mqtt = new Paho.MQTT.Client(host, port, 'clientid');
    var options = {
        timeout: 3,
        onSuccess: onConnect,
        onFailure: onFailure,
        userName: 'client',
        password: '123456',
        mqttVersion: 4
    };
    mqtt.onMessageArrived = onMessageArrived;
    mqtt.connect(options);
}

這裏我們利用 paho-mqtt.js 新建了一個 mqtt, 然後綁定 onSuccess(連接成功), onFailure(連接失敗) onMessageArrived(消息到來)等事件, 之後用 options 裏的配置連接到遠程的 mosquitto 服務器. 連接成功後, client 向 server 發送一條消息到 /p/client/upload topic, 通知服務端已經建立連接. 發送之後 mqtt 又訂閱 /p/client/download, 準備接受來自服務端的信息.

比如, 服務端可以這樣向客戶端推送消息:

import paho.mqtt.publish as publish
import time

HOST = 'mosquitto'
PORT = 1883


if __name__ == '__main__':
    client_id = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
    publish.single(
        '/p/client/download', 'hello mqtt', qos=2, hostname=HOST, port=PORT,
        client_id=client_id, auth={'username': 'server', 'password': '123456'})

這就是基於雙通道的服務端於客戶端通信

客戶端與服務端的單通道通信

單通道通信原理類似, 但是由於客戶端可能有多臺設備, 比如手機端, 微信小程序端, PC 端. 那麼身份的驗證不能直接由 mosquitto 確定, 應該在消息體內確定. 比如我們用 json 作爲消息的承載方式. 消息體可以這樣:

{
    "timestamp": "1539141568",
    "client_id": "1001001",
    "message_type": "ping",
    "data": {"detail": "content"},
    "token": "auth_token"
}

這裏應該按照具體的業務需求進行鑑權設計.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章