实际情况下客户端断开与服务端连接的方式有很多,例如开启飞行模式、突然的无网络服务,这类情况下的连接断开无法使用 handlerRemoved 捕获到,因此需要在服务端使用心跳检测机制来确保服务端的准确下线
可以使用IdleStateHandler进行心跳检测,心跳检测分为三种,分别是:没有读操作、没有写操作、没有读写操作,当这些情况发生后,IdleStateHandler将会触发事件给下一个Handler进行处理
代码:
心跳检测服务端代码:
package keepalive;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/* 心跳检测Handler案例 */
public class MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1); //管理连接请求线程组
EventLoopGroup workGroup = new NioEventLoopGroup(); //进行业务处理线程组
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workGroup) //设置两个线程组
.channel(NioServerSocketChannel.class) //使用 NioSocketChannel 作为服务器通道实现
.handler(new LoggingHandler(LogLevel.INFO)) //在BossGroup添加一个日志处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/*Netty提供的IdleStateHandler(处理空闲状态的处理器),实际的客户端离线情况并不是都会被 handlerRemoved 捕获到
* 只有心跳检测才能准确确认客户端离线的准确性,符合参数对应的事件发生后将会触发 IdlestateEvent
* readerIdleTime: 没有读操作的时间,到时间会发送一个心跳检测包检测是否连接
* writerIdleTime: 没有写操作的时间,到时间会发送一个心跳检测包检测是否连接
* allIdleTime: 没有读写操作的时间,到时间会发送一个心跳检测包检测是否连接
* TimeUnit: 时间格式
*/
pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
/*
* 当 IdlestateEvent 触发后,就会传递给管道的下一个handler
* 通过调用(触发)下一个handler的 userEventTiggered,在该方法中去处理 IdlestateEvent
* 所以需要自定义一个Handler放在IdleStateHandler的后面
*/
pipeline.addLast(new MyServerHandler()); //添加空闲检测进一步处理handler
}
});
ChannelFuture future = serverBootstrap.bind(7000).sync();
future.channel().closeFuture().sync(); //对关闭通道进行监听(当有关闭通道的消息时才进行监听)
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
心跳检测自定义Handler:
package keepalive;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
/* 心跳检测自定义处理Handler */
public class MyServerHandler extends ChannelInboundHandlerAdapter {
/*
* ctx: 上下文
* evt: 事件
*/
@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 = "读写空闲(客户端和服务端都没操作)";
ctx.channel().close();
break;
default: break;
}
System.out.println(ctx.channel().remoteAddress()+"--超时时间--"+eventType);
System.out.println("服务器需要做出处理....");
}
}
}
测试:
启动心跳检测服务端后再启动之前的聊天室客户端代码,可以看到在指定的allIdleTime时间后客户端由于没有做任何操作触发了 ALL_IDLE 事件,通道被关闭
客户端通道长时间闲置,在指定时间后被关闭