Skip to content

配置文件设置心跳超时时间

yaml
xim:
  tcpPort: 9000
  webSocketPort: 19000
  bossThreadSize: 1
  workThreadSize: 4
  heartBeatTime: 30000 # 心跳超时时间,单位毫秒
  # ……

配置类对象

java
@Data
public class BootstrapConfig {

    private TcpConfig xim;

    @Data
    public static class TcpConfig {
        private Integer tcpPort;
        private Integer webSocketPort;
        private Integer bossThreadSize;
        private Integer workThreadSize;
        private Long heartBeatTime;

        private RedisConfig redis;
    }
    // ……
}

开启Netty心跳检测

首先开启心跳检测,添加IdleStateHandler,当Netty检测到心跳不活跃时,会交给下一个Handler的userEventTriggered方法

java
public class XimServer {
    // ……
    public XimServer(BootstrapConfig.TcpConfig config) {
        // ……
        server.group(bossGroup, workerGroup)
                // ……
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        sc.pipeline().addLast(new MessageDecoder());
                        sc.pipeline().addLast(new IdleStateHandler(0, 0, 10)); //开启心跳检测
                        sc.pipeline().addLast(new NettyServerHandler());
                    }
                });
    }
    // ……
}

记录心跳跳动时间

如果是心跳消息,记录心跳最后一次跳动时间到channel连接对象中,后面在channel中取出时间与当前时间对比,如果超过设置时间则改为离线状态。

java
public class NettyServerHandler extends SimpleChannelInboundHandler<Message> {


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
        Integer command = msg.getMessageHeader().getCommand();
        if (command == SystemCommand.LOGIN.getCommand()) {
            login(ctx, msg);
        } else if (command == SystemCommand.LOGOUT.getCommand()) {
            // 用户主动退出,清空用户登录Session信息
            SessionSocketHolder.removeUserSession((NioSocketChannel) ctx.channel());
        } else if (command == SystemCommand.PING.getCommand()) {
            // 如果是心跳消息,设置心跳最后一次跳动时间
            ctx.channel().attr(AttributeKey.valueOf(Constants.ReadTime)).set(System.currentTimeMillis());
        }
    }
}

心跳不活跃设置登录状态为离线

当Netty检测到心跳不活跃时,会交给下一个Handler的userEventTriggered方法,我们创建一个新的Handler专门处理心跳不活跃时的情况。

  1. 判断是否是心跳消息
  2. 分别判断读空闲、写空闲、读写空闲,本项目只处理读写空间
java
@Slf4j
public class HearBeatHandler extends ChannelInboundHandlerAdapter {

    private Long heartBeatTime;

    public HearBeatHandler(Long heartBeatTime) {
        this.heartBeatTime = heartBeatTime;
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;        // 强制类型转换
            if (event.state() == IdleState.READER_IDLE) {
                log.info("读空闲");
            } else if (event.state() == IdleState.WRITER_IDLE) {
                log.info("进入写空闲");
            } else if (event.state() == IdleState.ALL_IDLE) {
                // 1.获取最后一次心跳跳动时间
                Long lastReadTime = (Long) ctx.channel().attr(AttributeKey.valueOf(Constants.ReadTime)).get();
                long now = System.currentTimeMillis();

                if (lastReadTime != null && now - lastReadTime > heartBeatTime) {
                    // 离线,清除channel连接信息,redis信息状态改为离线但不删除
                    SessionSocketHolder.offlineUserSession((NioSocketChannel) ctx.channel());
                }
            }
        }
    }
}

在Netty启动时添加心跳处理Handler

HearBeatHandler处理心跳,IdleStateHandler设置每10s没有读写触发一次HearBeatHandleruserEventTriggered方法。

  • 第一次触发时,当前时间-channel记录的时间≈1000ms
  • 第二次触发时,当前时间-channel记录的时间≈2000ms
  • 第三次触发时,当前时间-channel记录的时间≈3000ms
  • 第三次触发时发现3000ms大于等于配置文件中记录的3000ms,设置当前channel为离线状态。
java
public class XimServer {
    // ……
    public XimServer(BootstrapConfig.TcpConfig config) {
        // ……
        server.group(bossGroup, workerGroup)
                // ……
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        sc.pipeline().addLast(new MessageDecoder());
                        sc.pipeline().addLast(new IdleStateHandler(0, 0, 10)); //开启心跳检测
                        sc.pipeline().addLast(new HearBeatHandler(config.getHeartBeatTime())); // 处理心跳
                        sc.pipeline().addLast(new NettyServerHandler());
                    }
                });
    }
    // ……
}