Netty心跳基本检测机制
首先,了解下为什么需要心跳?
假设客户端(如手机,PAD)与服务器端已经建立了长连接,客户端开启飞行模式或者强制关机了,服务器端的handlerRemoved方法不会被调用,因为感知不到。此时,客户端往服务器端发送的消息无法到达。如何处理这种情况呢?那么就是通过心跳来做的,每隔一段时间客户端向服务器端发送心跳包,客户端经过一段时间收到后,客户端认为与服务器端连接是正常的。如果客户端经过一段时间没有收到心跳包,客户端则认为与服务端无法通信,由客户端发起断开连接,然后重新向服务器端发起新的连接。
Netty提供了空闲状态监测的处理器。IdleStateHandler
我们来看下Javadoc说明
/**
* Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
* read, write, or both operation for a while.
*
* <h3>Supported idle states</h3>
* <table border="1">
* <tr>
* <th>Property</th><th>Meaning</th>
* </tr>
* <tr>
* <td>{@code readerIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
* will be triggered when no read was performed for the specified period of
* time. Specify {@code 0} to disable.</td>
* </tr>
* <tr>
* <td>{@code writerIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
* will be triggered when no write was performed for the specified period of
* time. Specify {@code 0} to disable.</td>
* </tr>
* <tr>
* <td>{@code allIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
* will be triggered when neither read nor write was performed for the
* specified period of time. Specify {@code 0} to disable.</td>
* </tr>
* </table>
*
* <pre>
* // An example that sends a ping message when there is no outbound traffic
* // for 30 seconds. The connection is closed when there is no inbound traffic
* // for 60 seconds.
*
* public class MyChannelInitializer extends {@link ChannelInitializer}<{@link Channel}> {
* {@code @Override}
* public void initChannel({@link Channel} channel) {
* channel.pipeline().addLast("idleStateHandler", new {@link IdleStateHandler}(60, 30, 0));
* channel.pipeline().addLast("myHandler", new MyHandler());
* }
* }
*
* // Handler should handle the {@link IdleStateEvent} triggered by {@link IdleStateHandler}.
* public class MyHandler extends {@link ChannelDuplexHandler} {
* {@code @Override}
* public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {
* if (evt instanceof {@link IdleStateEvent}) {
* {@link IdleStateEvent} e = ({@link IdleStateEvent}) evt;
* if (e.state() == {@link IdleState}.READER_IDLE) {
* ctx.close();
* } else if (e.state() == {@link IdleState}.WRITER_IDLE) {
* ctx.writeAndFlush(new PingMessage());
* }
* }
* }
* }
*
* {@link ServerBootstrap} bootstrap = ...;
* ...
* bootstrap.childHandler(new MyChannelInitializer());
* ...
* </pre>
*
* @see ReadTimeoutHandler
* @see WriteTimeoutHandler
*/
public class IdleStateHandler extends ChannelDuplexHandler {
}
在一定时间内当有读、写或者读写没有被执行的时候,会触发一个空闲状态监测事件。
从Javadoc中我们也看到了基本的示例代码,理论+实践模式 :)
进一步看下构造方法:
/**
* Creates a new instance firing {@link IdleStateEvent}s.
*
* @param readerIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
* will be triggered when no read was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param writerIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
* will be triggered when no write was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param allIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
* will be triggered when neither read nor write was performed for
* the specified period of time. Specify {@code 0} to disable.
*/
public IdleStateHandler(
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds) {
this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
TimeUnit.SECONDS);
}
构造方法中提供了三个参数,第一个参数readerIdleTimeSeconds是指给定时间内没有读动作,则触发READER_IDLE事件;第二个参数writerIdleTimeSeconds是指给定时间内服务端没有写入动作则触发WRITER_IDLE事件,第三个参数allIdleTimeSeconds表示给定实践内没有发生读或者写动作,则触发ALL_IDLE事件。
我们可以编写自定义处理器,监测空闲状态,继承SimpleChannelInboundHandler,同时,重写
userEeventTriggered(ChannelHandlerContext ctx,Object evt)方法,两个参数分别是上下文对象,事件对象。userEeventTriggered方法是SimpleChannelInboundHandler父类ChannelInboundHandlerAdapter抽象适配类的方法。
当有操作操作超出指定空闲秒数时,便会触发UserEventTriggered事件。
IdleState代表了Channel的空心闲状态枚举,三种状态,包括READ_IDLE、WRITE_IDLE、ALL_IDLE
下面附上示例代码:
- Main方法启动类,这里我们增加了LoggerHandler日志处理器
public class MyIdleServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new MyIdleServerInitializer());
ChannelFuture channelFuture = bootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
- 编写MyIdleServerInitializer,初始化处理器链,增加了IdleStateHandler空闲事件处理器.
public class MyIdleServerInitializer extends ChannelInitializer<SocketChannel>{
@Override protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline channelPipeline = ch.pipeline();
channelPipeline.addLast(new IdleStateHandler(6, 4, 10));
channelPipeline.addLast(new MyIdleServerHandler());
}
}
- 编写自定义处理器MyIdleServerHandler,重写了channelRead0,目的主要为了测试写超时触发事件.
public class MyIdleServerHandler extends SimpleChannelInboundHandler<Object> {
@Override public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
int i=0;
while (i< 3) {
sleep(3000);
System.out.println("服务端执行写入操作3s");
ctx.channel().writeAndFlush("服务端写个客户端数据");
i++;
}
}
@Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvt = (IdleStateEvent) evt;
String eventType = null; // 空闲事件类型
switch (idleStateEvt.state()) {
case READER_IDLE:
eventType = "读空闲";
break;
case WRITER_IDLE:
eventType = "写空闲";
break;
case ALL_IDLE:
eventType = "读写空闲";
break;
}
System.out.println("客户端地址:" + ctx.channel().remoteAddress() + "超时事件被触发,eventType:" + eventType);
ctx.channel().close();
}
}
}
- 启动MyIdleServer后,使用MyChatClient请参见Netty多客户端通信一章
因设置写超时时间设置4秒,MyChatClient不输入任何内容,结果如下:
当MyChatClient输入内容后,执行结果会看到等到channelRead0执行完写入操作后,超过写超时时间才会触发WRITE_IDLE状态.
写入操作完成后触发事件
网友评论