【嵌入式Linux应用开发】5. 温湿度监控系统——学习paho mqtt的基本操作

1. 概述

​ 本篇的重点是解析paho mqtt官方例程,学习它的一些结构体、API和回调的处理,然后在下一篇学习下Linux中的线程和线程间数据通信,掌握相关API的使用,最后综合起来,使用多线程publish或处理subscribe的主题的消息,将订阅的温湿度消息解析出来在LVGL的表格中用折线图的方式显示出来。

温湿度监控系统应用开发所有文章

  1. 【嵌入式Linux应用开发】1. 移植LVGL到Linux开发板
  2. 【嵌入式Linux应用开发】2. 初步移植MQTT到Ubuntu和Linux开发板
  3. 【嵌入式Linux应用开发】3. SquareLine Studio与LVGL模拟器
  4. 【嵌入式Linux应用开发】4. 温湿度监控系统——绘制温湿度折线图
  5. 【嵌入式Linux应用开发】5. 温湿度监控系统——学习paho mqtt的基本操作
  6. 【嵌入式Linux应用开发】6. 温湿度监控系统——多线程与温湿度的获取显示
  7. 【嵌入式Linux应用开发】7. 设计温湿度采集MCU子系统
  8. 【嵌入式Linux应用开发】8. 阿里云物联网平台的简单使用

适用开发板

​ 适用于百问网的STM32MP157开发板和IMX6ULL开发板及其对应的屏幕,需要注意的是编译链要对应更改。

2. paho mqtt基本操作

​ 我们使用mqtt有如下几个操作:

  • 创建/销毁客户端
  • 连接/断开连接服务器
  • 订阅/取消订阅主题
  • 处理订阅消息
  • 发布主题消息

这些操作在paho mqtt的源码中,基于同步处理方式和异步处理方式又有不同的API,所以我们在使用paho mqtt的时候要在一开始就要定位好要选择哪种方式。

2.1 创建客户端

2.1.1 创建同步客户端

  • 客户端句柄:MQTTClient
// MQTTClient.h
typedef void* MQTTClient;

// .c文件中定义客户端句柄
MQTTClient client;
  • 创建客户端
int MQTTClient_create(MQTTClient* handle, 
                      const char* serverURI, 
                      const char* clientId,
                      int persistence_type, 
                      void* persistence_context)
{
    return MQTTClient_createWithOptions(handle, 
                                        serverURI, 
                                        clientId, 
                                        persistence_type, 
                                        persistence_context, 
                                        NULL);
}

可以看到这个函数调用了另一个函数MQTTClient_createWithOptions,它的原型定义:

int MQTTClient_createWithOptions(MQTTClient* handle, 
                                 const char* serverURI, 
                                 const char* clientId,
                                 int persistence_type, 
                                 void* persistence_context,
                                 MQTTClient_createOptions* options)

关于这个函数参数的释义:

参数名称 作用描述
handle MQTT客户端句柄
serverURI MQTT服务器地址
clientId 登录MQTT服务器时使用的客户端ID
persistence_type 客户端的持续类型:
MQTTCLIENT_PERSISTENCE_NONE: 当客户端运行失败或者下线了,正在发布的消息会立刻丢失,即便是QOS1或QOS2类型的消息质量;
MQTTCLIENT_PERSISTENCE_DEFAULT:客户端下线后正在发布的消息会被保存到指定的目录persistence_context中,此时persistence_context可以被指定为NULL;
MQTTCLIENT_PERSISTENCE_USER:客户端下线后正在发布的消息会被保存到指定的目录persistence_context中,此时persistence_context必须被指定不能为NULL
persistence_context 保存消息的目录
options 创建客户端的额外操作
返回值 如果成功返回MQTTCLIENT_SUCCESS否则返回错误码
#include <stdio.h>
#include "MQTTClient.h"

#define ADDRESS     "tcp://mqtt.eclipse.org:1883"
#define CLIENTID    "ExampleClientPub"

MQTTClient client;
int rc;

if ((rc = MQTTClient_create(&client, 
                            ADDRESS, 
                            CLIENTID,
                            MQTTCLIENT_PERSISTENCE_NONE, 
                            NULL)) != MQTTCLIENT_SUCCESS)
{
	printf("Failed to create client, return code %d\n", rc);
	exit(EXIT_FAILURE);
}

2.1.2 创建异步客户端

  • 客户端句柄:MQTTAsync
// MQTTAsync.h
typedef void* MQTTAsync;

// .C
MQTTAsync client;
  • 创建客户端
int MQTTAsync_create(MQTTAsync* handle, 
                     const char* serverURI, 
                     const char* clientId,
                     int persistence_type, 
                     void* persistence_context)
{
	MQTTAsync_init_rand();

	return MQTTAsync_createWithOptions(handle, 
                                       serverURI, 
                                       clientId, 
                                       persistence_type,
                                       persistence_context, 
                                       NULL);
}
int MQTTAsync_createWithOptions(MQTTAsync* handle, 
                                const char* serverURI, 
                                const char* clientId,
                                int persistence_type, 
                                void* persistence_context,
                                MQTTAsync_createOptions* options)

其实和同步的创建是差不多的,参数意义也几乎是一样的,就不在重复解释了,下面是应用:

#include <stdio.h>
#include "MQTTAsync.h"

#define ADDRESS     "tcp://mqtt.eclipse.org:1883"
#define CLIENTID    "ExampleClientPub"

MQTTAsync client;
int rc;

if ((rc = MQTTAsync_create(&client, 
                           ADDRESS, 
                           CLIENTID, 
                           MQTTCLIENT_PERSISTENCE_NONE, 
                           NULL)) != MQTTASYNC_SUCCESS)
{
	printf("Failed to create client object, return code %d\n", rc);
	exit(EXIT_FAILURE);
}

2.2 销毁客户端

  • 销毁同步客户端
void MQTTClient_destroy(MQTTClient* handle)

​ 传入的参数就是同步客户端的句柄:

MQTTClient client;
MQTTClient_destroy(&client);
  • 销毁异步客户端
void MQTTAsync_destroy(MQTTAsync* handle)

​ 传入的参数就是异步客户端的句柄:

MQTTAsync client;
MQTTAsync_destroy(&client);

2.3 回调函数设置

​ 我们可以使用这个设置回调函数的API将MQTT的一些消息处理放到多线程里面,在paho mqtt中有个设置回调函数的API,在这个API中指定下面几个操作的处理回调函数:

  • 和服务器的连接异常丢失了:这种情况通常会在发布过程、发送心跳包没有得到响应的情况下被发现和服务器的连接断开了;
  • 处理订阅的消息;
  • 成功发布消息后的处理;

函数原型:

  • 同步客户端
int MQTTClient_setCallbacks(MQTTClient handle, 
                            void* context, 
                            MQTTClient_connectionLost* cl,
                            MQTTClient_messageArrived* ma, 
                            MQTTClient_deliveryComplete* dc)
参数名称 描述
handle mqtt客户端
context 用户自定义的背景信息处理回调函数:客户端ID、用户名和密码这些信息
cl 连接丢失处理回调函数
ma 处理订阅消息的回调函数
dc 成功发布消息后的回到函数
返回值 MQTTCLIENT_SUCCESS或者错误码
  • 异步客户端
int MQTTAsync_setCallbacks(MQTTAsync handle, void* context,
									MQTTAsync_connectionLost* cl,
									MQTTAsync_messageArrived* ma,
									MQTTAsync_deliveryComplete* dc)

参数作用和同步客户端的是一样的。

2.4 和服务器建立连接

2.4.1 同步客户端建立连接

​ 和服务器建立连接需要定义一个连接控制包,其结构体的定义如下(删掉了注释):

typedef struct
{
	char struct_id[4];
	int struct_version;
	int keepAliveInterval;
	int cleansession;
	int reliable;
	MQTTClient_willOptions* will;
	const char* username;
	const char* password;
	int connectTimeout;
	int retryInterval;
	MQTTClient_SSLOptions* ssl;
	int serverURIcount;
	char* const* serverURIs;
	int MQTTVersion;
	struct
	{
		const char* serverURI;     /**< the serverURI connected to */
		int MQTTVersion;     /**< the MQTT version used to connect with */
        
        /**< if the MQTT version is 3.1.1, the value of sessionPresent returned in the connack */
		int sessionPresent;  
	} returned;
	struct
	{
		int len;           /**< binary password length */
		const void* data;  /**< binary password data */
	} binarypwd;

	int maxInflightMessages;
	int cleanstart;
	const MQTTClient_nameValue* httpHeaders;
	const char* httpProxy;
	const char* httpsProxy;
} MQTTClient_connectOptions;

下表是对参数的解释:

参数名称 解释
struct_id 必须是’M’‘Q’‘T’‘C’
struct_version 0-没有SSL和多服务器;
1-没有多服务器;
2-没有MQTT版本;
3-建立连接后没有返回值;
4-不支持二进制密码操作;
5-不支持maxInflightMessages和清除开始标志;
6-不支持HTTP headers;
7-不支持HTTP代理
keepAliveInterval 保活周期,客户端向服务器发送心跳包的周期,单位秒
cleansession 会话清楚标志,用来告诉服务器是否清除当前客户端上一次建立连接后产生的会话消息,1清除,0保持
reliable 是否支持发布多重消息;0支持同时最多10消息处于发布状态;1不支持多消息发布,必须等待上一次发布完成后才能发布下一条消息
will 遗嘱操作,设置遗嘱topic和消息
username 登录服务器用的用户名
password 登录服务器用的密码
connectTimeout 连接超时时间
retryInterval 发布消息但没有收到服务器响应的话,重发retryInterval这么多秒;0关闭重发;
ssl ssl操作,设置SSL属性
serverURIcount 服务器数量,默认是0
serverURIs 服务器地址
MQTTVersion MQTT版本:
0-默认版本3.1.1,连接失败的话回退使用3.1版本;
3-只会尝试用3.1版本和服务器建立连接;
4-只会尝试用3.1.1版本和服务器建立连接;
5-5.0版本
returned 建立连接成功后服务器的响应信息
binarypwd 登录服务器用的二进制密码,里面设置密码长度和密码内容
maxInflightMessages 如果支持多消息一起发布的话,这个值设置多消息的最大数量
cleanstart 5.0版本下的MQTT支持清除开始标志
httpHeaders http头部信息
httpProxy http代理
httpsProxy https的代理

paho mqtt使用宏定义了几个初始化的连接控制包:

1. 通用3.x版本的无遗嘱无SSL的连接控制包
#define MQTTClient_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 8, 60, 1, 1, NULL, NULL, NULL, 30, 0, 		NULL,0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL}
2. 通用5.0版本的无遗嘱无SSL的连接控制包
#define MQTTClient_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 8, 60, 0, 1, NULL, NULL, NULL, 30, 0, 		 NULL,0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL}
3. 3.x版本有遗嘱和SSL
#define MQTTClient_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 1, 1, NULL, NULL, NULL, 30, 		0, NULL,0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL}
4. 5.0版本有遗嘱和SSL
#define MQTTClient_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 0, 1, NULL, NULL, NULL, 30, 		 0, NULL,0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL}

我们在初始化连接的时候,需要先定义连接控制包结构体变量,然后将例如用户名、密码等信息复制后在调用连接API登录服务器。和服务器建立连接的API原型:

LIBMQTT_API int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options);

第一个参数就是客户端句柄,第二个参数是连接控制包的结构体变量指针,所以我们的用法是:

MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
int rc;
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
	printf("Failed to connect, return code %d\n", rc);
	rc = EXIT_FAILURE;
	goto destroy_exit;
}

2.4.2 异步客户端建立连接

​ 异步客户端的连接方法和同步客户端几乎是一样的,只是连接控制包结构体的名称和连接函数名不一样,并且异步客户端可以将连接成功/失败的处理放入回调函数中,用异步的方式处理:

typedef struct
{...}MQTTAsync_connectOptions;
#define MQTTAsync_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 8, 60, 1, 65535, NULL, NULL, NULL, 30, 		0,NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, 			NULL, NULL, NULL, NULL}

#define MQTTAsync_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 8, 60, 0, 65535, NULL, NULL, NULL, 30, 		 0,NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, 			NULL, NULL, NULL}

#define MQTTAsync_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 1, 65535, NULL, NULL, NULL, 			30, 0,NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, 			NULL, NULL, NULL, NULL, NULL}

#define MQTTAsync_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 0, 65535, NULL, NULL, NULL, 		30, 0,NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, 			NULL, NULL, NULL, NULL}

LIBMQTT_API int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options);

用法也类似:

MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer;
int rc;
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
conn_opts.onSuccess = onConnect;
conn_opts.onFailure = onConnectFailure;
conn_opts.context = client;
if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS)
{
	printf("Failed to start connect, return code %d\n", rc);
	exit(EXIT_FAILURE);
}

void onConnectFailure(void* context, MQTTAsync_failureData* response)
{
	printf("Connect failed, rc %d\n", response->code);
	finished = 1;
}

void onConnect(void* context, MQTTAsync_successData* response)
{
	MQTTAsync client = (MQTTAsync)context;
	MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
	int rc;

	printf("Successful connection\n");
}

2.5 和服务器断开连接

​ 在断开连接上,同步客户端和异步客户端区别有点大,传入的参数有差异。

2.5.1 同步客户端断开连接

​ 同步客户端断开服务器连接的API原型:

LIBMQTT_API int MQTTClient_disconnect(MQTTClient handle, int timeout);

只需要两个参数:MQTT客户端和断开连接超时时间:

MQTTClient client;
int rc;

if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)
{
	printf("Failed to disconnect, return code %d\n", rc);
	rc = EXIT_FAILURE;
}

2.5.2 异步客户端断开连接

​ 异步客户端断开和服务器的连接需要操作一个控制结构体:

typedef struct
{
	/** The eyecatcher for this structure. Must be MQTD. */
	char struct_id[4];
	/** The version number of this structure.  Must be 0 or 1.  0 signifies no V5 properties */
	int struct_version;
	/**
      * The client delays disconnection for up to this time (in
      * milliseconds) in order to allow in-flight message transfers to complete.
      */
	int timeout;
	/**
    * A pointer to a callback function to be called if the disconnect successfully
    * completes.  Can be set to NULL, in which case no indication of successful
    * completion will be received.
    */
	MQTTAsync_onSuccess* onSuccess;
	/**
    * A pointer to a callback function to be called if the disconnect fails.
    * Can be set to NULL, in which case no indication of unsuccessful
    * completion will be received.
    */
	MQTTAsync_onFailure* onFailure;
	/**
	* A pointer to any application-specific context. The
    * the <i>context</i> pointer is passed to success or failure callback functions to
    * provide access to the context information in the callback.
    */
	void* context;
	/**
	 * MQTT V5 input properties
	 */
	MQTTProperties properties;
	/**
	 * Reason code for MQTTV5 disconnect
	 */
	enum MQTTReasonCodes reasonCode;
	/**
    * A pointer to a callback function to be called if the disconnect successfully
    * completes.  Can be set to NULL, in which case no indication of successful
    * completion will be received.
    */
	MQTTAsync_onSuccess5* onSuccess5;
	/**
    * A pointer to a callback function to be called if the disconnect fails.
    * Can be set to NULL, in which case no indication of unsuccessful
    * completion will be received.
    */
	MQTTAsync_onFailure5* onFailure5;
} MQTTAsync_disconnectOptions;

#define MQTTAsync_disconnectOptions_initializer { {'M', 'Q', 'T', 'D'}, 0, 0, NULL, NULL, NULL,\
        MQTTProperties_initializer, MQTTREASONCODE_SUCCESS, NULL, NULL }

#define MQTTAsync_disconnectOptions_initializer5 { {'M', 'Q', 'T', 'D'}, 1, 0, NULL, NULL, NULL,\
        MQTTProperties_initializer, MQTTREASONCODE_SUCCESS, NULL, NULL }

结构体成员较少且英文也不难看懂,就不做翻译工作了,异步客户端断开服务器连接的API原型:

LIBMQTT_API int MQTTAsync_disconnect(MQTTAsync handle, const MQTTAsync_disconnectOptions* options);
LIBMQTT_API int MQTTClient_disconnect5(MQTTClient handle, int timeout, enum MQTTReasonCodes reason, 											   MQTTProperties* props);

来看一下具体的用法:

int disc_finished = 0;
int rc;
MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer;

disc_opts.onSuccess = onDisconnect;
disc_opts.onFailure = onDisconnectFailure;
if ((rc = MQTTAsync_disconnect(client, &disc_opts)) != MQTTASYNC_SUCCESS)
{
	printf("Failed to start disconnect, return code %d\n", rc);
	rc = EXIT_FAILURE;
	goto destroy_exit;
}

void onDisconnectFailure(void* context, MQTTAsync_failureData* response)
{
	printf("Disconnect failed, rc %d\n", response->code);
	disc_finished = 1;
}

void onDisconnect(void* context, MQTTAsync_successData* response)
{
	printf("Successful disconnection\n");
	disc_finished = 1;
}
while (!disc_finished){...}

2.6 获取连接状态

​ 如果客户端喝服务器处于连接状态啧返回true,否则返回false。

  • 同步客户端:LIBMQTT_API int MQTTClient_isConnected(MQTTClient handle);
  • 异步客户端:LIBMQTT_API int MQTTAsync_isConnected(MQTTAsync handle);

2.7 订阅主题

​ 在订阅主题上,同步客户端喝异步客户端的差别就是异步客户端可以将订阅动作的结果放到异步回调函数中处理,而同步客户端没有这个功能。

2.7.1 同步客户端订阅

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

只需要传入客户端句柄、订阅的主题以及消息质量即可:

#define TOPIC       "MQTT Examples"
#define QOS         1

int rc;

if ((rc = MQTTClient_subscribe(client, TOPIC, QOS)) != MQTTCLIENT_SUCCESS)
{
	printf("Failed to subscribe, return code %d\n", rc);
	rc = EXIT_FAILURE;
}

2.7.2 异步客户端订阅

LIBMQTT_API int MQTTAsync_subscribe(MQTTAsync handle, const char* topic, int qos, 
                                    MQTTAsync_responseOptions* response);

可以看到多了一个参数叫做响应操作,指向的就是对订阅操作动作的响应回调函数:

typedef struct MQTTAsync_responseOptions
{
	/** The eyecatcher for this structure.  Must be MQTR */
	char struct_id[4];
	/** The version number of this structure.  Must be 0 or 1
	 *   if 0, no MQTTV5 options */
	int struct_version;
	/**
    * A pointer to a callback function to be called if the API call successfully
    * completes.  Can be set to NULL, in which case no indication of successful
    * completion will be received.
    */
	MQTTAsync_onSuccess* onSuccess;
	/**
    * A pointer to a callback function to be called if the API call fails.
    * Can be set to NULL, in which case no indication of unsuccessful
    * completion will be received.
    */
	MQTTAsync_onFailure* onFailure;
	/**
    * A pointer to any application-specific context. The
    * the <i>context</i> pointer is passed to success or failure callback functions to
    * provide access to the context information in the callback.
    */
	void* context;
	/**
    * A token is returned from the call.  It can be used to track
    * the state of this request, both in the callbacks and in future calls
    * such as ::MQTTAsync_waitForCompletion.
    */
	MQTTAsync_token token;
	/**
    * A pointer to a callback function to be called if the API call successfully
    * completes.  Can be set to NULL, in which case no indication of successful
    * completion will be received.
    */
	MQTTAsync_onSuccess5* onSuccess5;
	/**
    * A pointer to a callback function to be called if the API call successfully
    * completes.  Can be set to NULL, in which case no indication of successful
    * completion will be received.
    */
	MQTTAsync_onFailure5* onFailure5;
	/**
	 * MQTT V5 input properties
	 */
	MQTTProperties properties;
	/*
	 * MQTT V5 subscribe options, when used with subscribe only.
	 */
	MQTTSubscribe_options subscribeOptions;
	/*
	 * MQTT V5 subscribe option count, when used with subscribeMany only.
	 * The number of entries in the subscribe_options_list array.
	 */
	int subscribeOptionsCount;
	/*
	 * MQTT V5 subscribe option array, when used with subscribeMany only.
	 */
	MQTTSubscribe_options* subscribeOptionsList;
} MQTTAsync_responseOptions;

#define MQTTAsync_responseOptions_initializer { {'M', 'Q', 'T', 'R'}, 1, NULL, NULL, 0, 0, NULL, NULL, 				MQTTProperties_initializer, MQTTSubscribe_options_initializer, 0, NULL}

用法:

#define TOPIC       "MQTT Examples"
#define QOS         1

MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
int rc;

opts.onSuccess = onSubscribe;
opts.onFailure = onSubscribeFailure;
opts.context = client;
if ((rc = MQTTAsync_subscribe(client, TOPIC, QOS, &opts)) != MQTTASYNC_SUCCESS)
{
	printf("Failed to start subscribe, return code %d\n", rc);
	finished = 1;
}

消息到来后的处理是在设置回调函数的时候就设置了处理订阅消息的回调函数的:

int rc;

if ((rc = MQTTAsync_setCallbacks(client, NULL, connlost, messageArrived, NULL)) != MQTTASYNC_SUCCESS)
{
	printf("Failed to set callback, return code %d\n", rc);
	exit(EXIT_FAILURE);
}

int messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* m)
{
	/* not expecting any messages */
	return 1;
}

我们就可以在这个回调函数中处理服务器下发过来的消息了。

2.8 发布消息

​ 在发布消息上,同步客户端既支持同步机制也支持异步机制,同步机制就是必须要等到上一次的消息发布完成才能发布下一次的消息,异步则是支持一次处理多条消息;而异步客户端则只有异步机制。某种程度上来说,异步发布是兼容同步发布的。

​ 发布消息有一个消息结构体,将消息的长度、消息内容、消息质量等抽象集成到了一起,这个结构体在同步客户端喝异步客户端也是不同的。

2.8.1 同步客户端的同步发布

​ 消息结构体:

typedef struct
{
	/** The eyecatcher for this structure.  must be MQTM. */
	char struct_id[4];
	/** The version number of this structure.  Must be 0 or 1
	 *  0 indicates no message properties */
	int struct_version;
	/** The length of the MQTT message payload in bytes. */
	int payloadlen;
	/** A pointer to the payload of the MQTT message. */
	void* payload;
	/**
     * The quality of service (QoS) assigned to the message.
     * There are three levels of QoS:
     * <DL>
     * <DT><B>QoS0</B></DT>
     * <DD>Fire and forget - the message may not be delivered</DD>
     * <DT><B>QoS1</B></DT>
     * <DD>At least once - the message will be delivered, but may be
     * delivered more than once in some circumstances.</DD>
     * <DT><B>QoS2</B></DT>
     * <DD>Once and one only - the message will be delivered exactly once.</DD>
     * </DL>
     */
	int qos;
	/**
     * The retained flag serves two purposes depending on whether the message
     * it is associated with is being published or received.
     *
     * <b>retained = true</b><br>
     * For messages being published, a true setting indicates that the MQTT
     * server should retain a copy of the message. The message will then be
     * transmitted to new subscribers to a topic that matches the message topic.
     * For subscribers registering a new subscription, the flag being true
     * indicates that the received message is not a new one, but one that has
     * been retained by the MQTT server.
     *
     * <b>retained = false</b> <br>
     * For publishers, this indicates that this message should not be retained
     * by the MQTT server. For subscribers, a false setting indicates this is
     * a normal message, received as a result of it being published to the
     * server.
     */
	int retained;
	/**
      * The dup flag indicates whether or not this message is a duplicate.
      * It is only meaningful when receiving QoS1 messages. When true, the
      * client application should take appropriate action to deal with the
      * duplicate message.
      */
	int dup;
	/** The message identifier is normally reserved for internal use by the
      * MQTT client and server.
      */
	int msgid;
	/**
	 * The MQTT V5 properties associated with the message.
	 */
	MQTTProperties properties;
} MQTTClient_message;

发布消息的API:

LIBMQTT_API int MQTTClient_publishMessage(MQTTClient handle, const char* topicName, MQTTClient_message* msg,
                                          MQTTClient_deliveryToken* dt);

最后个参数是发布消息的分发序号,在异步机制中起作用,同步机制中复制为NULL即可。来看一下具体的用法:

#define TOPIC       "MQTT Examples"
#define PAYLOAD     "Hello World!"

MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
int rc;

pubmsg.payload = PAYLOAD;
pubmsg.payloadlen = (int)strlen(PAYLOAD);
pubmsg.qos = QOS;
pubmsg.retained = 0;
if ((rc = MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token)) != MQTTCLIENT_SUCCESS)
{
	printf("Failed to publish message, return code %d\n", rc);
	exit(EXIT_FAILURE);
}

printf("Waiting for up to %d seconds for publication of %s\n"
       "on topic %s for client with ClientID: %s\n",
       (int)(TIMEOUT/1000), PAYLOAD, TOPIC, CLIENTID);
rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
printf("Message with delivery token %d delivered\n", token);

2.8.2 同步客户端的异步发布

​ 消息包喝分发序号以及发布API就不多说了,区别在于用异步发布的时候,在设置回调函数的时候需要将发布动作的回调函数指定:

MQTTClient_deliveryToken deliveredtoken;
int rc;

if ((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS)
{
	printf("Failed to set callbacks, return code %d\n", rc);
	rc = EXIT_FAILURE;
	goto destroy_exit;
}
    
void delivered(void *context, MQTTClient_deliveryToken dt)
{
    printf("Message with token value %d delivery confirmed\n", dt);
    deliveredtoken = dt;
}

发布消息还是一样需要对消息包结构体进行赋值然后调用API发布出去:

MQTTClient_message pubmsg = MQTTClient_message_initializer;
int rc;
pubmsg.payload = PAYLOAD;
pubmsg.payloadlen = (int)strlen(PAYLOAD);
pubmsg.qos = QOS;
pubmsg.retained = 0;
deliveredtoken = 0;
if ((rc = MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token)) != MQTTCLIENT_SUCCESS)
{
	printf("Failed to publish message, return code %d\n", rc);
	rc = EXIT_FAILURE;
}

但是异步机制下如果发布没有成功的话,是不会进入回调函数中的,而异步客户端则有对发布失败的回调处理。

2.8.3 异步客户端发布

​ 在异步客户端中,它既支持同步客户端的异步机制中的分发回调函数处理,也支持将发布消息成功和失败的回调处理器,它将这一操作放到了响应操作结构体中:

MQTTAsync_responseOptions

通过对相应操作机构体的赋值以及发送消息的API调用,来进行回调处理设置:

MQTTAsync client = (MQTTAsync)context;
MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
MQTTAsync_message pubmsg = MQTTAsync_message_initializer;
int rc;

printf("Successful connection\n");
opts.onSuccess = onSend;
opts.onFailure = onSendFailure;
opts.context = client;
pubmsg.payload = PAYLOAD;
pubmsg.payloadlen = (int)strlen(PAYLOAD);
pubmsg.qos = QOS;
pubmsg.retained = 0;
if ((rc = MQTTAsync_sendMessage(client, TOPIC, &pubmsg, &opts)) != MQTTASYNC_SUCCESS)
{
	printf("Failed to start sendMessage, return code %d\n", rc);
	exit(EXIT_FAILURE);
}

void onSendFailure(void* context, MQTTAsync_failureData* response)
{
	MQTTAsync client = (MQTTAsync)context;

	printf("Message send failed token %d error code %d\n", response->token, response->code);
}

void onSend(void* context, MQTTAsync_successData* response)
{
	MQTTAsync client = (MQTTAsync)context;

	printf("Message with token value %d delivery confirmed\n", response->token);
}

3. 示例代码

#include "mqtt_iot.h"
#include "src/MQTTClient.h"  //需要在系统中提前安装好MQTT,可以参考

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>

#define ADDRESS     "tcp://xxx:1883" //根据 MQTT 实际主机地址调整
#define CLIENTID    "xxx"
#define USERNAME    "xxx"
#define PASSWORD    "xxx"
#define QOS 0
#define TIMEOUT 	10000L
#define PUB_TOPIC 	"xxx" 
#define SUB_TOPIC 	"xxx" 

extern void set_temp_humi_data(unsigned short value);

volatile MQTTClient_deliveryToken deliveredtoken;

pthread_t threads[2];
sem_t discon_sem;
int pubmsg_d = -1;
int submsg_d = -1;
msgbuf subMsg = {1, 0};
msgbuf pubMsg = {2, 0};
MQTTClient client;  //定义一个MQTT客户端client
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;

//传递给MQTTClient_setCallbacks的回调函数,消息发送成功后,调用此回调函数
void delivered(void *context, MQTTClient_deliveryToken dt)
{
	printf("Message with token value %d delivery confirmed\n", dt);
	deliveredtoken = dt;
}

//传递给MQTTClient_setCallbacks的回调函数 消息到达后,调用此回调函数 
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
	printf("Message arrived\n");
	printf(" topic: %s\n", topicName);
	printf(" message: %.*s\n", message->payloadlen, (char*)message->payload);

	unsigned short value = 0;
	unsigned short len = message->payloadlen;
	char *buf = (char*)message->payload;
	for(unsigned short i=0; i<len; i++)
	{
		if(buf[i] == '\0')	break;
		if(buf[i]<='9' && buf[i]>='0')
			value = value*10 + buf[i] - '0';
	}
	set_temp_humi_data(value);

	MQTTClient_freeMessage(&message);
	MQTTClient_free(topicName);
	return 1; 
}

//传递给MQTTClient_setCallbacks的回调函数 连接异常断开后调用此回调函数 
void connlost(void *context, char *cause)
{
	printf("\nConnection lost\n");
	printf(" cause: %s\n", cause);
}

//实现MQTT的发布 
void *mqtt_publish(void *argv)
{
	MQTTClient_message pubmsg = MQTTClient_message_initializer;
	MQTTClient_deliveryToken token;
	char data[4];
	int rc;
	pubmsg.qos = QOS;
	pubmsg.retained = 0;
	while(1) 
    {
		int res = msgrcv(pubmsg_d, &pubMsg, sizeof(pubMsg.value), 0, IPC_NOWAIT);
		if(res < 0)	continue;

		{
			
			printf("Publish: %d\n", pubMsg.value);
			sprintf(data, "%d", pubMsg.value);
			pubmsg.payload = data;
			pubmsg.payloadlen = 4;
			if ((rc = MQTTClient_publishMessage(client, PUB_TOPIC,
							&pubmsg, &token)) != MQTTCLIENT_SUCCESS)
			{
				printf("Failed to publish message, return code %d\n", rc);
				break;
			}
			rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
			printf("Message with delivery token %d delivered\n", token);
		}
	}

    if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)  //断开和服务器的连接 
	{
		printf("Failed to disconnect, return code %d\n", rc);
	}
    pthread_exit(&threads[PubThread]);
	return NULL; 
}

int mqtt_disconnect(void)
{
    int rc = EXIT_SUCCESS;

	if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)  //断开和服务器的连接 
	{
		printf("Failed to disconnect, return code %d\n", rc);
		rc = EXIT_FAILURE;
	}
	else
	{
		printf("MQTT disconnect success\n");
		MQTTClient_destroy(&client);
	}

	return rc;
}


int mqtt_iot(void)
{
    int rc = EXIT_SUCCESS;
	if ((rc = MQTTClient_create(&client, ADDRESS, CLIENTID,
					MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
	{
		printf("Failed to create client, return code %d\n", rc);
		goto exit;
	}
	//设置回调函数,
	if ((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd,
					delivered)) != MQTTCLIENT_SUCCESS)
	{
		printf("Failed to set callbacks, return code %d\n", rc);
		goto destroy_exit;
	}
    conn_opts.username = USERNAME;
    conn_opts.password = PASSWORD;
	conn_opts.keepAliveInterval = 60;
	conn_opts.cleansession = 1;
	//连接服务器
	if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
	{
		printf("Failed to connect, return code %d\n", rc);
		goto destroy_exit;
	}

    //订阅主题 
	if ((rc = MQTTClient_subscribe(client, SUB_TOPIC, QOS)) != MQTTCLIENT_SUCCESS)
	{
		printf("Failed to subscribe, return code %d\n", rc);
        goto destroy_exit;
	}

	// 初始化信号量
	if(sem_init(&discon_sem, 1, 0) != 0)
	{
		printf("Failed to init semaphore\n");
		return -1;
	}
	// 创建队列
	pubmsg_d = msgget(0x1234, IPC_CREAT);
	submsg_d = msgget(0x5678, IPC_CREAT);
	if(pubmsg_d == -1 || submsg_d==-1)
	{
		printf("Failed to create a mqtt message, pubid:%d, subid:%d\n", pubmsg_d, submsg_d);
		return -1;
	}
	else
	{
		printf("Publish message id: %d\n", pubmsg_d);
		printf("Subscribe message id: %d\n", submsg_d);
	}

	printf("MQTT connect success, press 'Q' or 'q' to disconnect mqtt server\n");
    return 0;

destroy_exit:
	MQTTClient_destroy(&client); //释放客户端的资源 
    return -1; 
exit:
    return -1;
}