写在前面
通过之前MQTT系列-Eclipse.Paho源码分析(二)-消息的发送与接收的介绍,相信仔细阅读过的小伙伴已经对Eclipse.Paho内部发送和订阅消息的流程有了一个较为清晰的认识,今天我们就把剩下的边角料扫一扫,也就是Eclipse.Paho作为客户端是如何进行容灾补偿和心跳的相关介绍。
心跳机制
首先了解一下在MQTT协议中心跳请求和响应是如何规定的。下面是官方文档中的描述:简单来说,就是在创建连接时发送的CONNECT控制报文中,在第九和第十字节会携带客户端与服务端的最大连接时长,如果超过这个时间,服务端和客户端会各自做一些相应的处理。但是这样会有一个问题,当客户端在超过最大连接时长的时间段内确实没有消息上送至服务器,此时服务器是无法判断因为客户端出现故障导致的还是确实没有收到消息导致的。所以MQTT协议中规定了PINGREQ和PINGRESP两种控制类型的报文用来处理上述情况,即如果客户端真的没有消息上送,你也要定时给我发送一个PINGREQ类型的报文告诉我你还活着,我服务器收到后会即使回送一个PINGRESP报文告诉客户端我收到了,这就是一条心跳消息。
下面我们来看看Eclipse.Paho的实现: 初始化心跳消息发送器 默认eclipse paho提供了两种MqttPingSender的实现:TimerPingSender:使用了Java的原生定时工具Timer
ScheduledExecutorPingSender:基于线程池的定时任务调
public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException {
// 构造方法中创建 TimerPingSender this(serverURI, clientId, persistence, new TimerPingSender()); }
我们来分析一下这个类,这个类实现了MqttPingSender接口,接口中提供了5个方法
public interface MqttPingSender {
/** * Initial method. Pass interal state of current client in. * @param comms The core of the client, which holds the state information for pending and in-flight messages. */ // 初始化心跳发送器 void init(ClientComms comms); /** * Start ping sender. It will be called after connection is success. */ // 心跳开始 void start(); /** * Stop ping sender. It is called if there is any errors or connection shutdowns. */ // 心跳终止 void stop(); /** * Schedule next ping in certain delay. * @param delayInMilliseconds delay in milliseconds. */ // 触发下一次心跳 void schedule(long delayInMilliseconds);}
init() 心跳初始化
public void init(ClientComms comms) {
if (comms == null) {
throw new IllegalArgumentException("ClientComms cannot be null."); } // 包装ClientComms对象 this.comms = comms; }
2.start()第一次心跳触发
客户端与服务端建立连接后,服务端会响应一个CONNACK类型的报文,所以在消息接收线程中,如果判断是这种类型的报文,会创建Timer并开始心跳
protected void notifyReceivedAck(MqttAck ack) throws MqttException {
if (token == null) {
... } else if (ack instanceof MqttPubRec) {
... } else if (ack instanceof MqttPubAck || ack instanceof MqttPubComp) {
... } else if (ack instanceof MqttPingResp) {
... } else if (ack instanceof MqttConnack) {
int rc = ((MqttConnack) ack).getReturnCode(); if (rc == 0) {
synchronized (queueLock) {
if (cleanSession) {
clearState(); // Add the connect token back in so that users can be // notified when connect completes. tokenStore.saveToken(token,ack); } inFlightPubRels = 0; actualInFlight = 0; restoreInflightMessages(); // 开启PingSender connected(); } } ... }
在start()方法中,会创建一个PingTask并将定时任务开启,定时任务的执行时间为初始化设置的keepAlive
在PingTask