【mqtt】编译使用 MQTT-C

MQTT-C 简介

MQTT-C 是用 C 语言编写的 MQTT v3.1.1 客户端, 适用于嵌入式系统和 PC。
MQTT-C 完全是 线程安全,但也可以在单线程系统上运行良好,使 MQTT-C 非常适合嵌入式系统和微控制器。
MQTT-C 很小 —— 只有两个源文件,代码总计少于 2000 行。

下载、编译 MQTT-C 源码包

下载源码包并解压

MQTT-C 的源码仓库 MQTT-C 位于GitHub上。

unzip MQTT-C-master.zip

编译 MQTT-C 源码包

这里使用 MQTT-C 源码包中提供的 makefile 进行编译。
MQTT-C 使用 cmocka 单元测试框架进行单元测试。 因此,要安装 cmocka 才能构建和运行单元测试。
如果不想安装 cmocka 在 makefile 中将下图第 12 行注释掉就可以。
在这里插入图片描述

在解压后的源码目录执行编译

make

如果没有安装 OpenSSL 的开发库,会出现如下编译报错:

gcc -Wextra -Wall -std=gnu99 -Iinclude -Wno-unused-parameter -Wno-unused-variable -Wno-duplicate-decl-specifier `pkg-config --cflags openssl` -D MQTT_USE_BIO examples/bio_publisher.c src/mqtt.c src/mqtt_pal.c -lpthread  `pkg-config --libs openssl` -o bin/bio_publisher
Package openssl was not found in the pkg-config search path.
Perhaps you should add the directory containing `openssl.pc'
to the PKG_CONFIG_PATH environment variable
No package 'openssl' found
Package openssl was not found in the pkg-config search path.
Perhaps you should add the directory containing `openssl.pc'
to the PKG_CONFIG_PATH environment variable
No package 'openssl' found
In file included from include/mqtt.h:43,
                 from examples/bio_publisher.c:11:
include/mqtt_pal.h:100:22: fatal error: openssl/bio.h: No such file or directory
  100 |             #include <openssl/bio.h>
      |                      ^~~~~~~~~~~~~~~
compilation terminated.
In file included from include/mqtt.h:43,
                 from src/mqtt.c:25:
include/mqtt_pal.h:100:22: fatal error: openssl/bio.h: No such file or directory
  100 |             #include <openssl/bio.h>
      |                      ^~~~~~~~~~~~~~~
compilation terminated.
In file included from include/mqtt.h:43,
                 from src/mqtt_pal.c:25:
include/mqtt_pal.h:100:22: fatal error: openssl/bio.h: No such file or directory
  100 |             #include <openssl/bio.h>
      |                      ^~~~~~~~~~~~~~~
compilation terminated.
make: *** [makefile:24: bin/bio_publisher] Error 1

安装 OpenSSL 的开发库 libssl-dev 即可

sudo apt install libssl-dev

编译出来的可执行程序在 bin 中:

test@test-virtual-machine:~/work/MQTT-C-master$ ls bin/
bio_publisher  openssl_publisher  reconnect_subscriber  simple_publisher  simple_subscriber

测试

使用 simple_subscriber 进行简单测试:
simple_subscriber 是一个简单的程序,每当按下 Enter 键时,它就会发布当前时间。
这里使用的 simple_subscriber 是对源文件 MQTT-C-master/examples/simple_publisher.c 进行调整后编译出来的,调整如下:

mqtt_connect(&client, client_id, NULL, NULL, 0, NULL, NULL, connect_flags, 400);
调整为
char *will_message = "bye-bye";
mqtt_connect(&client, client_id, "datetime", will_message, strlen(will_message), "MQTTx1", "admin", connect_flags, 400);

下图给出了 mqtt_connect 函数的原型,及参数定义,从这可以看出此次做了两方面调整:

  • 通过用户名和密码来进行相关的认证。
  • 设置了遗嘱消息。
    遗嘱消息是 MQTT 为那些可能出现意外断线的设备提供的将遗嘱优雅地发送给其他客户端的能力。
    设置了遗嘱消息的 MQTT 客户端异常下线时,MQTT 服务器会发布该客户端设置的遗嘱消息。
    mqtt_connect 函数通过参数 will_topic 设置遗嘱消息的 Topic 。
    在这里插入图片描述

调整过的 simple_publisher.c 如下所示:这里主要看 main 函数,其它内容省略
从下面代码中可以看出

  • 服务器的端口号默认为:1883
  • 发布当前时间的 Topic 默认为:datetime
/**
 * @file
 * A simple program to that publishes the current time whenever ENTER is pressed.
 */
......
int main(int argc, const char *argv[])
{
    const char* addr;
    const char* port;
    const char* topic;

    /* get address (argv[1] if present) */
    if (argc > 1) {
        addr = argv[1];
    } else {
        addr = "test.mosquitto.org";
    }

    /* get port number (argv[2] if present) */
    if (argc > 2) {
        port = argv[2];
    } else {
        port = "1883";
    }

    /* get the topic name to publish */
    if (argc > 3) {
        topic = argv[3];
    } else {
        topic = "datetime";
    }

    /* open the non-blocking TCP socket (connecting to the broker) */
    int sockfd = open_nb_socket(addr, port);

    if (sockfd == -1) {
        perror("Failed to open socket: ");
        exit_example(EXIT_FAILURE, sockfd, NULL);
    }

    /* setup a client */
    struct mqtt_client client;
    uint8_t sendbuf[2048]; /* sendbuf should be large enough to hold multiple whole mqtt messages */
    uint8_t recvbuf[1024]; /* recvbuf should be large enough any whole mqtt message expected to be received */
    mqtt_init(&client, sockfd, sendbuf, sizeof(sendbuf), recvbuf, sizeof(recvbuf), publish_callback);
    /* Create an anonymous session */
    const char* client_id = NULL;
    /* Ensure we have a clean session */
    uint8_t connect_flags = MQTT_CONNECT_CLEAN_SESSION;
    /* Send connection request to the broker. */
    char *will_message = "bye-bye";
    mqtt_connect(&client, client_id, "datetime", will_message, strlen(will_message), "MQTTx1", "admin", connect_flags, 400);

    /* check that we don't have any errors */
    if (client.error != MQTT_OK) {
        fprintf(stderr, "error: %s\n", mqtt_error_str(client.error));
        exit_example(EXIT_FAILURE, sockfd, NULL);
    }

    /* start a thread to refresh the client (handle egress and ingree client traffic) */
    pthread_t client_daemon;
    if(pthread_create(&client_daemon, NULL, client_refresher, &client)) {
        fprintf(stderr, "Failed to start client daemon.\n");
        exit_example(EXIT_FAILURE, sockfd, NULL);

    }

    /* start publishing the time */
    printf("%s is ready to begin publishing the time.\n", argv[0]);
    printf("Press ENTER to publish the current time.\n");
    printf("Press CTRL-D (or any other key) to exit.\n\n");
    while(fgetc(stdin) == '\n') {
        /* get the current time */
        time_t timer;
        time(&timer);
        struct tm* tm_info = localtime(&timer);
        char timebuf[26];
        strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tm_info);

        /* print a message */
        char application_message[256];
        snprintf(application_message, sizeof(application_message), "The time is %s", timebuf);
        printf("%s published : \"%s\"", argv[0], application_message);

        /* publish the time */
        mqtt_publish(&client, topic, application_message, strlen(application_message) + 1, MQTT_PUBLISH_QOS_0);

        /* check for errors */
        if (client.error != MQTT_OK) {
            fprintf(stderr, "error: %s\n", mqtt_error_str(client.error));
            exit_example(EXIT_FAILURE, sockfd, &client_daemon);
        }
    }

    /* disconnect */
    printf("\n%s disconnecting from %s\n", argv[0], addr);
    sleep(1);

    /* exit */
    exit_example(EXIT_SUCCESS, sockfd, &client_daemon);
}
......

执行下面命令:192.168.0.107 是 MQTT Broker (即 emqx )宿主机的 IP地址。
在这里插入图片描述

通过 wireshark 抓包,在 connect 阶段的报文如下所示:MQTT 为应用层协议,传输层使用 TCP
在这里插入图片描述
从上图可以看出,在 Publish 与 Broker 建立 TCP 连接后,Publish 向 Broker 发送了一个 Connect Command ,报文中 MQTT 协议部分携带的信息就是调用 mqtt_connect 函数时传入的参数。
在 Broker 收到 Connect Command 后, Broker 又向 Publish 回了一个 Connect Ack 。

mqtt_connect 函数中参数 keep_alive(即保活周期)设置为 400s:
在这里插入图片描述

打开 mqttx 做 Subscribe,其配置如下,点击连接:
在这里插入图片描述

可以看连接成功以后,Subscribe1 订阅的两个 Topic,其中 publish-test 是之前使用Subscribe1遗留的不用管。
在这里插入图片描述

可以看到 Subscribe 与 Broker 建立连接时的报文交互:
在这里插入图片描述

连接建立成功后,Subscribe1 接着向 Broker 接连发送了两个订阅报文 Subscribe Request (id=5186) [publish-test] 与 Subscribe Request (id=5187) [datetime] 。
在这里插入图片描述
在这里插入图片描述

Publish 发布当前时间:
在这里插入图片描述

报文如下:
在这里插入图片描述

Broker 在收到 Publish 发布的 Topic 为 datetime 的消息后,又将消息分发给了订阅 Topic 为 datetime 的 Subscribe1
在这里插入图片描述

异常终止掉进程 simple_publisher (即 Publish) :
在这里插入图片描述

可以看到断开 Publish 与 Broker TCP 连接的 4 次挥手报文,之后 Broker 就向 Subscribe1 发送了 Topic 为 datetime 的消息,消息的内容为 “bye-bye”,这是 Publish 设置的遗嘱消息。
在这里插入图片描述

下图展示了 Subscribe1 收到的消息,只用关注最后两条消息即可,其它消息为前次使用遗留。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值