前言
在网络通信中,心跳(Heartbeat)指的是一种周期性的消息,用于维持通信连接的活动状态。心跳包的主要作用是检测连接是否处于活动状态,及时发现连接异常并重新恢复连接,维护网络通信的稳定性和可靠性。
MQTT(Message Queuing Telemetry Transport)是一种轻量级、开放式的消息协议,用于在低带宽和不可靠的网络环境下传输消息。MQTT协议规定了PINGREQ和PINGRESP消息类型,用于实现心跳机制。PINGREQ消息是由客户端向服务器发送的心跳包,PINGRESP消息是服务器回复的心跳包确认消息。
本文通过对mosquitto源码(mosquitto-2.0.15)分析,介绍mosquitto如何实现基于MQTT协议的心跳和网络重连机制。
一、MQTT各个版本对心跳机制的定义
不同版本的MQTT协议对心跳机制的定义略有不同,以下是各个版本对心跳机制的简要介绍:
- MQTT v3.1.0
MQTT v3.1.0协议定义了keepalive选项,它是一个16位的值,表示客户端与服务器之间的最大空闲时间。如果在这段时间内没有任何通信活动,客户端将发送PINGREQ消息给服务器,以确认服务器是否仍然在线。如果服务器没有在规定时间内响应PINGREQ消息,客户端将关闭连接。keepalive的默认值为60秒。 - MQTT v3.1.1
MQTT v3.1.1协议在v3.1.0的基础上做了一些改进。其中,keepalive选项的最小值改为了1.5倍的发送间隔,最大值改为65535秒。这样可以避免由于keepalive时间过短而导致频繁发送PINGREQ消息,从而影响性能。此外,如果客户端在规定时间内没有收到服务器的响应,它可以重新发送PINGREQ消息,最多重试三次。 - MQTT v5.0
MQTT v5.0协议进一步改进了心跳机制。它定义了一个心跳超时属性,用于指定服务器应该在多长时间内发送PINGREQ消息。客户端也可以在PINGREQ消息中设置超时属性。如果服务器在规定时间内没有响应PINGREQ消息,它将被视为已断开连接。同时,MQTT v5.0还引入了会话恢复机制,它可以让客户端在断开连接后重新连接并恢复之前的会话状态。 - MQTT-SN
MQTT-SN(MQTT for Sensor Networks)是专门设计用于传感器网络的MQTT版本。它定义了一个心跳间隔选项,表示客户端和网关之间的最大空闲时间。如果客户端在规定时间内没有发送消息,网关将发送PINGREQ消息。如果网关在规定时间内没有收到任何消息,它将发送DISCONNECT消息,断开连接。心跳间隔选项的默认值为30秒。
总的来说,MQTT协议的不同版本都对心跳机制进行了规定,旨在确保客户端和服务器之间的连接状态,并避免不必要的资源浪费。MQTT协议的心跳机制对于保证网络稳定性和消息传递的可靠性非常重要。
Mosquitto是一款常用的MQTT代理服务器,它支持多个MQTT协议版本。以下是Mosquitto版本与MQTT协议版本大体的对应关系:
- Mosquitto 0.x版本支持MQTT 3.1协议。
- Mosquitto 1.5.x版本支持MQTT 3.1和MQTT 3.1.1协议。
- Mosquitto 1.6.x以上版本支持MQTT 3.1、MQTT 3.1.1和MQTT 5.0协议。
不同版本的Mosquitto对应不同版本的MQTT协议,对于相同版本的MQTT协议,Mosquitto的实现与协议规范是相符合的。在Mosquitto中,心跳超时的实现遵循对应的MQTT协议规范,因此在不同版本的Mosquitto中,心跳部分的实现与相应版本的MQTT协议规范是一致的。
特别需要说明的是从Mosquitto 1.5.x开始,Mosquitto增加了对MQTT-SN的支持,同时包含了MQTT-SN网关功能,可以将MQTT-SN消息转发到MQTT broker。虽然Mosquitto支持MQTT-SN,但是MQTT-SN和MQTT其他协议的使用方式略有不同,必须仔细阅读Mosquitto的官方文档,确保正确设置选项和命令行参数。
二、Mosquitto心跳和网络重连机制的实现
MQTT 协议中的心跳机制用于维持客户端和服务器之间的连接,确保连接不会因为长时间没有数据交互而被断开。Mosquitto MQTT 代理服务器中的心跳功能和网络重连由服务器和客户端共同完成:
1.心跳功能实现过程
1)客户端向服务器发送心跳
在客户端使用的struct mosquitto
结构体中,有一个 last_msg_in
字段和一个 last_msg_out
字段,分别表示客户端最近一次收到消息(注意:这里是所有消息,包括PINGREQ、PINGRESP、PUBLISH等类型的消息)和发送消息的时间戳。当客户端在一段时间内没有发送任何消息时,主动向服务器发送一次心跳消息( PINGREQ 消息类型)。
客户端发送心跳包之间的时间间隔由客户端keepalive参数决定,发送心跳的时间应该是last_msg_out + keepalive
(不是last_msg_in + keepalive
)。
在客户端使用 MQTT 协议连接到服务器时,通过设置 MQTT CONNECT 消息中的 keep alive 字段, keepaliv参数发送到服务器。
keepalive参数的设置过程如下:
在客户端,mosquitto源码定义了两个重要的数据结构struct mosq_config
和struct mosquitto
,分别用于存储mosquitto客户端的配置信息和运行时状态和信息。对于keepalive
参数,首先需要在struct mosq_config
的对象cfg中设置,然后在连接前拷贝到struct mosquitto
结构体对象。
对于cfg->keepalive参数,客户端可以用三种方式确定(三种方式后面会覆盖前面):
(1)第一次清空struct mosq_config对象cfg数值,并对部分参数赋初值,其中cfg->keepalive = 60;
(2)如果客户端配置文件配置了keepalive参数,在初始化函数中赋值cfg->keepalive;
(3)用命令行参数设置的参数设置keepalive参数,在初始化函数中赋值cfg->keepalive。
2)服务器接收和回应来自客户端的心跳
当 Mosquitto 服务器接收到客户端的心跳包后,发送 PINGRESP 消息到此客户作为响应。同时更新链表中保存的此客户端struct mosquitto
结构体中的 last_msg_in
成员变量,记录最后一次接收到此客户消息的时间戳。
3)客户端接收来自服务器的心跳响应
Mosquitto 客户端接收来自服务器的心跳响应。当客户端接收到来自服务器的心跳响应时,更新struct mosquitto
结构体中 last_msg_in
成员变量,设置为当前的时间戳 。
2.断线的判定和重连
1)客户端
客户端当前正在使用的struct mosquitto
结构体实例中保存着当前状态和相关信息,其中包括客户端 ID、连接参数、订阅信息、回调函数等。如果一定时间客户端没有收到任何消息,则可以认为连接已经断开,这个超时时间就是keepalive
参数,而struct mosquitto
结构体中的last_msg_in
则记录了客户端最后一次收到消息的时间。换句话说如果在last_msg_in + keepalive
内未能收到任何消息,可以认为连接已经断开。此时,客户端向服务器发送DISCONNECT`消息进行断开连接操作,并尝试重新连接服务器。
2)服务器
在Mosquitto服务器中,当一个客户端连接到服务器时,Mosquitto服务器将客户端加入到 客户端列表中。判断客户端连接超时就是通过客户端列表中的最后连接时间来实现的,当客户端和 Mosquitto 服务器之间建立连接时,会向服务器发送一个 CONNECT
消息,消息中有一个 keepa