MQTT客户端开发
一、项目概述
基于Paho MQTT C客户端库实现的设备与MQTT服务器通信程序,主要用于设备属性上报和数据订阅。
二、头文件解析 (head.h)
cpp
#ifndef HEAD_H
#define HEAD_H
#include <MQTTAsync.h>
#include <MQTTClient.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
// MQTT服务器地址(OneNET平台)
#define NEW_ADDRESS "tcp://183.230.40.96:端口号"
// 设备信息
#define DEV_NAME "设备名字"
#define CLIENTID DEV_NAME
#define PRODUCT_ID "ip"
// 认证密码(包含版本、资源、过期时间、签名等信息)
#define PASSWD "认证密码"
// MQTT配置参数
#define QOS 0 // 服务质量等级
#define TIMEOUT 10000L // 超时时间(ms)
#endif // HEAD_H
三、主程序实现 (main.c)
cpp
#include <stdio.h>
#include "head.h"
// 全局变量定义
static char topic[2][200] = {0}; // 主题数组
static MQTTClient client; // MQTT客户端实例
static int id = 10000; // 消息ID计数器
volatile static MQTTClient_deliveryToken deliveredtoken; // 消息传递令牌
/**
* @brief 构建MQTT主题
* @param dev_name 设备名称
* @param pro_id 产品ID
*/
void pack_topic(char *dev_name, char *pro_id)
{
// 主题格式说明:
// topic[0]: 订阅主题 - 用于接收服务器响应
// topic[1]: 发布主题 - 用于发送设备数据
sprintf(topic[0], "$sys/%s/%s/thing/property/post/reply", pro_id, dev_name);
sprintf(topic[1], "$sys/%s/%s/thing/property/post", pro_id, dev_name);
}
/**
* @brief 消息发送确认回调函数
* @param context 上下文
* @param dt 传递令牌
*/
void delivered(void *context, MQTTClient_deliveryToken dt)
{
printf("消息发送成功,令牌值: %d\n", dt);
deliveredtoken = dt;
}
/**
* @brief 消息接收回调函数
* @param context 上下文
* @param topicName 主题名称
* @param topicLen 主题长度
* @param message 消息内容
* @return 处理结果
*/
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
int i;
char *payloadptr;
printf("\n=== 收到新消息 ===\n");
printf("主题: %s\n", topicName);
printf("内容: ");
// 逐字符打印消息内容
payloadptr = (char *)message->payload;
for (i = 0; i < message->payloadlen; i++)
{
putchar(*payloadptr++);
}
putchar('\n');
printf("==================\n");
// 释放资源
MQTTClient_freeMessage(&message);
MQTTClient_free(topicName);
return 1;
}
/**
* @brief 连接断开回调函数
* @param context 上下文
* @param cause 断开原因
*/
void connlost(void *context, char *cause)
{
printf("\n!!! 连接已断开 !!!\n");
printf("原因: %s\n", cause);
}
/**
* @brief MQTT客户端初始化
* @return 初始化结果
*/
int mqtt_init()
{
int rc;
// 1. 构建主题
pack_topic(DEV_NAME, PRODUCT_ID);
// 2. 创建MQTT客户端
rc = MQTTClient_create(&client, NEW_ADDRESS, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL);
if (MQTTCLIENT_SUCCESS != rc)
{
printf("MQTT客户端创建失败...\n");
exit(1);
}
// 3. 配置连接选项
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
conn_opts.keepAliveInterval = 20; // 心跳间隔(秒)
conn_opts.cleansession = 1; // 清除会话
conn_opts.username = PRODUCT_ID; // 用户名(产品ID)
conn_opts.password = PASSWD; // 密码
// 4. 设置回调函数
rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered);
if (MQTTCLIENT_SUCCESS != rc)
{
printf("回调函数设置失败,错误码: %d\n", rc);
exit(EXIT_FAILURE);
}
// 5. 连接到服务器
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
printf("连接服务器失败,错误码: %d\n", rc);
exit(EXIT_FAILURE);
}
// 6. 订阅主题
MQTTClient_subscribe(client, topic[0], QOS);
printf("订阅信息:\n");
printf(" 主题: %s\n", topic[0]);
printf(" 客户端ID: %s\n", CLIENTID);
printf(" QoS等级: %d\n\n", QOS);
return rc;
}
/**
* @brief 发送MQTT消息
* @param key 数据键名
* @param value 数据值
* @return 发送结果
*/
int mqtt_send(char *key, int value)
{
MQTTClient_deliveryToken deliveryToken;
MQTTClient_message test2_pubmsg = MQTTClient_message_initializer;
char message[1024] = {0};
// 配置消息参数
test2_pubmsg.qos = QOS;
test2_pubmsg.retained = 0;
test2_pubmsg.payload = message;
// 构建JSON格式消息体
// 格式: {"id":"消息ID","version":"1.0","params":{"键名":{"value":值}}}
sprintf(message, "{\"id\":\"%d\",\"version\":\"1.0\",\"params\":{\"%s\":{\"value\":%d}}}",
id++, key, value);
test2_pubmsg.payloadlen = strlen(message);
printf("发送消息: %s\n", message);
// 发布消息
int rc = MQTTClient_publishMessage(client, topic[1], &test2_pubmsg, &deliveryToken);
if (MQTTCLIENT_SUCCESS != rc)
{
printf("消息发布失败... 线程ID: %lu\n", pthread_self());
exit(1);
}
printf("等待消息发布完成... (超时: %d秒)\n", (int)(TIMEOUT / 1000));
MQTTClient_waitForCompletion(client, deliveryToken, TIMEOUT);
return rc;
}
/**
* @brief MQTT客户端清理
*/
void mqtt_deinit()
{
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
printf("MQTT客户端已断开连接并销毁\n");
}
/**
* @brief 主函数
* @return 程序退出码
*/
int main(void)
{
printf("=== MQTT客户端程序启动 ===\n");
// 1. 初始化MQTT客户端
mqtt_init();
printf("MQTT客户端初始化成功,开始发送数据...\n\n");
// 2. 主循环:定时发送数据
while (1)
{
// 生成随机温度值(1-100)
int value = rand() % 100 + 1;
// 发送温度数据
mqtt_send("tmp", value);
// 等待25秒
sleep(25);
}
// 3. 清理资源(实际上不会执行到这里)
mqtt_deinit();
return 0;
}
四、关键技术点
1. MQTT主题设计
-
发布主题 :
$sys/{产品ID}/{设备名}/thing/property/post -
订阅主题 :
$sys/{产品ID}/{设备名}/thing/property/post/reply -
采用OneNET平台的标准主题格式
2. 消息格式
{
"id": "消息ID",
"version": "1.0",
"params": {
"数据键名": {
"value": 数据值
}
}
}
3. 认证机制
-
使用OneNET平台的Token认证方式
-
密码包含版本、资源路径、过期时间、签名方法等信息
4. 回调机制
-
connlost: 处理连接断开事件 -
msgarrvd: 处理接收到的消息 -
delivered: 处理消息发送确认
5. 线程安全
-
使用
volatile关键字确保令牌变量的可见性 -
适合在多线程环境中使用
五、编译和运行
1. 编译命令
gcc -o mqtt_client main.c -lpaho-mqtt3c -lpthread
2. 运行程序
./mqtt_client
3. 依赖库
-
Paho MQTT C Client Library
-
pthread 线程库
六、注意事项
-
密码有效期 : 当前密码中的
et=1837255523表示过期时间(时间戳),需要定期更新 -
QoS级别: 当前设置为0(最多一次),可根据可靠性需求调整
-
资源管理: 确保及时释放MQTT消息资源
-
错误处理: 生产环境需要更完善的错误处理和重连机制
-
心跳间隔: 当前设置为20秒,根据网络状况可适当调整
七、扩展建议
-
配置文件: 将设备信息、服务器地址等配置外置
-
日志系统: 添加更完善的日志记录
-
重连机制: 实现自动重连和连接状态监控
-
多线程: 将消息发送和接收分离到不同线程
-
数据持久化: 在网络异常时缓存未发送的数据
这个MQTT客户端程序实现了基本的设备数据上报功能,可以作为物联网设备接入OneNET平台的基础框架。