实际情况下客户端断开与服务端连接的方式有很多,例如开启飞行模式、突然的无网络服务,这类情况下的连接断开无法使用 handlerRemoved 捕获到,因此需要在服务端使用心跳检测机制来确保服务端的准确下线
可以使用IdleStateHandler进行心跳检测心跳检测分为三种,分别是:没有读操作、没有写操作、没有读写操作,当这些情况发生后,IdleStateHandler将会触发事件给下一个Handler进行处理

代码:

心跳检测服务端代码:

  1. package keepalive;
  2. import io.netty.bootstrap.ServerBootstrap;
  3. import io.netty.channel.*;
  4. import io.netty.channel.nio.NioEventLoopGroup;
  5. import io.netty.channel.socket.SocketChannel;
  6. import io.netty.channel.socket.nio.NioServerSocketChannel;
  7. import io.netty.handler.logging.LogLevel;
  8. import io.netty.handler.logging.LoggingHandler;
  9. import io.netty.handler.timeout.IdleStateHandler;
  10. import java.util.concurrent.TimeUnit;
  11. /* 心跳检测Handler案例 */
  12. public class MyServer {
  13. public static void main(String[] args) throws InterruptedException {
  14. EventLoopGroup bossGroup = new NioEventLoopGroup(1); //管理连接请求线程组
  15. EventLoopGroup workGroup = new NioEventLoopGroup(); //进行业务处理线程组
  16. try {
  17. ServerBootstrap serverBootstrap = new ServerBootstrap();
  18. serverBootstrap.group(bossGroup,workGroup) //设置两个线程组
  19. .channel(NioServerSocketChannel.class) //使用 NioSocketChannel 作为服务器通道实现
  20. .handler(new LoggingHandler(LogLevel.INFO)) //在BossGroup添加一个日志处理器
  21. .childHandler(new ChannelInitializer<SocketChannel>() {
  22. @Override
  23. protected void initChannel(SocketChannel ch) throws Exception {
  24. ChannelPipeline pipeline = ch.pipeline();
  25. /*Netty提供的IdleStateHandler(处理空闲状态的处理器),实际的客户端离线情况并不是都会被 handlerRemoved 捕获到
  26. * 只有心跳检测才能准确确认客户端离线的准确性,符合参数对应的事件发生后将会触发 IdlestateEvent
  27. * readerIdleTime: 没有读操作的时间,到时间会发送一个心跳检测包检测是否连接
  28. * writerIdleTime: 没有写操作的时间,到时间会发送一个心跳检测包检测是否连接
  29. * allIdleTime: 没有读写操作的时间,到时间会发送一个心跳检测包检测是否连接
  30. * TimeUnit: 时间格式
  31. */
  32. pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
  33. /*
  34. * 当 IdlestateEvent 触发后,就会传递给管道的下一个handler
  35. * 通过调用(触发)下一个handler的 userEventTiggered,在该方法中去处理 IdlestateEvent
  36. * 所以需要自定义一个Handler放在IdleStateHandler的后面
  37. */
  38. pipeline.addLast(new MyServerHandler()); //添加空闲检测进一步处理handler
  39. }
  40. });
  41. ChannelFuture future = serverBootstrap.bind(7000).sync();
  42. future.channel().closeFuture().sync(); //对关闭通道进行监听(当有关闭通道的消息时才进行监听)
  43. } finally {
  44. bossGroup.shutdownGracefully();
  45. workGroup.shutdownGracefully();
  46. }
  47. }
  48. }

心跳检测自定义Handler:

  1. package keepalive;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.ChannelInboundHandlerAdapter;
  4. import io.netty.handler.timeout.IdleStateEvent;
  5. /* 心跳检测自定义处理Handler */
  6. public class MyServerHandler extends ChannelInboundHandlerAdapter {
  7. /*
  8. * ctx: 上下文
  9. * evt: 事件
  10. */
  11. @Override
  12. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
  13. if(evt instanceof IdleStateEvent){
  14. IdleStateEvent event = (IdleStateEvent) evt;
  15. String eventType = null;
  16. switch (event.state()){
  17. case READER_IDLE:
  18. eventType = "读空闲(客户端没有发送消息)";
  19. break;
  20. case WRITER_IDLE:
  21. eventType = "写空闲(服务端没有数据返回给客户端)";
  22. break;
  23. case ALL_IDLE:
  24. eventType = "读写空闲(客户端和服务端都没操作)";
  25. ctx.channel().close();
  26. break;
  27. default: break;
  28. }
  29. System.out.println(ctx.channel().remoteAddress()+"--超时时间--"+eventType);
  30. System.out.println("服务器需要做出处理....");
  31. }
  32. }
  33. }

测试:

启动心跳检测服务端后再启动之前的聊天室客户端代码,可以看到在指定的allIdleTime时间后客户端由于没有做任何操作触发了 ALL_IDLE 事件,通道被关闭
image.png
客户端通道长时间闲置,在指定时间后被关闭