[轉]:MQTT協議原理簡介與代碼實現

文章目錄

1. 前言
2. 簡介
3. 實現方式
4. 特性
4.1 Qos 0的工作原理
4.2 Qos 1的工作原理
4.3 Qos 2的工作原理
5. 事件上報
6. 代碼實現
6.1 發佈者
6.2 訂閱者
7. 實驗現象
7.1 發佈者
7.2 訂閱者
參考文獻

1. 前言

物聯網 (Internet of things(IoT)), 通過各種信息傳感器、無線網絡技術,實現不同物理對象之間互聯互通。
物聯網設備與PC和服務器相比,性能低下。
物聯網帶寬與局域網帶寬相比,傳輸帶寬小,速率低。
實現物聯網中各個設備之間的信息傳輸,MQTT是個非常合適的工具。

2. 簡介

物聯網 (Internet of things(IoT)), 通過各種信息傳感器、無線網絡技術,實現不同物理對象之間互聯互通。
物聯網設備與PC和服務器相比,性能低下。
物聯網帶寬與局域網帶寬相比,傳輸帶寬小,速率低。
實現物聯網中各個設備之間的信息傳輸,MQTT是個非常合適的工具。

3. 實現方式

實現MQTT協議需要客戶端和服務器端通訊完成,在通訊過程中,MQTT協議中有三種身份:發佈者( publisher )、代理(broker)、訂閱者(subscriber)。
其中,消息的發佈者和訂閱者都是客戶端,消息代理是服務器,消息發佈者可以同時是訂閱者。
MQTT傳輸的消息分爲:主題(Topic)和負載(payload)兩部分:
(1)Topic:訂閱的主題,即channel,頻道;
(2)payload:消息的內容,發佈者向訂閱者發佈的具體消息。
爲了方便大家對上述文字的理解,具舉例如下場景:

如上圖所示,subscribers1和subscribers2訂閱的主題是topic1和topic2,publisher1向topic1這個主題發送數據,因此只有subscribers1才能收到,subscribers2是收不到topic1這個主題中的消息的。對於topic2來說,也是同樣的道理。
現在問題來了,如何MQTT如何確保消息可靠的傳輸呢?

4. 特性

MQTT支持QOS(quality of service 服務質量),提供信息的可靠傳輸。
Qos 0:信息最多發送一次。
Qos 1:消息最少發送一次。
Qos 2:消息只發送一次。

4.1 Qos 0的工作原理

由上圖可知,Qos的消息最多發送一次,發送信息後,就不管了。是一種盡力而爲的服務類型。

4.2 Qos 1的工作原理

由上圖可知,Qos 1工作時會有確認機制,發佈者發送消息給到代理服務器,代理服務器如果收到了,就需要回應PUBACK報文,表示自己收到的數據。當發佈者收到PUBACK數據時,會將本地緩存的信息刪除。
當然,如果代理沒有收到數據,發佈者沒有收到PUBACK的應答信息,一段時間之後發佈者就會進行信息重傳。
代理和訂閱者之間也是同樣的道理。
在使用Qos 1時,訂閱者可能會收到重複的信息,因此Qos 1適用於客戶端能夠接收並處理重複信息的場景。

4.3 Qos 2的工作原理

如上圖所示,Qos 2中發佈者的信息只會發送一次,可以避免重複信息。
同時,雖然信息只發送一次,但是會有多次的確認機制,從而保證可靠。
綜上,從0到2,可靠程度是逐漸上升的。

5. 事件上報

虛擬機生命週期發生變化時,會產生事件通知。但是,事件發生時,默認情況下,不會傳輸給到前端界面的。因此,需要藉助mqtt協議,輔助進行信息的傳遞。

6. 代碼實現

6.1 發佈者

#include <stdio.h>
#include <stdlib.h>
#include <libvirt/libvirt.h>
#include <pthread.h>
#include <time.h>
#include <string.h>
#include "mosquitto.h"

#define HOST "127.0.0.1"
#define PORT  1883
#define KEEP_ALIVE 60
#define MSG_MAX_SIZE  512


static int  myEventCallback(virConnectPtr conn,
              virDomainPtr dom,
              int event,
              int detail,
              void *opaque) 
{
    const char *name = virDomainGetName(dom);
    struct tm *currTime;
    time_t now;
    time(&now);
    currTime = localtime(&now);

    int ret;
    struct mosquitto *mosq;
    char buff[MSG_MAX_SIZE];
        
    ret = mosquitto_lib_init();

    mosq =  mosquitto_new("Pub", true, NULL);

    char username[] = "username";
    char password[] = "123456";
    int authRet = mosquitto_username_pw_set(mosq, username, password);

    ret = mosquitto_connect(mosq, HOST, PORT, KEEP_ALIVE);

    printf("Start!\n");

    snprintf(buff, MSG_MAX_SIZE, "%d/%d/%d %d:%d:%d event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);

   
    mosquitto_publish(mosq, NULL, "topic1", strlen(buff) + 1, buff, 0, 0);

    mosquitto_disconnect(mosq);
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    printf("End!\n");

    return 0;
}

static void* eventThreadLoop() {
    while(1) {
        if(virEventRunDefaultImpl() < 0) {
            printf("Run errer.\n");
        }
    }
    abort();
}

int main() {

    virConnectPtr conn = NULL;

    int eventid = VIR_DOMAIN_EVENT_ID_LIFECYCLE;

    virEventRegisterDefaultImpl();

    conn = virConnectOpen(NULL);   

    pthread_t eventThread;

    pthread_create(&eventThread, NULL, eventThreadLoop, NULL);

    int id = virConnectDomainEventRegisterAny(conn, 
                               NULL,
                          eventid,
                          VIR_DOMAIN_EVENT_CALLBACK(myEventCallback),
                          NULL,
                          NULL);

    while(1) pause();

    virConnectDomainEventDeregisterAny(conn, id);

    virConnectClose(conn);

    return 0;
}





6.2 訂閱者

#include <stdio.h>
#include <stdlib.h>
#include "mosquitto.h"

#define HOST "127.0.0.1"
#define PORT 1883
#define KEEPALIVE 60

void my_connect_callback(struct mosquitto *mosq, void *obj, int rec) {
    printf("Call the function: my_connect_callback.\n");
    if(rec){
        printf("On_connect error.\n");
        exit(1);
    } else {
    if(mosquitto_subscribe(mosq, NULL, "topic1", 0)) {
            printf("Set topic error.\n");
        exit(1);
        }
    }
}

void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rec) {
    printf("Call the function: my_disconnect_callback.\n");
}

void my_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int*granted_qos) {
    printf("Call the function: my_subscribe_callback.\n");
}

void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) {
    printf("Call the function: my_message_callback.\n");
    
    printf("Receive a message %s: %s", (char*)msg->topic, (char*)msg->payload);
}

int main() {
    int ret = 0;
    struct mosquitto *mosq;

    ret = mosquitto_lib_init();
    if(ret) {
        printf("init lib is failed.\n");
        return -1;
    }

    mosq = mosquitto_new("sub", true, NULL);
    if(mosq == NULL) {
        printf("create new mosquitto instance failed.\n");
        mosquitto_lib_cleanup();
        return -1;
    }

    const char username[] = "username";
    const char password[] = "123456";
    ret = mosquitto_username_pw_set(mosq, username, password);
    if(ret) {
        printf("username or password is wrong.\n");
        mosquitto_destroy(mosq);
        mosquitto_lib_cleanup();
        return -1;
    }

    mosquitto_connect_callback_set(mosq, my_connect_callback);
    mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
    mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);
    mosquitto_message_callback_set(mosq, my_message_callback);

    ret = mosquitto_connect(mosq, HOST, PORT, KEEPALIVE);
    if(ret) {
        printf("connect to broker is failed.\n");
        mosquitto_destroy(mosq);
        mosquitto_lib_cleanup();
        return -1;       
    }

    printf("Subscribe begin.\n");
    int loop = mosquitto_loop_start(mosq);
    if(loop) {
        printf("mosquitto loop error.\n");
        return -1;
    }

    const char cmd[10];
    while(1) {
        scanf("%s", cmd);
        if(0 == strcmp(cmd, "quit")) {
            running = 0;
        }
    }

    mosquitto_disconnect(mosq);    
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    printf("Subscribe end.\n");
    return 0;   
}





7. 實驗現象

7.1 發佈者

對虛擬機進行操作

發佈者顯示信息

7.2 訂閱者

接收發布者發送的虛擬機事件信息。

參考文獻

[1] https://blog.p2hp.com/archives/4100
[2] https://blog.csdn.net/qq_33406883/article/details/107466430
[3] https://www.cnblogs.com/sxkgeek/p/9140180.html

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