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 中的计数逻辑也能解决问题。

相关推荐
caimouse2 小时前
reactos编码规范
c语言·开发语言
方便面不加香菜5 小时前
Linux--基础IO(一)
linux·运维·服务器
AI thought7 小时前
【转】C语言中 -> 是什么意思?
c语言·位移运算符·右移赋值·无符号整数·算术右移
普中科技8 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 45 章 FSMC-外扩 SRAM 实验
stm32·单片机·嵌入式硬件·fsmc·普中科技·外扩sram·is62wv51216
mounter6259 小时前
现代 Linux 内存管理的演进与变革:从传统 LRU 到多代架构 MGLRU
linux·服务器·kernel
赵渝强老师9 小时前
【赵渝强老师】Kubernetes(K8s)中的金丝雀升级
linux·docker·云原生·容器·kubernetes
Qt程序员10 小时前
Linux RCU 原理与应用
linux·c++·内核·linux内核·rcu
The Sheep 202310 小时前
Vue复习
linux·服务器·数据库
qeen8710 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
兄台の请冷静10 小时前
Linux 安装es
linux·elasticsearch·jenkins