### Netty心跳检测机制
#### 概念
所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性.
在 Netty 中, 实现心跳机制的关键是 IdleStateHandler, 看下它的构造器:
这里解释下三个参数的含义:
- readerIdleTimeSeconds: 读超时. 即当在指定的时间间隔内没有从 Channel 读取到数据时, 会触发一个 READER_IDLE 的 IdleStateEvent 事件.
- writerIdleTimeSeconds: 写超时. 即当在指定的时间间隔内没有数据写入到 Channel 时, 会触发一个 WRITER_IDLE 的 IdleStateEvent 事件.
- allIdleTimeSeconds: 读/写超时. 即当在指定的时间间隔内没有读或写操作时, 会触发一个 ALL_IDLE 的 IdleStateEvent 事件.
#### 源码分析
注:这三个参数默认的时间单位是秒。若需要指定其他时间单位,可以使用另一个构造方法:
```
IdleStateHandler(booleanobserveOutput,longreaderIdleTime,longwriterIdleTime,longallIdleTime,TimeUnitunit)
```
要实现Netty服务端心跳检测机制需要在服务器端的ChannelInitializer中加入如下的代码:
```
pipeline.addLast(new IdleStateHandler(3, 0, 0, TimeUnit.SECONDS));
```
1、先看下IdleStateHandler中的channelRead方法:
```
//通道读取接收数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
reading = true;
firstReaderIdleEvent = firstAllIdleEvent = true;
}
ctx.fireChannelRead(msg); //只是进行透传,不做任何业务逻辑处理,让chanelpiple中的下一个handler处理channelRead方法
}
```
2、在看下IdleStateHandler中的channelActive方法:
//通道激活通道
```
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// This method will be invoked only if this handler was added
// before channelActive() event is fired. If a user adds this handler
// after the channelActive() event, initialize() will be called by beforeAdd().
initialize(ctx); //initialize的方法,这是IdleStateHandler的精髓
super.channelActive(ctx);
}
```
3、初始化通道连接 initialize 方法:
```
private void initialize(ChannelHandlerContext ctx) {
// Avoid the case where destroy() is called before scheduling timeouts.
// See: https://github.com/netty/netty/issues/143
switch (state) {
case 1:
case 2:
return;
}
state = 1;
initOutputChanged(ctx);
lastReadTime = lastWriteTime = ticksInNanos();
if (readerIdleTimeNanos > 0) { //读超时大于0
//这边会触发一个Task,ReaderIdleTimeoutTask
readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
readerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (writerIdleTimeNanos > 0) {
writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
writerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (allIdleTimeNanos > 0) {
allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
allIdleTimeNanos, TimeUnit.NANOSECONDS);
}
}
```
4、触发一个task,ReaderIdleTimeoutTask -> run方法如下:
```
@Override
protected void run(ChannelHandlerContext ctx) {
long nextDelay = readerIdleTimeNanos; // IdleStateHandler 中设置的读超时时间
if (!reading) {
nextDelay -= ticksInNanos() - lastReadTime; //表示当前时间减去最后一次调用channelRead方法时间,读超时时间再减去这个时间
}
if (nextDelay <= 0) { //nextDelay<=0,说明上一次条用channelRead方法时间超过了readerIdleTimeNanos(读超时时间),称为读空闲
// Reader is idle - set a new timeout and notify the callback.
readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
boolean first = firstReaderIdleEvent;
firstReaderIdleEvent = false;
try {
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
channelIdle(ctx, event); //此方法会触发下一个handler的 userEventTriggered方法:
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Read occurred before the timeout - set a new timeout with shorter delay.
readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
```
5、下一个handler的 userEventTriggered方法: 用此方法来处理超时后发生的动作,比如断开连接
```
/**
* Is called when an {@link IdleStateEvent} should be fired. This implementation calls
* {@link ChannelHandlerContext#fireUserEventTriggered(Object)}.
*/
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
```