[Netty实践] 心跳检测

5 篇文章 0 订阅

目录

一、什么是心跳检测

二、前置学习

三、服务端实现

四、客户端实现

五、测试


一、什么是心跳检测

心跳检测是用于保障服务端与客户端之间通信连接状态的实时监控。客户端不断向服务端发送心跳包(心跳包就是一组数据,自行定义,能够进行区分就好)。服务端在一定时间范围内能够正常接收客户端心跳包的话,就认为连接正常活跃;如果服务端在一定时间内没有接收到客户端心跳包的话,就认为连接出现中断或异常,那么就可以进行连接断开、释放资源等操作,从而保证节省服务端连接资源。

二、前置学习

实现心跳检测,将通过由Netty提供的以下类:

1、IdleStateHandler

主要有三个关键参数,通过构造函数传入:

读空闲时间,readerIdleTime/readerIdleTimeSeconds

写空闲时间,writerIdleTime/writerIdleTimeSeconds

读写空闲时间,allIdleTime/allIdleTimeSeconds

如果对某一个参数不感兴趣,只需要将对应参数设置为0即可

2、触发事件

当通道有一段时间没有执行读取、写入或同时执行这两种操作时,触发IdleStateEvent。

没有读取操作,将触发 IdleState.READER_IDLE事件

没有写入操作,将触发IdleState.WRITER_IDLE事件

没有读写事件,将触发IdleState.ALL_IDLE事件

3、处理事件

当触发上诉事件时,我们需要接收并且进行对应处理,通过重写handler中的userEventTriggered方法,进行事件处理,参考如下:

public class MyHandler extends ChannelDuplexHandler {
      @Override
      public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
          if (evt instanceof IdleStateEvent) {
              IdleStateEvent e = (IdleStateEvent) evt;
              if (e.state() == IdleState.READER_IDLE) {
                  ctx.close();
              } else if (e.state() == IdleState.WRITER_IDLE) {
                  ctx.writeAndFlush(new PingMessage());
              }
          }
      }
  }

接下来,将通过实际代码,来实现自己的心跳检测。

三、服务端实现

本次实现,服务端只关注读空闲事件,也就是如果服务端在3秒内没有收到客户端的心跳包,将会关闭与客户端之间的连接。

1、ServerChannelInitializer,用于与每个客户端Channel的初始化

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 服务端只关注读事件,如果3秒内没有收到客户端的消息,将会触发IdleState.READER_IDLE事件,将由HeartbeatHandler进行处理
        pipeline.addLast(new IdleStateHandler(3, 0, 0));
        pipeline.addLast(new LineBasedFrameDecoder(1024));
        pipeline.addLast(new StringDecoder());
        // 处理客户端读超时
        pipeline.addLast(new HeartbeatHandler());
    }
    
}

2、HeartbeatHandler,用于处理IdleStateEvent事件,服务端只关注读空闲事件,当触发读空闲事件时,将会关闭与客户端之间的连接

public class HeartbeatHandler extends ChannelDuplexHandler {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 处理IdleState.READER_IDLE时间
        if(evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;

            IdleState idleState = ((IdleStateEvent) evt).state();

            // 如果是触发的是读空闲时间,说明已经超过n秒没有收到客户端心跳包
            if(idleState == IdleState.READER_IDLE) {
                System.out.println("超过n秒没有收到客户端心跳, channel: " + ctx.channel());

                // 关闭channel,避免造成更多资源占用
                ctx.close();
            }

        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("接收到客户端数据, channel: " + ctx.channel() + ", 数据: " + msg.toString());
    }
}

3、NettyServer,用于创建并启动服务端

public class NettyServer {


    public void bind(Integer port){
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();

        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ServerChannelInitializer());

            // 阻塞, 等待启动
            ChannelFuture channelFuture = serverBootstrap.bind(port).syncUninterruptibly();

            System.out.println("server启动成功!");

            // 阻塞,等到关闭
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            childGroup.shutdownGracefully();
            parentGroup.shutdownGracefully();
        }


    }

}

四、客户端实现

客户端只关注写空闲事件,如果在2秒内没有任何写操作,将会触发写空闲事件,向服务端发送心跳包。

1、ClientChannelInitializer,用于与服务端通信的Channel初始化

public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 客户端只关注写事件,如果超过2秒没有发送数据,则发送心跳包
        pipeline.addLast(new IdleStateHandler(0, 2, 0));
        pipeline.addLast(new LineBasedFrameDecoder(1024));
        pipeline.addLast(new StringEncoder());
        pipeline.addLast(new HeartbeatHandler());
    }

}

2、HeartbeatHandler,处理写空闲事件

public class HeartbeatHandler extends ChannelDuplexHandler {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;

            IdleState idleState = idleStateEvent.state();

            if(idleState == IdleState.WRITER_IDLE) {
                ctx.writeAndFlush("两秒没有写数据,发送心跳包\n");
                System.out.println("超过两秒没有写数据,发送心跳包");
            }

        }
    }
}

3、NettyClient,初始化客户端并且启动

public class NettyClient {

    public void connect(String remoteIP, Integer port) {
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();

            bootstrap.group(workGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.AUTO_READ, true)
                    .handler(new ClientChannelInitializer());

            // 阻塞,等待连接
            ChannelFuture channelFuture = bootstrap.connect(remoteIP, port).sync();

            System.out.println("client启动成功!");

            // 阻塞,等待关闭
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workGroup.shutdownGracefully();
        }
    }

}

五、测试

1、启动服务端

new NettyServer().bind(8000);

2、启动客户端

new NettyClient().connect("127.0.0.1", 8000);

3、查看运行结果:

1)客户端

client启动成功!
超过两秒没有写数据,发送心跳包
超过两秒没有写数据,发送心跳包
超过两秒没有写数据,发送心跳包
...

2)服务端

server启动成功!
接收到客户端数据, channel: [id: 0x093d838a, L:/127.0.0.1:8000 - R:/127.0.0.1:54593], 数据: 两秒没有写数据,发送心跳包
接收到客户端数据, channel: [id: 0x093d838a, L:/127.0.0.1:8000 - R:/127.0.0.1:54593], 数据: 两秒没有写数据,发送心跳包
接收到客户端数据, channel: [id: 0x093d838a, L:/127.0.0.1:8000 - R:/127.0.0.1:54593], 数据: 两秒没有写数据,发送心跳包
...

4、对比

可将ClientChannelInitializer中的以下语句进行注释

pipeline.addLast(new HeartbeatHandler());

重启客户端,观察结果:

1)服务端

server启动成功!
超过n秒没有收到客户端心跳, channel: [id: 0xd8e262c0, L:/127.0.0.1:8000 - R:/127.0.0.1:54718]

2)客户端,channel关闭退出

client启动成功!

Process finished with exit code 0

要使用Netty搭建一个接收心跳包的Socket接口,可以按照以下步骤进行: 1. 导入Netty的依赖包: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.42.Final</version> </dependency> ``` 2. 创建一个服务器端的ChannelInitializer,用于初始化通道: ```java public class HeartbeatServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 添加心跳包处理器 pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS)); pipeline.addLast(new HeartbeatServerHandler()); } } ``` 3. 创建一个处理器,用于处理心跳包: ```java public class HeartbeatServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 处理业务逻辑 // ... // 如果收到心跳包,就回复一个心跳包 if (msg instanceof HeartbeatMessage) { ctx.writeAndFlush(new HeartbeatMessage()); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; if (event.state() == IdleState.READER_IDLE) { // 如果读取超时,就关闭连接 ctx.close(); } } else { super.userEventTriggered(ctx, evt); } } } ``` 4. 创建一个消息类,用于表示心跳包: ```java public class HeartbeatMessage implements Serializable { private static final long serialVersionUID = 1L; } ``` 5. 启动服务器: ```java public class HeartbeatServer { public static void main(String[] args) throws Exception { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(new HeartbeatServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8080).sync(); channelFuture.channel().closeFuture().sync(); } } ``` 这样,就成功地使用Netty搭建了一个接收心跳包的Socket接口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值