使用MQTT实现简单命令控制

MQTT

Posted by bbkgl on October 26, 2019

塞上长城空自许

镜中衰鬓已先斑

前面的博文中已经讲了如何安装MQTT的环境,以及如何进行测试,并给出了一个C语言的小例子。今天再讲讲paho.mqtt.c中一些常用接口的使用以及实现一个命令控制及回显功能。

接口介绍

这里讲的不详细,如果有读者读到了感觉不太懂的话,需要先了解一下MQTT的的工作机制。

MQTTClient_create()

int MQTTClient_create(MQTTClient* handle, const char* serverURI, const char* clientId, int persistence_type, void* persistence_context);

对传入的handle进行初始化,绑定服务器地址,绑定当前客户端的id,最后面两个参数我也不知道干嘛的。。。

MQTTClient_connect()

int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options);

设置连接参数,比如:

MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
conn_opts.keepAliveInterval = 60;
conn_opts.cleansession = 1;
MQTTClient_connect(client, &conn_opts);

MQTTClient_subscribe()

int MQTTClient_subscribe(MQTTClient handle, const char* topic, int qos);

订阅话题,最后哪个数字我也不知道干嘛的。。。

MQTTClient_receive()

int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTClient_message** message, unsigned long timeout);

很简单,接收发布者在topic下发布的消息。

example:

char *cli_topic = nullptr;
int cli_topic_len;
MQTTClient_message *receive_msg = nullptr;

MQTTClient_receive(client, &cli_topic, &cli_topic_len, &receive_msg, 100000);

ptr = (char *)receive_msg->payload;
for (i = 0; i < receive_msg->payloadlen; i++)
    message[i] = *ptr++;
message[i] = '\0';

MQTTClient_publishMessage()

int MQTTClient_publishMessage(MQTTClient handle, const char* topicName, MQTTClient_message* message, MQTTClient_deliveryToken *deliveryToken);

发布消息。

MQTTClient_waitForCompletion()

int MQTTClient_waitForCompletion(MQTTClient handle, MQTTClient_deliveryToken mdt, unsigned long timeout);

等待消息发送完成。

释放资源三连

MQTTClient_unsubscribe(MQTTClient handle, const char* topic);
int MQTTClient_disconnect(MQTTClient handle, int timeout);
void MQTTClient_destroy(MQTTClient* handle);

这个看了前面就懂了,释放资源的。

实现命令控制

前面关于接口的介绍差不多已经讲了通信的过程,这里的难点其实就是如何将MQTT这种“单向”通信的方式写成“同步双向的”,MQTT原来的通信方式是:

  1. 服务器启动,等待转发消息
  2. 订阅端向服务器订阅某个话题,并传入自己的id,等待话题推送
  3. 发布端根据话题向服务器发布消息
  4. 服务端收到发布端发布的消息,根据话题将消息推送到订阅该话题的客户端
  5. 客户端收到消息,一次交互完成!

可以看到,MQTT的这种通信模式其实是“单向通信”,即同时只能服务端向客户端发送消息的,而如果要实现命令控制的话,我们得实现以下流程:

  • 被控制端首先订阅“command”话题
  • 控制端在“command”话题下发布控制命令消息
  • 被控制端收到命令消息,根据命令消息进行命令控制
  • 同时,控制端订阅了“return”话题
  • 被控制端执行完命令后,向了“return”话题发布执行结果的消息
  • 控制端收到执行结果,控制完成

EXAMPLE

下面给出一个例子:

控制端:

#include "base.h"

int main(int argc, char **argv) {
    char message[1000000];
    send_command(argv[1], TOPIC1, "client");    // 省略实现细节,参考paho.mqtt.c接口介绍
    recv_command(message, TOPIC2, "client");    // 省略实现细节,参考paho.mqtt.c接口介绍
    printf("%s\n", message);
    return 0;
}

被控制端:

#include <fcntl.h>
#include <stdio.h>
#include <cstring>
#include "base.h"

int main() {
    char message[100];
    char command[100];
    char info[1000000];
    recv_command(message, TOPIC1, "server");    // 省略实现细节,参考paho.mqtt.c接口介绍
    sprintf(command, "%s 1> a.txt", message);
    system(command);
    usleep(100000);
    int fd = open("a.txt", O_RDONLY);
    int len = -1;
    while (len) {
        char buff[1024] = {'\0'};
        len = read(fd, buff, sizeof(buff));
        strcat(info, buff);
    }
    printf("%s\n", info);
    send_command(info, TOPIC2, "server");    // 省略实现细节,参考paho.mqtt.c接口介绍
    close(fd);
    system("rm a.txt");
    return 0;
}

这里有个细节,被控制端收到命令后,阻塞一段时间后才继续,这是因为在往某个话题发布消息前,必须已经有客户端先订阅了该话题,不然消息会发不出去,客户端也接收不到。

效果图:

启动服务端,注意服务端所在路径:

H356eb7f7c5cc442f846d18ca1276c7ccE

客户端输入命令,得到返回结果:

H2fe39262324643d080688adf26062d97w

同样的,我们看ls命令:

H8f48b48f67094e6da06b3d33d8e6a45aa

可以看到,客户端返回了服务端所在目录下的文件和子目录。