1. 环境准备:Ubuntu系统与基础工具

在开始搭建MQTT+JSON通信系统之前,我们需要准备好基础环境。我推荐使用Ubuntu 18.04或20.04 LTS版本,这两个版本在软件兼容性和稳定性方面表现都很不错。如果你是Windows用户,可以考虑使用WSL2来运行Ubuntu环境,实测下来和原生Ubuntu几乎没有区别。

首先更新系统软件包列表是个好习惯:

sudo apt update
sudo apt upgrade -y

接下来安装一些必要的开发工具链,这些工具在后续编译过程中会用到:

sudo apt install -y build-essential cmake git wget

build-essential 包含了gcc、g++、make等基础编译工具,cmake 用于一些项目的构建,git 用于获取开源代码,wget 用于下载文件。我建议把这些工具都装上,避免后续因为缺少某个工具而中断工作流程。

2. 安装Mosquitto MQTT Broker

2.1 源码编译安装Mosquitto

Mosquitto是Eclipse基金会维护的一个轻量级MQTT broker,性能稳定且资源占用低。我选择从源码安装而不是直接apt安装,这样可以获得最新版本和更多配置选项。

首先下载源码包:

wget https://mosquitto.org/files/source/mosquitto-2.0.15.tar.gz
tar -xzf mosquitto-2.0.15.tar.gz
cd mosquitto-2.0.15

在编译前需要安装一些依赖库:

sudo apt install -y libssl-dev libc-ares-dev libwebsockets-dev

这些依赖库提供了SSL加密、异步DNS解析和WebSocket支持等功能。如果后续需要其他功能,可能还需要安装更多依赖,但以上这些是基础功能所需的。

接下来进行编译安装:

make
sudo make install

这个过程可能需要几分钟时间,取决于你的机器性能。如果编译过程中报错,通常是因为缺少某些依赖库,根据错误提示安装对应的开发包即可。

2.2 解决常见安装问题

在实际操作中,我遇到过几个典型问题,这里分享下解决方案:

问题1:找不到Mosquitto.h头文件

fatal error: mosquitto.h: No such file or directory

这是因为系统找不到Mosquitto的开发头文件。解决方法是将头文件目录添加到系统路径:

sudo cp /usr/local/include/mosquitto.h /usr/include/
sudo cp /usr/local/include/mosquitto_plugin.h /usr/include/

问题2:运行时找不到共享库

error while loading shared libraries: libmosquitto.so.1

这是因为动态链接库路径没有正确配置。解决方法:

sudo ln -s /usr/local/lib/libmosquitto.so.1 /usr/lib/
sudo ldconfig

3. 集成cJSON库处理JSON数据

3.1 获取并编译cJSON

cJSON是一个轻量级的C语言JSON解析库,非常适合嵌入式系统和资源受限环境。我们可以直接从GitHub获取最新源码:

git clone https://github.com/DaveGamble/cJSON.git
cd cJSON
mkdir build
cd build
cmake ..
make
sudo make install

安装完成后,cJSON的头文件会被安装到/usr/local/include/cjson目录,库文件会安装到/usr/local/lib目录。

3.2 在项目中使用cJSON

在你的项目中,只需要包含cJSON.h头文件,并在编译时链接cJSON库即可:

#include <cjson/cJSON.h>

编译命令需要添加链接选项:

gcc your_program.c -o your_program -lmosquitto -lcjson

4. 编写MQTT客户端程序

4.1 发布者(Publisher)实现

下面是一个完整的MQTT发布者示例,它会发布JSON格式的消息:

#include <stdio.h>
#include <stdlib.h>
#include <mosquitto.h>
#include <cjson/cJSON.h>

#define MQTT_HOST "localhost"
#define MQTT_PORT 1883
#define MQTT_TOPIC "sensor/data"

void on_connect(struct mosquitto *mosq, void *obj, int rc) {
    if(rc == 0) {
        printf("Connected to broker\n");
    } else {
        fprintf(stderr, "Connect failed: %s\n", mosquitto_connack_string(rc));
    }
}

int main() {
    struct mosquitto *mosq = NULL;
    int rc;
    
    mosquitto_lib_init();
    
    mosq = mosquitto_new(NULL, true, NULL);
    if(!mosq) {
        fprintf(stderr, "Error: Out of memory.\n");
        return 1;
    }
    
    mosquitto_connect_callback_set(mosq, on_connect);
    
    rc = mosquitto_connect(mosq, MQTT_HOST, MQTT_PORT, 60);
    if(rc != MOSQ_ERR_SUCCESS) {
        fprintf(stderr, "Unable to connect: %s\n", mosquitto_strerror(rc));
        mosquitto_destroy(mosq);
        return 1;
    }
    
    // 创建JSON消息
    cJSON *root = cJSON_CreateObject();
    cJSON_AddStringToObject(root, "device", "sensor-001");
    cJSON_AddNumberToObject(root, "temperature", 23.5);
    cJSON_AddNumberToObject(root, "humidity", 45.2);
    
    char *json_str = cJSON_Print(root);
    printf("Publishing: %s\n", json_str);
    
    rc = mosquitto_publish(mosq, NULL, MQTT_TOPIC, strlen(json_str), json_str, 0, false);
    if(rc != MOSQ_ERR_SUCCESS) {
        fprintf(stderr, "Publish failed: %s\n", mosquitto_strerror(rc));
    }
    
    cJSON_Delete(root);
    free(json_str);
    
    mosquitto_disconnect(mosq);
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    
    return 0;
}

4.2 订阅者(Subscriber)实现

对应的订阅者代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <mosquitto.h>
#include <cjson/cJSON.h>

#define MQTT_HOST "localhost"
#define MQTT_PORT 1883
#define MQTT_TOPIC "sensor/data"

void on_connect(struct mosquitto *mosq, void *obj, int rc) {
    if(rc == 0) {
        printf("Connected to broker\n");
        mosquitto_subscribe(mosq, NULL, MQTT_TOPIC, 0);
    } else {
        fprintf(stderr, "Connect failed: %s\n", mosquitto_connack_string(rc));
    }
}

void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) {
    printf("Received message on topic %s:\n", msg->topic);
    
    // 解析JSON消息
    cJSON *root = cJSON_Parse(msg->payload);
    if(root == NULL) {
        fprintf(stderr, "Failed to parse JSON\n");
        return;
    }
    
    char *json_str = cJSON_Print(root);
    printf("%s\n", json_str);
    free(json_str);
    
    // 提取具体字段
    cJSON *device = cJSON_GetObjectItemCaseSensitive(root, "device");
    cJSON *temp = cJSON_GetObjectItemCaseSensitive(root, "temperature");
    cJSON *humi = cJSON_GetObjectItemCaseSensitive(root, "humidity");
    
    if(cJSON_IsString(device) && cJSON_IsNumber(temp) && cJSON_IsNumber(humi)) {
        printf("Device: %s, Temp: %.1f, Humi: %.1f\n", 
               device->valuestring, temp->valuedouble, humi->valuedouble);
    }
    
    cJSON_Delete(root);
}

int main() {
    struct mosquitto *mosq = NULL;
    int rc;
    
    mosquitto_lib_init();
    
    mosq = mosquitto_new(NULL, true, NULL);
    if(!mosq) {
        fprintf(stderr, "Error: Out of memory.\n");
        return 1;
    }
    
    mosquitto_connect_callback_set(mosq, on_connect);
    mosquitto_message_callback_set(mosq, on_message);
    
    rc = mosquitto_connect(mosq, MQTT_HOST, MQTT_PORT, 60);
    if(rc != MOSQ_ERR_SUCCESS) {
        fprintf(stderr, "Unable to connect: %s\n", mosquitto_strerror(rc));
        mosquitto_destroy(mosq);
        return 1;
    }
    
    mosquitto_loop_forever(mosq, -1, 1);
    
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    
    return 0;
}

5. 实战排错与优化

5.1 常见连接问题

问题1:无法连接到MQTT服务器

Unable to connect: Connection refused

可能原因和解决方案:

  1. Mosquitto服务没有运行:执行 mosquitto -v 启动服务
  2. 防火墙阻止了1883端口:检查防火墙设置 sudo ufw status
  3. 服务器配置禁止了匿名连接:编辑/etc/mosquitto/mosquitto.conf,添加 allow_anonymous true

问题2:订阅者收不到消息

可能原因:

  1. 主题名称不匹配:检查发布和订阅的主题是否完全一致
  2. QoS级别不一致:确保发布和订阅使用相同的QoS级别
  3. 客户端ID冲突:为每个客户端设置唯一的ID

5.2 性能优化建议

  1. 使用持久会话:设置clean_session为false,可以避免断开连接后丢失消息
  2. 合理设置QoS
    • QoS 0:最多一次,性能最好但不保证送达
    • QoS 1:至少一次,保证送达但可能有重复
    • QoS 2:恰好一次,最可靠但性能开销最大
  3. 批量发送消息:对于高频数据,可以考虑批量发送多条数据在一个JSON数组中
  4. 启用压缩:对于大尺寸JSON数据,可以在应用层实现压缩

5.3 安全性考虑

  1. 启用TLS加密:在生产环境中务必使用MQTTS(8883端口)
  2. 使用认证机制:配置用户名密码或客户端证书
  3. ACL访问控制:限制客户端对特定主题的访问权限
  4. 定期更新:保持Mosquitto和cJSON库为最新版本

6. 实际应用案例

6.1 物联网传感器数据采集

在实际项目中,我用这套方案实现了分布式温湿度监测系统。多个传感器节点通过MQTT发布JSON格式的数据,格式如下:

{
  "node_id": "sensor-001",
  "timestamp": 1634567890,
  "readings": {
    "temperature": 23.5,
    "humidity": 45.2,
    "battery": 3.7
  }
}

中心服务器订阅这些消息并存入数据库,Web界面实时展示数据。这种架构的优点是:

  • 低延迟:传感器数据能在秒级内到达服务器
  • 松耦合:传感器节点和服务器可以独立升级
  • 易扩展:新增传感器只需配置MQTT连接

6.2 跨平台消息传递

另一个案例是实现了Windows、Linux和嵌入式设备之间的通信。Windows端用C#开发,Linux端用Python,嵌入式设备用C语言,都通过MQTT+JSON交互。JSON作为中间格式,完美解决了不同平台数据表示的差异问题。

7. 进阶话题

7.1 Mosquitto集群配置

对于高可用性要求高的场景,可以配置Mosquitto集群。基本步骤:

  1. 修改配置文件启用桥接:
connection bridge-to-node2
address node2.example.com:1883
topic # both 2 ""
  1. 设置相同的持久化存储
  2. 配置负载均衡器

7.2 使用WebSocket

现代Web应用通常需要通过浏览器直接连接MQTT,这需要启用WebSocket支持:

listener 9001
protocol websockets

然后前端可以使用MQTT.js等库连接ws://your-server:9001

7.3 替代方案比较

除了Mosquitto,还有其他MQTT broker可供选择:

  1. EMQX:更适合企业级应用,支持集群和规则引擎
  2. HiveMQ:商业产品,提供专业支持
  3. VerneMQ:高并发场景表现优异

对于JSON处理,也可以考虑:

  1. RapidJSON:性能更好但API较复杂
  2. Jansson:类似cJSON但功能更丰富

在实际项目中,我通常会根据具体需求选择合适的组合。对于资源受限的嵌入式设备,Mosquitto+cJSON是最轻量级的方案;对于企业级应用,EMQX+RapidJSON可能更合适。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐