mosquitto服務器不保存數據,只是轉發,如果想要保存數據只能對源碼進行修改,想實現mosquitto服務器(broker)接收到發佈端(pub)的數據後將收到數據保存到數據庫,等到有訂閱端訂閱主題就可以調數據庫裏相對應的主題(topic)的數據發送給客戶端(sub)。
一、Mosquitto源碼中的結構體
幾個常看到的結構體便於源碼理解:
struct mosquitto用來保存一個客戶端連接的所有信息,如用戶名、密碼、用戶ID、向該客戶端發送的消息等
struct mosquitto{
mosq_sock_t sock; //連接套接字
enum mosquitto__protocol protocol; //客戶端使用的協議版本號
char *address; //客戶端ip
char *id; //客戶端id
char *username; //客戶端的用戶名
char *password; //客戶端密碼(安全認證時使用)
uint16_t keepalive; //保活時間
uint16_t last_mid;
enum mosquitto_client_state state; //客戶端的狀態
time_t last_msg_in; //收到的上一條消息的時間
time_t next_msg_out; //下一條待發送的消息的時間
time_t ping_t; //發送ping request的時間間隔
struct mosquitto__packet in_packet; //收到的報文
struct mosquitto__packet *out_packet; //發送報文鏈表
struct mosquitto_message *will; //遺囑消息鏈表
struct mosquitto_message_all *in_messages; //收到的消息鏈表
struct mosquitto_message_all *out_messages; //發送的消息鏈表
.....
}
struct mosquitto_message{
int mid;
char *topic;
void *payload;
int payloadlen;
int qos;
bool retain;
};
struct _mosquitto_subhier在mosquitto_broker.h中定義,用於保存訂閱樹的節點(包括葉子節點和中間節點),mosquitto對訂閱樹採用孩子 - 兄弟鏈表法的方式進行存儲,該存儲方式主要藉助於數據結構struct _mosquitto_subhier來完成。
struct _mosquitto_subhier {
struct _mosquitto_subhier * children; //第一個孩子節點
struct _mosquitto_subhier * next; //下一個兄弟節點
struct _mosquitto_subleaf * subs; //訂閱列表
char * topic; //該節點對應的主題片段
struct mosquitto_msg_store * retain; //該主題下被保留標記的消息
};
struct mosquitto_msg_store{
struct mosquitto_msg_store *next;
struct mosquitto_msg_store *prev;
dbid_t db_id;
char *source_id;
char **dest_ids;
int dest_id_count;
int ref_count;
char* topic;
mosquitto__payload_uhpa payload;
uint32_t payloadlen;
uint16_t source_mid;
uint16_t mid;
uint8_t qos;
bool retain;
};
參考:http://www.360doc.com/content/19/0324/20/62984756_823882579.shtml
二、mosquitto源碼分析
參考:https://blog.csdn.net/weixin_38498942/article/details/88680291
服務端的實現邏輯主要是在/lib和/src目錄下,main函數所在文件是:/mosquitto-1.5.5/src/mosquitto.c,其大致流程是:
1. 調用net__broker_init函數,創建套接字。
2. 調用config__init函數,初始化服務端配置相關項。配置相關的結構體:struct mosquitto__config。
3. 調用config__parse_args,根據配置文件和命令行參數初始化服務端配置。
4. 調用db__open函數,主要是創建了訂閱樹。該變量是服務端最重要的結構體,維護了訂閱樹根節點,訂閱客戶端的索引,消息鏈表等。
5. 調用mosquitto_security_module_init函數,給db中安全認證相關的成員變量初始化。
6. 調用mosquitto_security_init,主要是函數指針調函數(上一步已經給函數指針賦值),實現安全認證相關的操作。
7. 根據config.listener_count數目,循環調用net__socket_listen函數,在該函數中創建監聽套接字,設置套接字屬性,並開始監聽。
8. 接下來將所有config.listener[i]的套接字統一放到listensock數組中去管理;
9. 調用drop_privileges函數,如果以root身份運行,此函數將嘗試更改爲非特權用戶和組。
10. 接下來是調用signal函數,爲信號註冊信號處理函數,實現消息的異步通知。
11.最後進入到mosquitto_main_loop函數,該函數的主要功能就是處理客戶端的訂閱以及發佈等請求,客戶端訂閱的主題有發佈調用db__message_write()發佈消息。
12.db__message_write();根據qos質量不同調用send_publish給已經訂閱的客戶端
13.send__publish();查看連接跟主題是否有效接着調用send__real_publish()
14.send__real_publish();調用packet__write_byte() 大概是裝包,返回packet__queue();
15.packet__queue();也還是對數據的封裝,返回packet__write();
16.packet__write();調用net__write(),net__write()裏如果是window調用write(),如果不是調用send(),最後發佈出去,大致這樣一個流程
但是db__message_write()是有訂閱端訂閱了對應的主題纔會發佈出去,如果沒有訂閱,單是發佈,消息會被遺棄不會被髮布出去。依舊在mosquitto_main_loop裏loop_handle_reads_writes根據讀寫事件發送或接收數據包。
1.loop_handle_reads_writes(),使用多路複用poll監聽事件,根據讀寫事件發送或接收數據包,當有事件可讀調用packet__read()。
2.packet__read()根據mosq->in_packet.command讀取有關packet的內容,然後調用handle__packet();
3.handle__packet()根據mosq->in_packet.command返回調用handle__publish()函數, case PUBLISH: return handle__publish(mosq);
4.handle__publish()需要發佈的數據進入到handle__publish函數,函數的最後根據qos服務質量調用message__cleanup()清空掉,所以在handle__publish函數可以讀取到發佈端發佈的消息即使沒有被訂閱。在這可以將發佈的數據保存到數據庫。
在handle__publish函數裏面檢查主題前將發佈的主題、載荷打印出來。
運行mosquitto服務器,另一個終端運行mosquitto_pub, 在connect mosqpub發佈端連接進來和disconnect斷開連接之間打印出消息
讀出數據後就可以將數據保存到數據庫
/mosquitto-1.5.5/src下面的Makefile文件在總目標前加上sqlite3庫的位置以及sqlite3.h頭文件的位置
LDFLAGS是告訴鏈接器從哪裏尋找庫文件,LDFLAGS:在裏面指定庫文件的位置,CFLAGS 表示用於 C 編譯器的選項,CFLAGS: 指定頭文件(.h文件)的路徑
在Makefile文件總目標前
LDFLAGS+=-L/usr/local/lib -lsqlite3
BROKER_CFLAGS+=-I/usr/local/include
樹莓派下用mosquitto庫寫的客戶端,每隔30秒給服務器發佈溫度
服務器讀到主題爲溫度,解析載荷並插入數據庫
查看數據庫
三、遇到的問題
主要還是對gcc和Makefile的不瞭解,平時自己寫着的程序gcc命令也就一點,自己寫的makefile也很簡陋,分析修改源碼看到這麼長的Makefile就傻眼了,還是基礎不牢,我會好好再瞭解一下gcc和Makefile。