mosquitto服務器源碼分析並修改將發佈的數據保存到數據庫

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。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章