天天看點

物聯網網關開發:程序之間通信方式--MQTT消息總線

道哥的第 020 篇原創

目錄

  • ​​一、Linux 系統中的程序之間通信(IPC)​​
  • ​二、基于 Socket 通信的優點​
  • ​​1. 跨主機,具有伸縮性​​
  • ​​2. 作業系統會自動回收資源​​
  • ​​3. 可記錄、可重制​​
  • ​​4. 跨語言​​
  • ​三、MQTT 消息總線​
  • ​​1. MQTT 是一個通信的機制​​
  • ​​2. MQTT 的實作​​
  • ​​3. 在 MQTT 之上,設計自己的通信協定​​
  • ​四、嵌入式系統中如何利用 MQTT 消息總線​
  • ​​1. 一個嵌入式系統的通信架構​​
  • ​​2. 稍微複雜一點的通信模型​​
  • ​五、Mosquitto: 一個簡單的測試代碼​
  • ​​1. 直接通過 apt 來安裝、測試​​
  • ​​2. 通過源碼來手動編譯、測試​​
  • ​​六、總結​​
  • ​七、資源下載下傳​
  • ​​1. mosquitto-1.4.9.tgz​​
  • ​​2. Mosquitto Demo 示例代碼​​

一、Linux 系統中的程序之間通信(IPC)

作為一名嵌入式軟體開發人員來說,處理程序之間的通信是很常見的事情。從通信目的的角度來看,我們可以把程序之間的通信分成 3 種:

  1. 為了程序的排程: 可以通過信号來實作;
  2. 為了共享資源:可以通過互斥鎖、信号量、讀寫鎖、檔案鎖等來實作;
  3. 為了傳遞資料:可以通過共享記憶體、命名管道、消息隊列、Socket來實作。

關于上面提到的這些、作業系統為我們提供的通信原語,網絡上的各種資料、文章滿天飛,在這裡就不啰嗦了。在這些方法中應該如何選擇呢?根據我個人的經驗,貴精不貴多,認真挑選三四樣東西就能完全滿足日常的工作需要。

我們今天想讨論的問題主要是第 3 個:傳遞資料,在上面這幾種傳遞資料的方法中,我最喜歡、最常用的就是 Socket 通信。

有些小夥伴可能會說:Socket 通信就是 TCP/IP 的那一套東西,還需要自己管理連接配接、對資料進行組包、分包,也是挺麻煩的。

沒錯,Socket 通信本身的确需要手動來處理這些底層的東西,但是我們可以給 Socket 穿上一層“外衣”:利用 MQTT 消息總線,在系統的各程序之間進行資料互動,下面我們就一一道來。

二、基于 Socket 通信的優點

這裡我就不自己發揮了,直接引用陳碩老師的那本書《Linux 多線程服務端程式設計》這本書中的觀點(第 65 頁,3.4小節):

1. 跨主機,具有伸縮性

反正都是多程序了,如果一台機器的處理能力不夠,就能用多台主機來處理。把程序分散到同一台區域網路的多台機器上,程式改改 Host:Port 配置就能繼續用。相反,文章開頭部分列出的那些程序之間通信方式都不能跨機器,這就限制了可擴充性。

2. 作業系統會自動回收資源

TCP port 由一個程序獨占,當程式意外退出時,作業系統會自動回收資源,不會給系統留下垃圾,程式重新開機之後能比較容易地恢複。

3. 可記錄、可重制

兩個程序通過 TCP 通信,如果一個崩潰了,作業系統會關閉連接配接,另一個程序幾乎立刻就能感受到,可以快速 failover。當然應用層的心跳是必不可少的。(補充:作業系統本身對于 TCP 連接配接有一個保活時間,預設是 2 個小時,而且是針對全局的。)

4. 跨語言

服務端和用戶端不必使用同一種程式設計語言。

  1. 陳碩老師描述的是通用的 Socket 通信,是以用戶端和服務端一般位于不同的實體機器上。
  2. 在嵌入式開發中,一般都是用同一種程式設計語言,是以,跨語言這個有點可以忽略不計了。

三、MQTT 消息總線

1. MQTT 是一個通信的機制

對物聯網領域熟悉的小夥伴,對于 MQTT 消息總線一定非常熟悉,目前幾大物聯網雲平台(亞馬孫、阿裡雲、華為雲)都提供了 MQTT 協定的接入方式。

目前,學習 MQTT 最好的文檔是 IBM 的線上手冊:​​https://developer.ibm.com/zh/technologies/messaging/articles/iot-mqtt-why-good-for-iot/。​​

這裡,我直接把一些重點資訊列出來:

  1. MQTT協定輕量、簡單、開放和易于實作;
  2. MQTT 是基于釋出 (Publish)/訂閱 (Subscribe)範式的消息協定;
  3. MQTT 工作在 TCP/IP協定族上;
  4. 有三種消息釋出服務品質;
  5. 小型傳輸,開銷很小(固定長度的頭部是 2 位元組),協定交換最小化,以降低網絡流量;

MQTT 消息傳輸需要一個中間件,稱為:Broker,其實也就是一個 Server。通信模型如下:

物聯網網關開發:程式之間通信方式--MQTT消息總線
  1. MQTT Broker 需要首先啟動;
  2. ClientA 和 ClientB 需要連接配接到 Broker;
  3. ClientA 訂閱主題 topic_1,ClientB 訂閱主題 topic_2;
  4. ClientA 往 topic_2 這個主題發送消息,就會被 ClientB 接收到;
  5. ClientB 往 topic_1 這個主題發送消息,就會被 ClientA 接收到;

基于 topic 主題的通信方式有一個很大的好處就是解耦,一個用戶端可以訂閱多個 topic,任何接入到總線的其他用戶端都可以往這些 topic 中發送資訊(一個用戶端發送消息給自己也是可以的)。

2. MQTT 的實作

MQTT 隻是一個協定而已,在 IBM 的線上文檔中可以看到,有很多語言都實作了 MQTT 協定,包括:C/C++、Java、Python、C#、JavaScript、Go、Objective-C等等。那麼對于嵌入式開發來說,使用比較多的是這幾個實作:

Mosquitto;

Paho MQTT;

wolfMQTT;

MQTTRoute。

在下面,我們會重點介紹 Mosquitto 這個開源實作的編譯和使用方式,這也是我在項目中使用最多的。

3. 在 MQTT 之上,設計自己的通信協定

從上面的描述中可以看出,MQTT 消息總線就是一個通信機制,為通信主體提供了一個傳遞資料的通道而已。

在這個通道之上,我們可以根據實際項目的需要,發送任何格式、編碼的資料。在項目中,我們最常用的就是 json 格式的純文字,這也是各家物聯網雲平台所推薦的方式。如果在文本資料中需要包含二進制資料,那就轉成 BASE64 編碼之後再發送。

四、嵌入式系統中如何利用 MQTT 消息總線

從上面的描述中可以看到,隻要在服務端運作着一個 MQTT Broker 服務,每個連接配接到總線的用戶端都可以靈活地互相收發資料。

我們可以把這個機制應用在嵌入式應用程式的設計中:MQTT Broker 作為一個獨立的服務運作在嵌入式系統本地,其他需要互動的程序,隻要連接配接到本地的這個 Broker,就可以互相發送資料了。運作模型如下:

物聯網網關開發:程式之間通信方式--MQTT消息總線

每一個程序隻需要訂閱一個固定的 topic(比如:自己的 client Id),那麼其他程序如果想要發送資料給它,就直接發送到這個 topic 即可。

1. 一個嵌入式系統的通信架構

我之前開發過一個環境監測系統,采集大氣中的 PM2.5、PM10等污染物參數,在 Contex A8 平台下開發,需要實作資料記錄(資料庫)、UI 監控界面等功能。

污染物的資料采樣硬體子產品是第三方公司提供的,我們隻需要通過該子產品提供的序列槽協定去控制采樣裝置、接收采樣資料即可。最終設計的通信模型如下:

物聯網網關開發:程式之間通信方式--MQTT消息總線
  1. UI 程序通過消息總線,發送控制指令給采樣控制程序,采樣控制程序接收到後通過序列槽發送控制指令給采樣子產品;
  2. 采樣控制程序從序列槽接收采樣子產品發來的PM2.5等資料後,把所有的資料發送到消息總線上指定的 topic 中;
  3. UI 程序程訂閱該 topic,接收到資料後,顯示在螢幕上;
  4. 資料庫程序也訂閱該 topic,接收到資料後,把資料存儲在 SQLite 資料庫中;

在這個産品中,核心程序是采樣控制程序,負責與采樣子產品的互動。通過把 UI 處理、資料庫處理設計成獨立的程序,降低了系統的複雜性,即使這 2 個程序崩潰了,也不會影響到核心的采樣控制程序。

比如:如果 UI 程序出現錯誤崩潰了,會立刻重新開機,啟動之後通過緩存資訊知道此刻正在執行采樣工作,于是 UI 程序立刻連接配接到消息總線、進入采樣資料顯示界面,繼續接收、顯示采樣控制程序發出的PM2.5等資料。

這個通信模型還有另外一個有點:可擴充性。

在項目開發的後期,甲方說需要內建一個第三方的氣體子產品,用來采集大氣中NO、SO2等參數,通信方式是 RS485。

此時擴充這個功能子產品就異常簡單了,直接寫一個獨立的氣體參數程序,接入到消息總線上。這個程序通過 RS485,從第三方氣體子產品接收到NO、SO2等氣體參數時,直接往消息總線上的某個 topic 一丢,UI程序、資料庫程序訂閱這個 topic,就可以立刻接收到氣體相關的資料了。

此外,這個設計模型還有其他一些優點:

  1. 并行開發:每個程序可以由不同的人員并行開發,隻要互相之間定義好通信協定即可;
  2. 調試友善:由于發送的資料都是 manual readable,在開發階段,可以在 PC 機上專門寫一個監控程式,接入到嵌入式系統中的 MQTT Broker 之後,這樣就可以接收到所有程序發出的消息;
  3. 通信安全:在産品 release 之後,為了防止其他人偷聽資料(比如 2 中的調試程序),可以為 MQTT Broker 指定一個配置檔案,隻能允許本地程序(127.0.0.1)連接配接到消息總線上。
物聯網網關開發:程式之間通信方式--MQTT消息總線

2. 稍微複雜一點的通信模型

在剛才描述的嵌入式系架構設計中,每一個程序都是運作在本地的,所有的消息也都是在系統内進行收發。那麼,如果需要把資料傳輸到雲端、或者需要從雲端接收一些控制指令,又該如何設計呢?

加入一個 MQTT Bridge 橋接子產品即可!也就是再增加一個程序,這個程序同時連接配接到雲端的 MQTT Broker 和本地的 MQTT Broker,通信模型如下:

物聯網網關開發:程式之間通信方式--MQTT消息總線
  1. MQTT Bridge 接收到雲端發來的指令時,轉發到本地的消息總線上;
  2. MQTT Bridge 接收到本地的消息時,轉發到雲端的消息總線上。

五、Mosquitto: 一個簡單的測試代碼

上面的内容主要讨論的是設計的思想,具體到代碼層面,我一般使用的是 Mosquitto 這個開源的實作。

在 Linux 系統中安裝、測試都非常友善,下面就簡單說明一下。

1. 直接通過 apt 來安裝、測試

可以參考這個文檔(​​https://www.vultr.com/docs/how-to-install-mosquitto-mqtt-broker-server-on-ubuntu-16-04​​)來安裝測試。

(1) 安裝

sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa
sudo apt-get update
sudo apt-get install mosquitto
sudo apt-get install mosquitto-clients
      

(2) 測試

mosquitto broker 在安裝之後會自動啟動,可以用 ​

​netstat​

​ 檢視 1883 端口來确認一下。

接收端:連接配接到 broker 之後,訂閱 "test" 這個 topic。

mosquitto_sub -t "test"
      

發送端:連接配接到 broker 之後,往 "test" 這個 topic 發送字元串 “hello”。

mosquitto_pub -m "hello" -t "test"
      

當發送端執行 mosquitto_pub 時,在接收端的終端視窗中,就可以接收到 “hello” 這個字元串。

2. 通過源碼來手動編譯、測試

通過 apt 來安裝主要是用來簡單的學習和測試,如果要在項目開發中使用 Mosquitto,肯定需要手動編譯,得到頭檔案和庫檔案,然後複制到應用程式中使用。

(1) 手動編譯、安裝 Mosquitto

我的開發環境是:

  1. 編譯器:gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
  2. Mosquitto 版本: mosquitto-1.4.9

mosquitto-1.4.9 可以到官方網站下載下傳,也可以從文末的網盤中下載下傳,你也可以嘗試更高的版本。

編譯、安裝指令:

make
make install prefix=$PWD/install
      

成功安裝之後,可以在目前目錄的 install 檔案夾下看到輸出檔案:

物聯網網關開發:程式之間通信方式--MQTT消息總線
  1. bin:mqtt 用戶端程式;
  2. include:應用程式需要 include 的頭檔案;
  3. lib:應用程式需要連結的庫檔案;
  4. sbin:mqtt broker 服務程式。

在編譯過程中,如果遇到一些諸如:ares.h、uuid.h 等依賴檔案找不到的錯誤,隻需要通過 apt 指令安裝響應的開發包即可。

(2) 最簡單的 mosquitto 用戶端代碼

在 mosquitto 源碼中,提供了豐富的 Sample 示例。如果你不樂意去探索,可以直接下載下傳文末的這個網盤中的 Demo 示例程式,這個程式連接配接到消息總線上之後,訂閱 “topic_01” 這個主題。當然,你也可以修改代碼去發送消息(調用:mosquitto_publish 這個函數)。

進入 c_mqtt 示例代碼目錄之後,可以看到已經包含了 bin、include 和 lib 目錄,它們就是從上面(1)中安裝目錄 install 中複制過來的。

執行 ​

​make​

​ 指令之後,即可編譯成功,得到可執行檔案: mqtt_client。

測試過程如下:

Step1: 啟動 MQTT Broker

在第 1 個終端視窗中,啟動 sbin/mosquitto 這個 Broker 程式。如果你在上面測試中已經啟動了一個 broker,需要先 kill 掉之前的那個 broker,因為它們預設都使用 1883 這個端口,無法共存。

Step2: 啟動接收端程式 mqtt_client

在第 2 個終端視窗中,啟動 ​

​mqtt_client​

​ 也就是我們的示例代碼編譯得到的可執行程式,它訂閱的 topic 是 “topic_01”。

./mqtt_client 127.0.0.1 1883
      

參數 1: Broker 服務的 IP 位址,因為都是在本地系統中,是以是 127.0.0.1;

參數 2: 端口号,一般預設是1883。

Step3: 啟動發送端程式 bin/mosquitto_pub

在第 3 個終端視窗中,啟動 bin/mosquitto_pub,指令如下:

./mosquitto_pub -h 127.0.0.1 -p 1883 -m "hello123" -t "topic_01"
      

參數 -h:Broker 服務的 IP 位址,因為都是在本地系統中,是以是 127.0.0.1;

參數 -p:端口号 1883;

參數 -m:發送的消息内容;

參數 -t:發送的主題 topic。

此時,可以在第 2 個終端視窗(mqtt_client)中列印出接收到的消息。

六、總結

這篇文章主要介紹了嵌入式系統中的一個設計模式:通過消息總線來實作程序之間的通信,并介紹了 Mosquitto 這個開源實作。

在實際的項目中,還需要更加嚴格的權限控制,比如:在接入消息總線時提供使用者名、密碼、裝置證書,用戶端的名稱必須滿足指定的格式,訂閱的 topic 必須符合一定的格式等等。

在下一篇文章中,我們繼續讨論這個話題,給出一個更具體、更實用的 Demo 例程。

七、資源下載下傳

1. mosquitto-1.4.9.tgz

連結:​​https://pan.baidu.com/s/1izQ3dAlGbHiHwDvKnOSfyg​​

密碼:dozt

2. Mosquitto Demo 示例代碼

連結:​​https://pan.baidu.com/s/1M-dU3xapNbKyk2w07MtDyw​​

密碼:aup3

物聯網網關開發:程式之間通信方式--MQTT消息總線

物聯網網關開發:程式之間通信方式--MQTT消息總線

物聯網網關開發:程式之間通信方式--MQTT消息總線
物聯網網關開發:程式之間通信方式--MQTT消息總線

物聯網網關開發:程式之間通信方式--MQTT消息總線

sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa
sudo apt-get update
sudo apt-get install mosquitto
sudo apt-get install mosquitto-clients
      

​netstat​

mosquitto_sub -t "test"
      
mosquitto_pub -m "hello" -t "test"
      

make
make install prefix=$PWD/install
      
物聯網網關開發:程式之間通信方式--MQTT消息總線

​make​

​mqtt_client​

./mqtt_client 127.0.0.1 1883
      
./mosquitto_pub -h 127.0.0.1 -p 1883 -m "hello123" -t "topic_01"