MQTT (Message Queuing Telemetry Transport) 是一種輕量且高效的通訊協定,被廣泛應用於各種通訊需求中,尤其在物聯網和即時通訊領域。 MQTT 採用發布/訂閱模型,當一個設備發布訊息到特定主題時,所有訂閱該主題的設備都能夠接收該訊息。 此外,MQTT 提供了多種不同的 QoS (Quality of Service) 等級,這確保了訊息的可靠性和一致性,同時允許根據應用情境進行適度的調整。

MQTT 軟體有非常多選擇,包含客戶端與伺服器端,但需要注意的是有些軟體僅是客戶端或者僅是伺服器端。 本文主要架設的是 Mosquitto Broker,其他 MQTT Broker 還有 EMQX、moquette、HiveMQ、EMQ X Broker...等等。

備註:MQTT 伺服器通常稱為 Broker,比較少看到用 Server

安裝 Mosquitto Broker

在 Ubuntu 安裝 Mosquitto Broker 相當容易,只需要使用 apt-get 安裝即可。

            
                # 安裝 Mosquitto Broker
                sudo apt install mosquitto

                # 安裝 Mosquitto Client
                sudo apt install mosquitto-clients
            
        

但本文並不打算詳細說明這種方式,我自己習慣使用 docker 運行 Mosquitto Broker,因此接下來我會一步一步介紹如何使用 docker 安裝 Mosquitto Broker。

Step 1: 準備映像檔

第一步當然是從 Docker Hub 下載 Mosquitto 映像檔。

                
                    # 將映像檔下載至本機
                    docker pull eclipse-mosquitto

                    # 查看映像檔資訊
                    docker inspect --type=image eclipse-mosquitto
                
            

Step 2: 建立本機目錄

Mosquitto 映像檔有三個目錄需要掛載至容器,分別是 config、data、及 log。

                
                    # 存放設定檔及密碼檔
                    # 這個目錄中需要有 mosquitto.conf 及 passwd_file
                    mkdir -p ~/docker/mosquitto/config

                    # 存放 SSL 憑證
                    mkdir -p ~/docker/mosquitto/config/ssl

                    # 存放 .db 資料
                    mkdir -p ~/docker/mosquitto/data

                    # 存放 log 檔
                    # 這個目錄需要有 mosquitto.log
                    mkdir -p ~/docker/mosquitto/log
                
            

Step 3: 建立 mosquitto.conf

mosquitto.conf 是 Mosquitto 重要的設定檔,有關 mosquitto.conf 的內容請參考下方。

                
                    # 啟動持久化訊息
                    persistence true
                    persistence_file mosquitto.db
                    persistence_location /mosquitto/data

                    # 設定 Log 檔
                    log_dest file /mosquitto/log/mosquitto.log

                    # 不允許匿名登入,必須透過帳密才能登入
                    allow_anonymous false
                    password_file /mosquitto/config/passwd_file

                    # 不需要使用證書進行身份驗證
                    require_certificate false

                    # 啟用 TCP 模式
                    listener 1883
                    protocol mqtt

                    # 啟用 WebSocket 模式
                    listener 9001
                    protocol websockets

                    # 啟用 TCP 模式 (SSL)
                    listener 8883
                    protocol mqtt
                    keyfile /mosquitto/config/ssl/server.key
                    certfile /mosquitto/config/ssl/server.crt
                    cafile /mosquitto/config/ssl/ca.crt

                    # 啟用 WebSocket 模式 (SSL)
                    listener 8884
                    protocol websockets
                    keyfile /mosquitto/config/ssl/server.key
                    certfile /mosquitto/config/ssl/server.pem
                    cafile /mosquitto/config/ssl/root.cer
                
            

提醒您,千萬不要把註解寫在設定值的後面,會出錯,請使用獨立一行撰寫註解。 還有設定檔中的路徑是檔案在容器中的路徑,不是本機路徑,這份設定檔後續會掛載至容器中使用。

設定檔中的 1883、9001、8883 及 8884 port 都是慣用的預設 port,如果沒有特殊需求就使用預設 port 就好,並不需要四個 port 都啟動,請根據需求調整。 上方設定檔是最基本的一些設定,其他更多詳細的設定,請參考官網說明。

Step 4: 建立 docker 容器

使用以下指令建立 docker 容器。

                
                    # -d: 背景執行,建立後返回容器 ID
                    # --restart=always: 開機自動啟動
                    # -u: 使用特定帳號執行
                    # -p: 設定本機 PORT 對應至容器中的 PORT
                    # -v: 設定本機目錄對應至容器中的目錄
                    docker run -d --name mosquitto \
                        --restart=always \
                        -u 501:20 \
                        -p 1883:1883 -p 9001:9001 \
                        -v ~/docker/mosquitto/data:/mosquitto/data:rw \
                        -v ~/docker/mosquitto/log:/mosquitto/log:rw \
                        -v ~/docker/mosquitto/config:/mosquitto/config:rw \
                        eclipse-mosquitto
                
            

Step 5: 建立 Mosquitto 帳號

由於 Mosquitto 安裝在容器內,因此建立帳號需要先進入 docker 中,再透過 mosquitto_passwd 指令新增帳號。

                
                    # 進入容器
                    docker exec -it mosquitto /bin/sh

                    # 建立帳號
                    mosquitto_passwd /mosquitto/config/passwd_file username
                    Password: xxxx
                    Reenter password: xxxx

                    # 如果 passwd_file 檔案不存在可以使用 -c 參數
                    # 但要特別注意,如果 passwd_file 檔案存在,又使用 -c,這樣檔案會被蓋掉
                    mosquitto_passwd -c /mosquitto/config/passwd_file username
                
            

Step 6: 測試 Mosquitto Broker

Mosquitto 提供 mosquitto_sub 指令用來訂閱主題,以及 mosquitto_pub 指令用來發布訊息,只是因為我們是在 docker 中安裝 Mosquitto,所以這兩個指令也存在 docker 中。

                
                    # 進入容器
                    docker exec -it mosquitto /bin/sh

                    # 訂閱主題
                    # -d: debug 模式
                    # -v: 詳細列印收到的訊息
                    # -h: Broker 的 IP
                    # -p: Broker 的 Port (預設為 1883 或 8883)
                    # -t: 訂閱的主題
                    mosquitto_sub -d -v -h [IP] -p [Port] -t topic

                    # 發布訊息到特定主題 (必需在另外再開一個終端機)
                    # -d: debug 模式
                    # -h: Broker 的 IP
                    # -p: Broker 的 Port (預設為 1883 或 8883)
                    # -t: 發布的主題
                    # -m: 發布的訊息
                    mosquitto_pub -d -h [IP] -p [Port] -t topic -m "Hello"
                
            

建議大家可以更多工具 MQTT 客戶端工具測試看看,從外面連進 docker 中的 Mosquitto 更能確保 MQTT 是否正確運行。 推薦使用 MQTT Explorer,該工具支援測試 TCP 模式及 WebSocket 模式。

探討 passwd_file 檔

在建立 MQTT 帳號密碼時,我們可以使用 mosquitto_passwd 指令將帳號及生成的加密密碼保存在 passwd_file 檔案中。但 mosquitto_passwd 只有在容器中使用,因此每次新增帳密時,都需要進入容器中進行操作,這樣的方式顯然相當不方便。

根據官網文件,密碼是由 PBKDF2-SHA512 演算法生成,因此我們完全可以自己撰寫程式生成,以下提供 python 及 php 的程式碼提供參考。

            
                <?php
                $password = '654321';
                $salt = substr(md5(mt_rand()), 0, 12);
                $iterations = 101;
                $hash = hash_pbkdf2('sha512', $password, $salt, $iterations, 0, true);
                echo '$7$' . $iterations . '$' . base64_encode($salt) . '$' . base64_encode($hash);
            
        
            
                from hashlib import pbkdf2_hmac
                from base64 import b64encode
                import secrets

                password = b'123456'
                iterations = 101
                salt = secrets.token_bytes(12)
                hashString = pbkdf2_hmac('sha512', password, salt, iterations)
                print("$7$" + str(iterations) + "$" + b64encode(salt).decode() + "$" + b64encode(hashString).decode())
            
        

以上程式僅是生成加密密碼而已,但在 passwd_file 檔案中是以[帳號]:[加密密碼]格式儲存,請自己在前方加入自己想要設定的密碼。當修改完 passwd_file 檔之後,docker 需要重啟才會生效。

關於 mosquitto.db

這個檔案以 .db 結尾,乍看之下會誤以為是 SQLite 檔,其實不是。根據官方的回覆,這個檔是用來儲存持久化資料,僅能給 Mosquitto 使用,不適用 Mosquitto 以外的用途,所以裡面不是儲存歷史訊息,不用嘗試想辦法打開它。

認識 QoS

QoS(Quality of Service)是用來確保訊息的可靠性和傳遞的保證級別。

QoS 0
訊息只送出一次,且不管訂閱者是否收到。因此可能發生遠端沒收到的狀況,效能較好。
QoS 1
確保訊息至少被接收一次,當訊息送出後,等待訂閱者回傳訊息,如果沒收到回傳訊息,就會再送出訊息,直到收到訂閱者回傳訊息為止。該模式所以遠端可能會重複收到相同訊息。
QoS 2
訊息只送出一次,但保證遠端會收到訊息,這是最可靠的傳遞方式,但也最耗費效能。