在节点通信时,经常需要心跳机制来探测对方是否是存活的。在netty中,IdleStateHandler就能提供这种心跳检测功能。让我们先看看例子:
服务端添加的handler
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//添加IdleStateHandler,5s检测一次读事件
p.addLast(new IdleStateHandler(5, 0, 0));
p.addLast(new TimeServerHandler());
p.addLast(new HeartBeatServerHandler());
}
public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter {
int lossConnectCount = 0;
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent){
IdleStateEvent event = (IdleStateEvent)evt;
if (event.state()== IdleState.READER_IDLE){
lossConnectCount++;
if (lossConnectCount>2){
System.out.println("关闭这个不活跃通道!");
ctx.channel().close();
}
}
}else {
super.userEventTriggered(ctx,evt);
}
}
}
服务端我们添加一个IdleStateHandler,若5s内无读事件则触发心跳处理方法HeartBeatServerHandler#userEventTriggered,若连续2次无读事件,则关闭这个客户端channel。
客户端:
.handler(new ChannelInitializer(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
ch.pipeline().addLast(new TimeClientHandler());
}
});
在看IdleStateHandler实现前,我们先想一下如果自己实现的话会怎么实现呢?
首先,IdleStateHandler在每个客户端接入时,会生成一个对象。同时要初始化做一些事,比如对读或写事件进行定时判断,看在指定的时间内有没有感兴趣的读/写事件,没有则触发事件,做一些自定义的事情。同时上次读/写事件完成后需更新时间,以便定时任务能及时感知。定时判断可以利用EventLoop父类自带的schedule调度方法, 更新上次读/写事件完成后的时间需要监听读完成、写完成事件,需要实现入站、出站接口。
让我们看看netty中的实现:
public class IdleStateHandler extends ChannelDuplexHandler
继承了ChannelDuplexHandler类,此类实现了入站、出站接口。继续看构造方法:
public IdleStateHandler(boolean observeOutput,
long readerIdleTime, long writerIdleTime, long allIdleTime,
TimeUnit unit) {
if (unit == null) {
throw new NullPointerException("unit");
}
this.observeOutput = observeOutput;
if (readerIdleTime <= 0) {
readerIdleTimeNanos = 0;
} else {
readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
}
if (writerIdleTime <= 0) {
writerIdleTimeNanos = 0;
} else {
writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
}
if (allIdleTime <= 0) {
allIdleTimeNanos = 0;
} else {
allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
}
}
初始化了读、写、读或写的超时参数。再看看初始化调度方法:
private void initialize(ChannelHandlerContext ctx) {
// Avoid the case where destroy() is called before scheduling timeouts.
// See: https://github.com/netty/netty/issues/143
//state: 0 - none, 1 - initialized, 2 - destroyed
switch (state) {
case 1:
case 2:
return;
}
state = 1;
initOutputChanged(ctx);
lastReadTime = lastWriteTime = ticksInNanos();
if (readerIdleTimeNanos > 0) {
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);
}
}
initialize方法在addLast -> handlerAdded中会调用。比如我们设置了检测读事件,那readerIdleTimeNanos>0,会执行schedule方法。这里会传一个ReaderIdleTimeoutTask对象过去,让我们先看看ReaderIdleTimeoutTask做了什么:
private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
}
@Override
protected void run(ChannelHandlerContext ctx) {
//读超时时间
long nextDelay = readerIdleTimeNanos;
//如果还在读时间进行中则不进行判断
if (!reading) {
//判断当前时间-上次读时间是否大于超时时间
nextDelay -= ticksInNanos() - lastReadTime;
}
if (nextDelay <= 0) {
// Reader is idle - set a new timeout and notify the callback.
readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
boolean first = firstReaderIdleEvent;
firstReaderIdleEvent = false;
try {
//触发fireUserEventTriggered方法
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
channelIdle(ctx, event);
} 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);
}
}
}
private abstract static class AbstractIdleTask implements Runnable {
private final ChannelHandlerContext ctx;
AbstractIdleTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run() {
if (!ctx.channel().isOpen()) {
return;
}
//调用子类的run方法
run(ctx);
}
protected abstract void run(ChannelHandlerContext ctx);
}
再看看schedule方法:
ScheduledFuture> schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
return ctx.executor().schedule(task, delay, unit);
}
ScheduledFuture schedule(final ScheduledFutureTask task) {
if (inEventLoop()) {
scheduledTaskQueue().add(task);
} else {
execute(new Runnable() {
@Override
public void run() {
scheduledTaskQueue().add(task);
}
});
}
return task;
}
schedule方法最终会调用AbstractScheduledEventExecutor#schedule方法,将定时任务包装成ScheduledFutureTask放入scheduledTaskQueue队列中。在eventLoop的runAllTasks中会拉取task进行调度(如果到了此延迟任务执行的时候)。
再看看上次读事件更新:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
//读事件进行中,会将reading设为true,此时不会除非读检测事件。
reading = true;
firstReaderIdleEvent = firstAllIdleEvent = true;
}
ctx.fireChannelRead(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//读事件完成后会更新读时间
if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
lastReadTime = ticksInNanos();
reading = false;
}
ctx.fireChannelReadComplete();
}
其他检测事件也类似,这里就不分析了。