1、服务端 -- 添加心跳、业务 Handler
pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
pipeline.addLast("handler", new NettyServerHandler());
2、服务端 -- NettyServerHandler
package com.sample.modules.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
public class NettyServerHandler extends ChannelInboundHandlerAdapter{
/**
* 心跳丢失次数
*/
private int counter = 0;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//重置心跳丢失次数
counter = 0;
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.READER_IDLE)){
counter++;
System.out.println("丢失了第 " + counter + " 个心跳包");
if (counter >= 3) {
ctx.channel().close().sync();
System.out.println("已与"+ctx.channel().remoteAddress()+"断开连接");
}
}
}else {
super.userEventTriggered(ctx,evt);
}
}
}
3、客户端 -- 添加心跳、业务 Handler
pipeline.addLast(new IdleStateHandler(0,2,0, TimeUnit.SECONDS));
pipeline.addLast(new StringEncoder());
pipeline.addLast("handler", new NettyClientHandler(host,port));
4、客户端 -- NettyClientHandler
package com.sample.modules.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.EventLoop;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.util.concurrent.TimeUnit;
public class NettyClientHandler extends ChannelInboundHandlerAdapter{
private String host;
private int port;
private NettyClient nettyClient;
public NettyClientHandler(String host, int port) {
this.host = host;
this.port = port;
nettyClient = new NettyClient(host,port);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client 断开连接");
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(()->{
nettyClient.start();
}, 1, TimeUnit.SECONDS);
ctx.fireChannelInactive();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.WRITER_IDLE)) {
System.out.println("client 需要发送心跳包");
ctx.channel().writeAndFlush("ping");
}
}
}
}
5、源码解析
1、属性说明
readerIdleTimeNanos: 读空闲超时时间设定
writerIdleTimeNanos: 写空闲超时时间设定
allIdleTimeNanos: 所有类型的空闲超时时间设定,包括读空闲和写空闲;
2、initialize 方法
- 重写了 handlerAdded、channelRegistered、channelActive 方法,调用 initialize 方法
- 在 initialize 方法中初始化 readerIdleTimeout、writerIdleTimeout、allIdleTimeout 定时任务
3、readerIdleTimeoutTask
@Override
protected void run(ChannelHandlerContext ctx) {
long nextDelay = readerIdleTimeNanos;
if (!reading) {
//1.当前时间减去上次读的时间, 也就是多久没有读数据到来
nextDelay -= ticksInNanos() - lastReadTime;
}
//2.如果小于0, 说明心跳超时
if (nextDelay <= 0) {
//3.开启下一次定时任务
readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
boolean first = firstReaderIdleEvent;
firstReaderIdleEvent = false;
//4.触发 userEventTriggered 方法
try {
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
4、writerIdleTimeoutTask
@Override
protected void run(ChannelHandlerContext ctx) {
long lastWriteTime = IdleStateHandler.this.lastWriteTime;
//1.当前时间减去上次写的时间, 记录还有多久没有写数据了
long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
//2.写超时
if (nextDelay <= 0) {
//3.开启下一次定时任务
writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
boolean first = firstWriterIdleEvent;
firstWriterIdleEvent = false;
try {
//4.如果有写数据, 直接返回
if (hasOutputChanged(ctx, first)) {
return;
}
//5.触发 userEventTriggered 方法
IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
5、channelRead 方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
//1.有数据到来, reading=true
reading = true;
firstReaderIdleEvent = firstAllIdleEvent = true;
}
//2.数据继续传递
ctx.fireChannelRead(msg);
}
6、channelReadComplete 方法
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//1.记录上次读的时间
if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
lastReadTime = ticksInNanos();
reading = false;
}
//2.继续传递
ctx.fireChannelReadComplete();
}
7、write 方法
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
//1.异步监听写事件, 记录上次写的时间
ctx.write(msg, promise.unvoid()).addListener(writeListener);
} else {
ctx.write(msg, promise);
}
}
8、destroy 方法
重写了 handlerRemoved、channelInactive 方法,调用了 destroy 方法,调用各个定时任务的取消方法
private void destroy() {
state = 2;
if (readerIdleTimeout != null) {
readerIdleTimeout.cancel(false);
readerIdleTimeout = null;
}
if (writerIdleTimeout != null) {
writerIdleTimeout.cancel(false);
writerIdleTimeout = null;
}
if (allIdleTimeout != null) {
allIdleTimeout.cancel(false);
allIdleTimeout = null;
}
}