1 案例要求
- 编写一个 Netty心跳检测机制案例:
- 当服务器超过3秒没有读时,就提示读空闲;
- 当服务器超过5秒没有写操作时,就提示写空闲;
- 实现当服务器超过7秒没有读或者写操作时,就提示读写空闲;
2 代码实现分析
2.1 代码实现
public class MyServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))// 日志处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 加入 netty 提供的 IdleStateHandler
/*
1.IdleStateHandler 是 netty 提供的处理空闲状态的处理器
2.参数说明:
long readerIdleTime:表示服务端多长时间没有读数据,就会发送一个心跳检测包检测是否连接
long writerIdleTime:表示服务端多长时间没有写数据,就会发送一个心跳检测包检测是否连接
long allIdleTime:表示服务端多长时间没有 读/写 数据,就会发送一个心跳检测包检测是否连接
TimeUnit unit:时间单位
3.当 IdleStateEvent 触发后,就会传递给管道的下一个handler去处理,
通过调用(触发)handler 的 userEventTiggered,在该方法中去处理
IdleStateEvent(读空闲、写空闲、读写空闲)
*/
pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
// 加入一个对空闲检测进一步处理的 handler(自定义)
// 实现 userEventTiggered() 方法
pipeline.addLast(new MyServerHandler());
}
});
System.out.println("netty 服务器启动成功");
ChannelFuture channelFuture = bootstrap.bind(7000).sync();
// 监听关闭事件
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
// ================================================================ //
public class MyServerHandler extends ChannelInboundHandlerAdapter {
/**
*
* @param ctx 上下文
* @param evt 事件
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
case READER_IDLE:
eventType = "读空闲";
break;
case WRITER_IDLE:
eventType = "写空闲";
break;
case ALL_IDLE:
eventType = "读写空闲";
break;
}
System.out.println(ctx.channel().remoteAddress() + " 发生了超时事件:" + eventType);
}
}
}
上面的代码和前面几篇文章的总体实现思路基本一致,只是在 bootstrap中加入了 Netty 自带的 日志处理器LoggingHandler
,并且在pipline中加入了Netty 自带的 IdleStateHandler
。
2.2 代码分析
我们重点分析一下IdleStateHandler类,IdleStateHandler类 是 netty 提供的处理空闲状态的处理器。
首先我们看一下官方文档中对它的描述:Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed read, write, or both operation for a while.翻译过来就是:当 {@link Channel} 有一段时间没有执行读取、写入或读写操作时,触发 {@link IdleStateEvent}。
我们在pipline添加它时配置了四个参数:
long readerIdleTime
:表示服务端多长时间没有读数据,就会发送一个心跳检测包检测是否连接long writerIdleTime
:表示服务端多长时间没有写数据,就会发送一个心跳检测包检测是否连接long allIdleTime
:表示服务端多长时间没有 读/写 数据,就会发送一个心跳检测包检测是否连接TimeUnit unit
:时间单位pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
根据官方文档的描述,我们可以知道当 Channel 有一段时间没有执行读取、写入或读写操作时,就会触发 IdleStateEvent。当 IdleStateEvent
触发后,就会传递给管道的下一个handler去处理(就是上面代码中自己写的 handler),通过调用(触发) handler 中的的 userEventTiggered()方法,在该方法中去处理IdleStateEvent(读空闲、写空闲、读写空闲)。
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
case READER_IDLE:
eventType = "读空闲";
break;
case WRITER_IDLE:
eventType = "写空闲";
break;
case ALL_IDLE:
eventType = "读写空闲";
break;
}
System.out.println(ctx.channel().remoteAddress() + " 发生了超时事件:" + eventType);
}
}
我们可以看见该方法有一个 Objec evt 参数,它的类型是 IdleStateEvent。
我们能通过 IdleStateEvent
类中的 IdleState state
属性得知发生的是什么事件(读空闲、写空闲、读写空闲)。IdleState 是一个枚举类。
/**
* An {@link Enum} that represents the idle state of a {@link Channel}.
*/
public enum IdleState {
/**
* No data was received for a while.
*/
READER_IDLE,
/**
* No data was sent for a while.
*/
WRITER_IDLE,
/**
* No data was either received or sent for a while.
*/
ALL_IDLE
}
3 问题思考
之前我们自定义handler的时候会继承 SimpleChannelInboundHandler,它的父类有handlerRemoved()方法,该方法会在channel断开连接的时候调用,那么有这个方法为什么还需要心跳检测呢?直接在channel断开的时候做处理不就行了吗?
public abstract class ChannelHandlerAdapter implements ChannelHandler {
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
}
这是因为很多情况服务端感知不到channel断开连接,比如手机突然强制关机、进入飞行模式等情况,这样的话TCP连接没有经过四次挥手断开连接,因此服务端无法感知,还是需要心跳检测机制来确保客户端是否在线。