Netty心跳检测机制实战(附源码)

这里是小奏 ,觉得文章不错可以关注公众号小奏技术

心跳分类

一般心跳有两个层面

  • 协议层
  • 应用层

为什么要分两个心跳呢?全部交给tcp的心跳(keepLive)不就行了吗?应用还自己发送心跳干嘛?

这里我们举一个简单例子就说明情况

目前我们有一个client和一个server

某个时间点server虽然没有宕机,端口还在监听,但是server比如因为cpu满了或者GC之类的情况,导致server无法处理client的请求

但是在tcp层面的keepLive来说还是正常的,这个时候client就会一直等待server的响应,一直请求这个server

实际这个server已经无法处理请求了。类似进入假死状态

协议层心跳检测

netty中要开始tpckeepLive心跳检测机制使用如下方式即可

java 复制代码
ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup) 
                     .channel(NioServerSocketChannel.class) 
                     //设置TCP Keepalive
                     .childOption(ChannelOption.SO_KEEPALIVE, true)

核心就是ChannelOption.SO_KEEPALIVE, true

应用层心跳检测

应用层心跳检测netty提供了开箱即用的心跳检测类IdleStateHandler,这个类可以用来检测channel的空闲状态,如果超过一定时间没有读写操作,就会触发userEventTriggered方法,我们可以在这个方法中处理心跳逻辑

IdleStateHandler的核心构造函数就一个

java 复制代码
 public IdleStateHandler(boolean observeOutput,
    long readerIdleTime, long writerIdleTime, long allIdleTime,
    TimeUnit unit) {}

其他的构造都是对这个构造的重载

我们来看看这个几个参数

  • observeOutput 默认false,这个参数控制是否观察出站数据,以决定一个连接是否处于空闲状态。如果设置为true,当有出站数据时(即数据从Netty的出站缓冲区写入到操作系统的socket发送缓冲区),则认为这个连接不是写空闲的。 这对于那些有潜在的写操作,但数据可能会在Netty的出站缓冲区里停留较长时间的情况很有用。设置为false时,只考虑用户代码层面的写操作
  • readerIdleTime 读空闲时间,即多长时间没有读操作,就会触发userEventTriggered方法
  • writerIdleTime 写空闲时间,即多长时间没有写操作,就会触发userEventTriggered方法
  • allIdleTime 读写空闲时间,即多长时间没有读写操作,就会触发userEventTriggered方法
  • unit 时间单位

实战

纸上得来终觉浅,绝知此事要躬行

我们基于IdleStateHandler来检测管理我们连接试试。

连接管理Handler NettyConnectManageHandler

  • NettyConnectManageHandler
java 复制代码
@ChannelHandler.Sharable
public class NettyConnectManageHandler extends ChannelDuplexHandler {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.ALL_IDLE)) {
                System.out.println("[xiaozou] 触发 读写空闲事件");
                ctx.writeAndFlush("Closing connection due to idle timeout...")
                    .addListener((ChannelFutureListener) future -> {
                        if (future.isSuccess()) {
                            System.out.println("[xiaozou] 消息发送成功,关闭连接");
                            future.channel().close();
                        } else {
                            System.out.println("[xiaozou] 消息发送失败");
                            future.cause().printStackTrace();
                        }
                    });
            }
        }
        ctx.fireUserEventTriggered(evt);

    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[xiaozou] 连接断开");
        super.channelInactive(ctx);
        ctx.channel().close();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[xiaozou] 连接建立");
        super.channelActive(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[xiaozou] 连接取消注册");
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[xiaozou] 连接注册");
        super.channelRegistered(ctx);
    }

}

这里我们主要是对触发了IdleStateEvent事件进行处理,如果是ALL_IDLE,我们就关闭连接

Server

java 复制代码
public class XiaoZouHeartBeatServer {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            NettyConnectManageHandler manageHandler = new NettyConnectManageHandler();
            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    ch.pipeline()
                        .addLast(StringDecoder.class.getSimpleName(), new StringDecoder())
                        .addLast(StringEncoder.class.getSimpleName(), new StringEncoder())
                        .addLast(new IdleStateHandler(0, 0, 10))
                        .addLast(manageHandler)
                        .addLast(new ReadMsgHandler());

                }
            });
            ChannelFuture future = bootstrap.bind(9000).sync();
            System.out.println("xiaozou netty server start done.");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

server主要添加了IdleStateHandler,这里我们使用的是读写空闲时间为10秒,如果10秒没有读写操作,我们就关闭连接

Client

java 复制代码
public class XiaoZouHeartBeatClient {

    public static void main(String[] args) throws Exception {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            NettyConnectManageHandler handler = new NettyConnectManageHandler();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline()
                            .addLast(StringDecoder.class.getSimpleName(), new StringDecoder())
                            .addLast(StringEncoder.class.getSimpleName(), new StringEncoder())
                            .addLast(new IdleStateHandler(0, 0, 5))
                            .addLast(handler)
                            .addLast(new ReadMsgHandler());
                    }
                });
            Channel channel = bootstrap.connect("127.0.0.1", 9000).sync().channel();
            System.out.println("xiaozou netty client start done.");
            Random random = new Random();
            while (channel.isActive()) {
                int i = random.nextInt(8);
                System.out.println("random value is " + i);
                TimeUnit.SECONDS.sleep(i);
                channel.writeAndFlush(HeartBeatConstant.HEART_BEAT);
            }

        } finally {
            eventLoopGroup.shutdownGracefully();
        }

    }
}

client我们也添加了IdleStateHandler,这里我们使用的是读写空闲时间为5秒,如果5秒没有读写操作,我们就关闭连接

运行结果

  • server
  • client

总结

上面的代码我们主要是客户端触发了空闲读写事件,然后进行了连接关闭。

可以看到IdleStateHandler心跳是一个单向的心跳检测。

这里我们演示的是clientserver都有心跳检测。 比如client 5s没有读写操作,就会关闭连接,server10s没有读写操作,也会关闭连接。

源码

相关推荐
奋斗小强3 分钟前
前端工程化:从 Webpack 到 Vite,打包速度提升 10 倍的秘密
后端
我叫黑大帅5 分钟前
Golang中实时推送的功臣 - WebSocket
后端·面试·go
朱雨鹏20 分钟前
图解RocketMQ运行原理
后端·rocketmq
颜颜颜yan_23 分钟前
从千毫秒到亚毫秒:连接条件下推如何让复杂 SQL 飞起来
后端
程序员小崔日记39 分钟前
WebSocket 全面解析:让浏览器“实时说话”的黑科技(建议收藏)
后端·websocket·实时通信
-Da-1 小时前
【操作系统学习日记】《现代处理器性能的三重奏:ISA架构、流水线与缓存系统》
后端·缓存·架构·系统架构
李慕婉学姐1 小时前
Springboot养老服务管理系统c0t92vu6(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
rell3361 小时前
机顶盒播放udp/rtp马赛克
java·网络·网络协议·udp
lied16636348061 小时前
配置网站HTTP 自动跳转 HTTPS
网络协议·http·https
tang777891 小时前
哪些行业用动态代理ip?哪些行业用静态代理IP?怎样区分动态ip和静态ip?(互联网人必码·实用长文)
大数据·网络·爬虫·python·网络协议·tcp/ip·智能路由器