lwIP MQTT 心跳 Bug 分析与修复

一、背景

在使用 lwIP 内置 MQTT 客户端时,如果你用的是 2.2.0 之前的版本 ,很可能会遇到一个恼人的问题:客户端和服务器正常连接,但一段时间后 会话被 broker 踢掉

比如常见的现象:

  • Mosquitto / EMQX 日志显示客户端超时断开。

  • lwIP 端没有主动调用 mqtt_disconnect(),却突然进入了 MQTT_DISCONNECTED 状态。

  • 配置的 keep-alive 时间是 60s,但实际上 90s 左右就会掉线。

经过排查,这其实是 心跳(keep-alive)定时逻辑的 bug。下面来分析一下原因,并给出解决方法

二、问题现象

在 lwIP 2.1.x 的 mqtt.c 里,心跳定时逻辑在 mqtt_cyclic_timer() 中实现

cpp 复制代码
if (client->keep_alive > 0) {
    client->server_watchdog++;
    
    if ((client->server_watchdog * MQTT_CYCLIC_TIMER_INTERVAL) > (client->keep_alive +             client->keep_alive / 2)) {
        mqtt_close(client, MQTT_CONNECT_TIMEOUT);
        restart_timer = 0;
    }

    /* keep-alive 超时检测 */
    if ((client->cyclic_tick * MQTT_CYCLIC_TIMER_INTERVAL) >= client->keep_alive) {
        // 发送心跳包 PINGREQ
        mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0, 0);
        client->cyclic_tick = 0;
    } else {
        client->cyclic_tick++;
    }
}

看似合理,但这里有个细节:

  • 只有在 else****分支中才会执行 cyclic_tick++

  • 如果进入 if (...) 发送了心跳包,就会直接 cyclic_tick = 0,漏掉了一次累加。

结果就是:

  • 心跳计数器实际触发频率比预期低。

  • PINGREQ 的发送比配置的 keep-alive 更晚。

  • Broker 端在 1.5 倍 keep-alive 没收到心跳时,就会断开连接。

三、解决方法

只需要在进入分支判断之前,提前增加一次 cyclic_tick

cpp 复制代码
client->cyclic_tick++; // 修复点:每个周期都先自增
if ((client->cyclic_tick * MQTT_CYCLIC_TIMER_INTERVAL) >= client->keep_alive) {
    mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0, 0);
    client->cyclic_tick = 0;
} else {
    client->cyclic_tick++;
}

这样就保证了:

  • 每次定时器调用,cyclic_tick 都会+1。

  • 不会出现"少算一次"的情况。

  • 心跳严格按照配置的 keep-alive 周期发送。

四、结论

  • lwIP 2.1.x 版本的 MQTT 实现存在心跳 bug,导致 PINGREQ 延迟发送,broker 判定超时。

  • 原因在于 cyclic_tick++ 的位置不对,导致计数器漏算。

  • 解决办法:在 lwIP 2.2.0 中,官方已经调整了 mqtt_cyclic_timer() 的逻辑,把 cyclic_tick 的自增位置放到固定地方,避免了这个 bug。因此,如果你的项目允许,推荐直接升级 lwIP 到 ≥ 2.2.0 。如果受限于平台或历史代码,直接修改 mqtt.c 中的计数逻辑也能解决问题。

相关推荐
其实防守也摸鱼18 小时前
GDB安装与配置(保姆级教程)【Linux、Windows系统】
linux·运维·windows·命令模式·工具·虚拟机·调试
励志的小陈1 天前
贪吃蛇(C语言实现,API)
c语言·开发语言
FreakStudio1 天前
做了个Claude Code CLI 电子宠物:程序员的实体监工代码搭子
python·单片机·嵌入式·面向对象·并行计算·电子diy·电子计算机
AC赳赳老秦1 天前
OpenClaw二次开发实战:编写专属办公自动化技能,适配个性化需求
linux·javascript·人工智能·python·django·测试用例·openclaw
mounter6251 天前
【内核新动向】告别物理槽位束缚:深度解析 Linux Virtual Swap Space 机制
linux·内存管理·kernel·swap·virtual swap
handler011 天前
从零实现自动化构建:Linux Makefile 完全指南
linux·c++·笔记·学习·自动化
芯岭技术1 天前
PY32L020系列32位MCU,超低功耗、高性价比,支持三种低功耗模式
单片机·嵌入式硬件
2023自学中1 天前
i.MX6ULL 板子的完整启动流程图(从上电 → 用户空间)
linux·嵌入式
闫利朋1 天前
Ubuntu 24.04 桌面安装向日葵完整指南
linux·运维·ubuntu
爱编码的小八嘎1 天前
C语言完美演绎8-15
c语言