一、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
不吹噓,不炒作,不浮誇,認真寫好每一篇文章!
歡迎轉發、分享給身邊的技術朋友,道哥在此表示衷心的感謝! 轉發的推薦語已經幫您想好了:
道哥總結的這篇總結文章,寫得很用心,對我的技術提升很有幫助。好東西,要分享!
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告,必須在文章中給出原文連線。作者:道哥(公眾號: IOT物聯網小鎮)
知乎:道哥
B站:道哥分享
掘金:道哥分享
CSDN:道哥分享
推薦閱讀
C語言指標-從底層原理到花式技巧,用圖文和程式碼幫你講解透徹
一步步分析-如何用C實現物件導向程式設計
提高程式碼逼格的利器:巨集定義-從入門到放棄
原來gdb的底層除錯原理這麼簡單
利用C語言中的setjmp和longjmp,來實現異常捕獲和協程
關於加密、證照的那些事
深入LUA指令碼語言,讓你徹底明白除錯原理