道哥的第 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 種:
- 為了程序的排程: 可以通過信号來實作;
- 為了共享資源:可以通過互斥鎖、信号量、讀寫鎖、檔案鎖等來實作;
- 為了傳遞資料:可以通過共享記憶體、命名管道、消息隊列、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. 跨語言
服務端和用戶端不必使用同一種程式設計語言。
- 陳碩老師描述的是通用的 Socket 通信,是以用戶端和服務端一般位于不同的實體機器上。
- 在嵌入式開發中,一般都是用同一種程式設計語言,是以,跨語言這個有點可以忽略不計了。
三、MQTT 消息總線
1. MQTT 是一個通信的機制
對物聯網領域熟悉的小夥伴,對于 MQTT 消息總線一定非常熟悉,目前幾大物聯網雲平台(亞馬孫、阿裡雲、華為雲)都提供了 MQTT 協定的接入方式。
目前,學習 MQTT 最好的文檔是 IBM 的線上手冊:https://developer.ibm.com/zh/technologies/messaging/articles/iot-mqtt-why-good-for-iot/。
這裡,我直接把一些重點資訊列出來:
- MQTT協定輕量、簡單、開放和易于實作;
- MQTT 是基于釋出 (Publish)/訂閱 (Subscribe)範式的消息協定;
- MQTT 工作在 TCP/IP協定族上;
- 有三種消息釋出服務品質;
- 小型傳輸,開銷很小(固定長度的頭部是 2 位元組),協定交換最小化,以降低網絡流量;
MQTT 消息傳輸需要一個中間件,稱為:Broker,其實也就是一個 Server。通信模型如下:
- MQTT Broker 需要首先啟動;
- ClientA 和 ClientB 需要連接配接到 Broker;
- ClientA 訂閱主題 topic_1,ClientB 訂閱主題 topic_2;
- ClientA 往 topic_2 這個主題發送消息,就會被 ClientB 接收到;
- 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,就可以互相發送資料了。運作模型如下:
每一個程序隻需要訂閱一個固定的 topic(比如:自己的 client Id),那麼其他程序如果想要發送資料給它,就直接發送到這個 topic 即可。
1. 一個嵌入式系統的通信架構
我之前開發過一個環境監測系統,采集大氣中的 PM2.5、PM10等污染物參數,在 Contex A8 平台下開發,需要實作資料記錄(資料庫)、UI 監控界面等功能。
污染物的資料采樣硬體子產品是第三方公司提供的,我們隻需要通過該子產品提供的序列槽協定去控制采樣裝置、接收采樣資料即可。最終設計的通信模型如下:
- UI 程序通過消息總線,發送控制指令給采樣控制程序,采樣控制程序接收到後通過序列槽發送控制指令給采樣子產品;
- 采樣控制程序從序列槽接收采樣子產品發來的PM2.5等資料後,把所有的資料發送到消息總線上指定的 topic 中;
- UI 程序程訂閱該 topic,接收到資料後,顯示在螢幕上;
- 資料庫程序也訂閱該 topic,接收到資料後,把資料存儲在 SQLite 資料庫中;
在這個産品中,核心程序是采樣控制程序,負責與采樣子產品的互動。通過把 UI 處理、資料庫處理設計成獨立的程序,降低了系統的複雜性,即使這 2 個程序崩潰了,也不會影響到核心的采樣控制程序。
比如:如果 UI 程序出現錯誤崩潰了,會立刻重新開機,啟動之後通過緩存資訊知道此刻正在執行采樣工作,于是 UI 程序立刻連接配接到消息總線、進入采樣資料顯示界面,繼續接收、顯示采樣控制程序發出的PM2.5等資料。
這個通信模型還有另外一個有點:可擴充性。
在項目開發的後期,甲方說需要內建一個第三方的氣體子產品,用來采集大氣中NO、SO2等參數,通信方式是 RS485。
此時擴充這個功能子產品就異常簡單了,直接寫一個獨立的氣體參數程序,接入到消息總線上。這個程序通過 RS485,從第三方氣體子產品接收到NO、SO2等氣體參數時,直接往消息總線上的某個 topic 一丢,UI程序、資料庫程序訂閱這個 topic,就可以立刻接收到氣體相關的資料了。
此外,這個設計模型還有其他一些優點:
- 并行開發:每個程序可以由不同的人員并行開發,隻要互相之間定義好通信協定即可;
- 調試友善:由于發送的資料都是 manual readable,在開發階段,可以在 PC 機上專門寫一個監控程式,接入到嵌入式系統中的 MQTT Broker 之後,這樣就可以接收到所有程序發出的消息;
- 通信安全:在産品 release 之後,為了防止其他人偷聽資料(比如 2 中的調試程序),可以為 MQTT Broker 指定一個配置檔案,隻能允許本地程序(127.0.0.1)連接配接到消息總線上。
2. 稍微複雜一點的通信模型
在剛才描述的嵌入式系架構設計中,每一個程序都是運作在本地的,所有的消息也都是在系統内進行收發。那麼,如果需要把資料傳輸到雲端、或者需要從雲端接收一些控制指令,又該如何設計呢?
加入一個 MQTT Bridge 橋接子產品即可!也就是再增加一個程序,這個程序同時連接配接到雲端的 MQTT Broker 和本地的 MQTT Broker,通信模型如下:
- MQTT Bridge 接收到雲端發來的指令時,轉發到本地的消息總線上;
- 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
我的開發環境是:
- 編譯器:gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
- Mosquitto 版本: mosquitto-1.4.9
mosquitto-1.4.9 可以到官方網站下載下傳,也可以從文末的網盤中下載下傳,你也可以嘗試更高的版本。
編譯、安裝指令:
make
make install prefix=$PWD/install
成功安裝之後,可以在目前目錄的 install 檔案夾下看到輸出檔案:
- bin:mqtt 用戶端程式;
- include:應用程式需要 include 的頭檔案;
- lib:應用程式需要連結的庫檔案;
- 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
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
make
mqtt_client
./mqtt_client 127.0.0.1 1883
./mosquitto_pub -h 127.0.0.1 -p 1883 -m "hello123" -t "topic_01"