Netty的心跳机制
一、引入
在 TCP 保持长连接的过程中,可能会出现断网等网络异常出现,异常发生的时候, client 与 server 之间如果没有交互的话,它们是无法发现对方已经掉线。
二、工作原理
在 client 与 server 之间在一定时间内没有数据交互时, 即处于 idle 状态时, 客户端或服务器就会发送一个特殊的数据包给对方, 当接收方收到这个数据报文后, 也立即发送一个特殊的数据报文, 回应发送方, 此即一个 PING-PONG 交互。所以, 当某一端收到心跳消息后, 就知道了对方仍然在线, 这就确保 TCP 连接的有效性。
TCP 实际上自带的就有长连接选项,本身是也有心跳包机制,也就是 TCP 的选项:SO_KEEPALIVE。但是,TCP 协议层面的长连接灵活性不够。所以,一般情况下我们都是在应用层协议上实现自定义心跳机制的,也就是在 Netty 层面通过编码实现。通过 Netty 实现心跳机制的话,核心类是 IdleStateHandler 。
三、实现
在 Netty
中, 实现心跳机制的关键是 IdleStateHandler
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);
}
参数的含义:
•readerIdleTimeSeconds: 读超时. 即当在指定的时间间隔内没有从 Channel
读取到数据时, 会触发一个 READER_IDLE
的 IdleStateEvent
事件.•writerIdleTimeSeconds: 写超时. 即当在指定的时间间隔内没有数据写入到 Channel
时, 会触发一个 WRITER_IDLE
的 IdleStateEvent
事件.•allIdleTimeSeconds: 读/写超时. 即当在指定的时间间隔内没有读或写操作时, 会触发一个 ALL_IDLE
的 IdleStateEvent
事件.
注意:这三个参数默认的时间单位是秒。
心跳处理类:ClientIdleStateTrigger
/**
端发送一个心跳包。
* <p>
* 用于捕获{@link IdleState#WRITER_IDLE}事件(未在指定时间内向服务器发送数据),然后向Server
* p>
*/
public class ClientIdleStateTrigger extends ChannelInboundHandlerAdapter {
public static final String HEART_BEAT = "heart beat!";
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
// write heartbeat to server
ctx.writeAndFlush(HEART_BEAT);
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
四、源码剖析
第254行代码其实表示该方法只是进行了透传,不做任何业务逻辑处理,让channelPipe中的下一个handler
处理channelRead
方法,但是记录了一下这里的调用时间
channelActive方法
initialize方法
这边会触发一个Task,ReaderIdleTimeoutTask,这个task是部分源码
341行是这样的,用当前时间减去最后一次channelRead
方法调用的时间,假如这个结果是6s,说明最后一次调用channelRead
已经是6s之前的事情了,你设置的是5s,那么nextDelay则为-1,说明超时了,那么354行则会触发userEventTriggered
方法,如果没有超时则不触发userEventTriggered方法。
五、总结
IdleStateHandler
这个类会根据你设置的超时参数的类型和值,循环去检测channelRead
和write
方法多久没有被调用了,如果这个时间超过了你设置的值,那么就会触发对应的事件,read触发read,write触发write,all触发all
•如果超时了,则会调用userEventTriggered
方法,且会告诉你超时的类型•如果没有超时,则会循环定时检测,除非你将IdleStateHandler
移除Pipeline