Netty网络框架学习笔记-7((心跳)检测空闲连接以及超时)

Netty网络框架学习笔记-7((心跳)检测空闲连接以及超时)

1.0 前言:

为了能够及时的将资源释放出来,我们会检测空闲连接和超时。常见的方法是通过发送信息来测试一个不活跃的链接,通常被称为“心跳”,然后在远端确认它是否还活着。(还有一个方法是比较激进的,简单地断开那些指定的时间间隔的不活跃的链接)。

处理空闲连接是一项常见的任务,Netty 提供了几个 ChannelHandler 实现此目的。

名称描述
IdleStateHandler如果连接闲置时间过长,则会触发 IdleStateEvent 事件。在 ChannelInboundHandler 中可以覆盖 userEventTriggered(…) 方法来处理 IdleStateEvent。
ReadTimeoutHandler在指定的时间间隔内没有接收到入站数据则会抛出 ReadTimeoutException 并关闭 Channel。ReadTimeoutException 可以通过覆盖 ChannelHandler 的 exceptionCaught(…) 方法检测到。
WriteTimeoutHandlerWriteTimeoutException 可以通过覆盖 ChannelHandler 的 exceptionCaught(…) 方法检测到。

2.0 简单例子

用来检测连接是否在空闲状态下!

2.1 带心跳检测的服务端

/**
 * @Author: ZhiHao
 * @Date: 2022/4/24 17:08
 * @Description: 心跳服务器
 * @Versions 1.0
 **/
@Slf4j
public class HeartbeatServer {

    public static void main(String[] args) {
        NioEventLoopGroup boosGroup = new NioEventLoopGroup(2);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(boosGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .handler(new LoggingHandler(LogLevel.INFO)) // boosGroup服务器处理程序, 日志
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        // 添加框架提供的字符串编解码处理器
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        //1. IdleStateHandler 是 netty 提供的处理空闲状态的处理器
                        //2. long readerIdleTime : 表示多长时间没有读, 就会发送一个心跳检测包检测是否连接
                        //3. long writerIdleTime : 表示多长时间没有写, 就会发送一个心跳检测包检测是否连接
                        //4. long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接
                        pipeline.addLast(new IdleStateHandler(5, 5, 10, TimeUnit.SECONDS));
                        //5. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个 handler 去处理, 通过调用(触发)下一个 handler 的 userEventTiggered ,
                        // 加入自己的自定义处理器(在处理器该userEventTriggered方法中去处理 IdleStateEvent(读 空闲,写空闲,读写空闲))
                        pipeline.addLast(new HeartbeatServerHandle());
                    }
                });

        try {
            ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        log.info("HeartbeatServer==, 服务器已经启动成功, 等待连接中!");
                    }
                }
            });
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("HeartbeatServer===, 发生异常:{}", e);
        } finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

2.1.1 服务端自定义处理器(包含处理空闲事件)

@Slf4j
public class HeartbeatServerHandle extends SimpleChannelInboundHandler<String> {

    private static ConcurrentHashMap<String,Long> concurrentHashMap = new ConcurrentHashMap<>();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        log.info("HeartbeatServerHandle-读取到信息:{}", msg);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 判断事件是否是IdleStateEvent
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateHandler = (IdleStateEvent) evt;
            IdleState state = idleStateHandler.state();
            String evtState = null;
            String key = ctx.channel().id().asLongText();
            Long count = concurrentHashMap.getOrDefault(key, 0L);
            switch(state) {
                case READER_IDLE:
                    evtState = "读空闲";
                    break;
                case WRITER_IDLE:
                    evtState = "写空闲";
                    break;
                case ALL_IDLE:
                    evtState = "读写空闲";
                    count++;
                    break;
                default:
                    break;
            }
            log.info("userEventTriggered-evtState:{}", evtState);
            // 空闲计数达5次, 进行测试连接是否正常
            if (count > 2L){
                ctx.writeAndFlush("测试客户端是否能接收信息")
                    // 发送失败时关闭通道, 在或者可以在达到空闲多少次后, 进行关闭通道
                        .addListener(ChannelFutureListener.CLOSE_ON_FAILURE);  
                concurrentHashMap.remove(key);
                return;
            }
            concurrentHashMap.put(key,count);
        } else {
            // 事件不是一个 IdleStateEvent 的话,就将它传递给下一个处理程序
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

2.2 客户端

@Slf4j
public class HeartbeatClient {

    public static void main(String[] args) {
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        // 添加框架提供的字符串编解码处理器
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new HeartbeatClientHandle());
                    }
                });

        try {
            ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888)).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        log.info("HeartbeatClient==, 客户端已经启动成功, 连接成功!");
                    }
                }
            });
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("HeartbeatClient===, 发生异常:{}", e);
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

2.2.1 客户端处理器

@Slf4j
public class HeartbeatClientHandle extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        log.info("HeartbeatClientHandle-读取到信息:{}", msg);
        //ctx.writeAndFlush("服务端你好!");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

结果:

19:35:38.182 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:38.182 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:43.186 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读写空闲
19:35:43.186 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:43.186 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:48.189 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:48.189 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:53.191 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读写空闲
19:35:53.191 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:53.191 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:58.205 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:58.205 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:36:03.197 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读写空闲
19:36:03.210 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@40d2cc5c
19:36:03.214 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲

// -------------------------------------------------------------------------------------------------------
19:36:03.225 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@3a94a792
19:36:03.231 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.heartbeat.HeartbeatClientHandle - HeartbeatClientHandle-读取到信息:测试客户端是否能接收信息

一旦连接断开, 就不会在检测心跳!

3.0 另一种场景上报数据:

在物联网或者其他需要在连接空闲时候, 上报自身状态 (例如: 上报设备状态, 或上报服务器状态)

3.1 客户端改造

// 省略其他代码
Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        // 添加框架提供的字符串编解码处理器
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        //  在写空闲30秒的时候进行上报数据
                        pipeline.addLast(new IdleStateHandler(0,30,0, TimeUnit.SECONDS));
                        pipeline.addLast(new HeartbeatClientReportHandle());
                    }
                });
// 省略其他代码

3.1.1 客户端处理器

@Slf4j
public class HeartbeatClientReportHandle extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        log.info("HeartbeatClientReportHandle-读取到信息:{}", msg);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 判断事件是否是IdleStateEvent
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateHandler = (IdleStateEvent) evt;
            if (idleStateHandler.state().equals(IdleState.WRITER_IDLE)){
                ctx.writeAndFlush(String.format("当前设备号: %s, 状态:%s", RandomUtil.randomInt(),RandomUtil.randomInt()));
            }
        } else {
            // 事件不是一个 IdleStateEvent 的话,就将它传递给下一个处理程序
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

结果:

// 服务端日志
15:58:45.697 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: -1691569318, 状态:-1274325485
15:59:15.680 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 733278004, 状态:1290704661
15:59:45.683 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 167749378, 状态:-1514067376
16:00:15.696 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: -156490593, 状态:-50335223
16:00:45.711 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 1998916514, 状态:-1099924846
16:01:15.725 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 1809189822, 状态:1953605308
16:01:45.731 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: -668619106, 状态:-1750520516
16:02:15.744 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 2142985320, 状态:880620132

从结果可以看出, 客户端每隔39秒会上报一次数据。

PS: 这个是要在通道空闲时候才会进行催发, 我们可能模拟IdleStateHandler 写一个定时上报的处理器, 里面创建发送定时上报的事件传递给下一个处理器。

1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Netty-WebSocket-Spring-Boot-Starter是一个用于将Websocket集成到Spring Boot应用程序中的库。它使用Netty作为底层框架,提供了一种快速和可靠的方式来处理异步通信。 这个库提供了一种简单的方法来创建Websocket端点,只需要使用注释和POJO类即可。在这些端点上可以添加动态的事件处理程序,以处理连接、断开连接和消息事件等。 此外,Netty-WebSocket-Spring-Boot-Starter还包括了一些安全性的特性,如基于令牌的授权和XSS保护,可以帮助您保持您的Websocket应用程序安全。 总的来说,Netty-WebSocket-Spring-Boot-Starter提供了一种快速和易于使用的方式来构建Websocket应用程序,使得它成为应用程序开发人员的有用工具。 ### 回答2: netty-websocket-spring-boot-starter 是一个开源的 Java Web 开发工具包,主要基于 Netty 框架实现了 WebSocket 协议的支持,同时集成了 Spring Boot 框架,使得开发者可以更加方便地搭建 WebSocket 服务器。 该工具包提供了 WebSocketServer 配置类,通过在 Spring Boot 的启动配置类中调用 WebSocketServer 配置类,即可启动 WebSocket 服务器。同时,该工具包还提供了多种配置参数,如端口号、URI 路径、SSL 配置、认证配置等等,可以根据业务需求进行自定义配置。 此外,该工具包还提供了一些可扩展的接口和抽象类,如 WebSocketHandler、ChannelHandlerAdapter 等,可以通过继承和实现这些接口和抽象类来实现业务逻辑的处理和拓展。 总的来说,netty-websocket-spring-boot-starter 提供了一个高效、简单、易用的 WebSocket 服务器开发框架,可以减少开发者的开发成本和工作量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值