MQTT之初體驗

Young5566發表於2019-03-07

初識mqtt

MQTT 是一種基於 釋出/訂閱(publish/subscribe) 模式的“輕量級”通訊協議,該協議建立在TCP/IP協議上。MQTT最大優點在於,可以以極少的程式碼和有限的寬頻為遠端連線裝置提供實時可靠的訊息服務。作為一種低開銷、低寬頻佔用的即時通訊協議,使其在物聯網、小型裝置、移動開發等方面有比較廣泛的應用。 MQTT是一個基於客戶端-伺服器的訊息釋出/訂閱傳輸協議,MQTT協議中有三種身份:釋出者(Publish)代理(Broker)(伺服器)訂閱者(Subscribe),形式如下:

image

設計規範

  • 精簡,不新增可有可無的功能。
  • 釋出/訂閱(Pub/Sub)模式,方便訊息在感測器之間傳遞。
  • 允許使用者動態建立主題,零運維成本。
  • 把輸入量降低到最低以提高傳輸效率。
  • 把低寬頻、高延遲、不穩定的網路因素考慮在內。
  • 支援連續的會話控制。
  • 理解客戶端計算能力可能很低。
  • 提供服務質量管理
  • 假設資料不可知不強求傳輸資料的型別與格式保持靈活性。

主要特性

  • 使用釋出/訂閱訊息模式,提供一對多的訊息釋出,解除應用程式耦合。
  • 對負載內容遮蔽的訊息傳輸
  • 使用tcp/ip提供網路連線
  • 有三種訊息釋出質量
    • 至多一次:訊息釋出完全依賴底層TCP/IP網路。會發生訊息丟失或重複
    • 至少一次:確保訊息到達,但訊息重複可能會發生。
    • 只有一次:確保訊息到達一次
  • 小型傳輸,開銷很小(固定長度的頭部位元組是2位元組),協議交換最小化,以降低網路流量。
  • 使用Last WillTestament特性通知有關各方客戶端異常中斷的機制
    • Last Will:即遺言機制,用於通知同一主題下的去他裝置傳送遺言的裝置已經斷開連線。
    • Testament:遺囑機制,功能類似Last Will

MQTT協議原理

MQTT協議實現方式

實現MQTT協議需要客戶端和伺服器端通訊完成,在通訊過程中MQTT協議中有三種身份:釋出者(Publish)代理(Broker)(伺服器)訂閱者(Subscribe)。其中訊息的釋出者和訂閱者都是客戶端,訊息的代理是伺服器,訊息釋出者可以同時是訂閱者。 MQTT傳輸的訊息分為:

  • Topic(主題):可以理解為接頭暗號,訂閱者訂閱後(即口號對上後),就會收到該主題的訊息內容(payload)
  • payload(負載):可以理解為訊息內容,是指訂閱者具體需要的內容/

網路傳輸與應用訊息

MQTT會構建鍍層網路傳輸:他將建立客戶端到伺服器的連線,提供兩者之間的一個有序的、無損的、基於位元組流的雙向傳輸。當應用資料通過MQTT網路傳送時,MQTT會把與之相關的服務質量(Qos)和主題名(Topic)相關聯。

MQTT客戶端

一個使用MQTT協議的應用程式或者裝置,他總是建立到伺服器的網路連線。客戶端可以:

  • 釋出其他客戶端可能會訂閱的資訊
  • 訂閱其他客戶端資訊
  • 退訂或刪除應用程式的訊息
  • 斷開與伺服器連線

MQTT伺服器

MQTT伺服器以稱為訊息代理(Broker),可以是一個程式或一臺裝置他是位於訊息釋出者和訂閱者之間,它可以:

  • 接受來自客戶端的連線
  • 接受客戶端釋出的應用資訊
  • 處理來自客戶端的訂閱退訂請求
  • 向訂閱的客戶端轉發應用程式訊息

MQTT協議中的訂閱、主題、會話

訂閱(Subscription)

訂閱包含主題篩選器(Topic Filter)和最大服務質量(Qos)。訂閱會與一個會話(Session)關聯。一個會話可以包含多個訂閱。每個會話中的每個訂閱都有一個不同的主題篩選器。

會話(Session)

每個客戶端與伺服器建立連線後就是一個會話,客戶端和伺服器之間有狀態互動。會話存在與一個網路之間,也可能在客戶端和伺服器之間跨越多個連續的網路連線。

主題名(Topic Name)

連線到一個應用程式訊息的標籤,該標籤與伺服器的訂閱相匹配。伺服器會將訊息傳送給訂閱者所匹配標籤的每個客戶端。

主題篩選器(Topic Filter)

一個對主題名萬用字元篩選器,在訂閱表示式中使用,表示訂閱所匹配到的多個主題。

負載(Payload)

訊息訂閱者所具體接受的內容。

MQTT協議中的方法

MQTT協議中定義了一些方法(也被稱為動作),用於來表示對確定資源所進行操作,這個資源可以代表預先存在的資料或動態生成資料,這取決於伺服器的實現。通常來說,資源指伺服器上的檔案或輸出。主要方法有:

  • Connect:與伺服器建立連線,連線成功後有個回撥函式(如下是python程式碼)
# 連結mqtt伺服器函式
    def on_mqtt_connect(self):
        self.client.connect(self.MQTTHOST, self.MQTTPORT, 60)
        # 開始監聽
        self.client.loop_start() 

# 連結完成後的回撥函式
    def on_connect(self, client, userdata, flags, rc):
        logging.info("+++ Connected with result code {} +++".format(str(rc)))
        self.client.subscribe(self.topic_from_base)
複製程式碼
  • Disconnect: 等待MQTT客戶端完成所做的工作,並與伺服器斷開TCP/IP會話。
  • Subscribe:等待完成訂閱。(python程式碼如下)
# 訂閱函式
    def subscribe(self):
        self.client.subscribe(self.topic_from_base, 1)
        # 訊息到來處理函式
        self.client.on_message = self.on_message
        
    # 接收到資訊後的回撥函式, (client:客戶端資訊,userdata:使用者資訊,msg:訊息體)
    def on_message(self, client, userdata, msg):
        pass
複製程式碼
  • UnSubscribe:等待伺服器取消客戶端的一個或多個訂閱。
  • Publish:MQTT客戶端傳送訊息請求,傳送完成後返回應用程式執行緒。(python程式碼如下)
# 資料傳送函式
    def publish(self, index, fuc):
        # mqtt傳送
        self.client.publish(
            self.topic_to_base, #主題
            to_base_data    # 訊息題
        )
複製程式碼

MQTT協議資料包結構

在MQTT協議中,一個MQTT資料包由:固定頭(Fixed header)、可變頭(Variable header)、訊息題(Payload)三部分組成。MQTT資料包結構如下:

  • 固定頭(Fixed header): 存在與所有MQTT資料包中,表示資料包型別及資料包的分組類標識。
  • 可變頭(Variable header):存在與部分MQTT資料包中,資料包型別決定了可變頭是否存在及其具內容。
  • 訊息體(Payload):存在於部分MQTT資料包中,表示客戶端收到的具體內容。
MQTT固定頭

固定頭存在與所有MQTT資料包中,使用兩個位元組看,共十六位 其結構如下:

image

MQTT資料包型別

位置:Byte 1中的4-7位。

使用四位二進位制,可代表十六種型別訊息:

image

除去0和15位置屬於保留待用,共十四種訊息時間型別。

標誌位

位置:Byte 1 中的0-3

  • DUP :保證訊息可靠傳輸,預設為0,只佔用一個位元組,表示第一次傳送。不能用於檢測訊息重複傳送等。只適用於客戶端或伺服器端嘗試重發PUBLISH, PUBREL, SUBSCRIBE 或 UNSUBSCRIBE訊息,注意需要滿足條件:當QoS > 0訊息需要回復確認 此時,在可變頭部需要包含訊息ID,當值為1時,表示當前效益先前已經被傳送過。
  • Qos(服務質量):
    image
  • RETAIN:釋出保留標識,表示伺服器要保留這次推送的資訊,如果有新的訂閱者出現,就把這訊息推送給它,如果設有那麼推送至當前訂閱者後釋放
剩餘長度

位置:Byte 2

固定頭的第二位元組用來儲存變長頭部和訊息體的總大小的,但不是直接儲存的。這一位元組是可以擴充套件,其儲存機制,前7位用於儲存長度,後一部用做標識。當最後一位為1時,表示長度不足,需要使用二個位元組繼續儲存。例如:計算出後面的大小為0

MQTT可變頭

MQTT資料包中包含一個可變頭,它駐位於固定的頭和負載之間。很多型別資料包中都包括一個2位元組的資料包標識欄位,這些型別的包有:PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK。可變頭部內容位元組長度 + Playload/負荷位元組長度 = 剩餘長度,這個是需要牢記的可變頭的內容因資料包型別而不同,較常的應用是作為包的標識:

image

訊息體(Payloa)

Payload訊息體位MQTT資料包的第三部分,包含CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四種型別的訊息:

  • CONNECT:訊息體內容主要是:客戶端的ClientID、訂閱的Topic、Message以及使用者名稱和密碼。
  • SUBSCRIBE:訊息體內容是一系列的要訂閱的主題以及QoS。
  • SUBACK:訊息體內容是伺服器對於SUBSCRIBE所申請的主題及QoS進行確認和回覆。
  • UNSUBSCRIBE:訊息體內容是要訂閱的主題。

python 簡單測試程式碼

# -*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import time

class Mqtt(object):
    def __init__(self):
        self.MQTTHOST = "********"
        self.MOTTPORT = "********"
        self.client_id = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
        self.client = mqtt.Client(self.client_id)
        self.client.username_pw_set()

        # 設定連結上伺服器後回撥函式
        self.client.on_connect = self.on_connect

        # 設定接收到伺服器訊息後回撥函式
        self.client.on_message = self.on_message

        # 連線伺服器,維持心跳為60秒
        self.client.connect(self.MQTTHOST, self.MOTTPORT, 60)
        self.client.loop_forever()

    def on_connect(self,client, userdata, flag, rc):
        print("Connected with result code " + str(rc))
        client.subscribe("test")

    def on_publish(self, topic, payload, qos):
        self.client.publish(topic, payload, qos)
        print(topic+"訊息傳送成功。。。。。")
        return 1

    def on_message(self, client, userdata, msg):
        print(msg.topic+ "" +msg.payload.decode("utf-8"))
        return 1

    def on_subscribe(self, topic):
        self.client.subscribe(topic, 1)
        self.client.on_message = self.on_message
        return 1

if __name__ == "__main__":
    mqtt = Mqtt()
    mqtt.on_publish("test", "helloWord",1)
複製程式碼
參考:

https://www.jianshu.com/p/5c42cb0ed1e9
http://www.runoob.com/w3cnote/mqtt-intro.html
http://www.blogjava.net/yongboy/archive/2014/02/07/409587.html

新手上車,請多指教,如有問題,請郵件聯絡:young5678@qq.com

相關文章