Netty初步——心跳包与重连机制的实现

版权声明:个人博客:blog.suyeq.com 欢迎访问 https://blog.csdn.net/hackersuye/article/details/88076023

导入依赖

<dependency>
            <groupId>io.netty</groupId>
          <artifactId>netty-all</artifactId>
            <version>4.1.5.Final</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>

心跳包的实现

  • IdleStateHandler类

   该类构造方法为:

public IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit)

   readerIdleTime代表着读的空闲时间,而writerIdleTime表示写的空闲时间,TimeUnit表示时间的单位

   当超过读的时间(会一直监听channelRead方法)或者写的时间(监听有无写入)时,会调用入站ChannelInboundHandlerAdapter类中的userEventTriggered方法,只要在该方法里判断引起该方法的事件就可以来进行逻辑的处理,比如,引起读的事件:

@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent){
            //服务端对应着读事件,当为READER_IDLE时触发
            IdleStateEvent event = (IdleStateEvent)evt;
            if(event.state() == IdleState.READER_IDLE){
              .......
                
            }
            }else{
                super.userEventTriggered(ctx,evt);
            }
        }
    }
  • 具体实现

   了解到这个类后原理很简单,可以在客户端加入IdleStateHandler类处理,当客户端超过设置的写入时间时,会调用userEventTriggered方法,在里面判断是否是出发写事件,将服务端写入数据,服务端也原理一样

重连机制

   重连作用于某个时刻服务端断掉,或者客户端断掉后自动进行重新连接的操作。上述是一个需要重连的场景,还有一个场景就是第一次连接服务器不成功,需要再次尝试的情况下。

   针对第一种情况,基于Netty的机制,我们可以在channelInactive方法里,进行重连的操作,因为该方法是在Channel关闭时调用的。

   针对第二种情况,我们可以在客户端连接服务端的时候加入监听,当连接不成功的情况下,再次进行连接。

详细实现

   下面的Demo是在网上现成的代码下修改的,客户端会向服务端发送三次消息,服务端接收到三次消息后会把客户端给关闭,客户端则触发重新连接的操作,将在10后进行重连的操作

  1. HeartBeatServerHandler类
public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter {

    private int loss_connect_time = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + "Server :" + msg.toString());
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent){
            //服务端对应着读事件,当为READER_IDLE时触发
            IdleStateEvent event = (IdleStateEvent)evt;
            if(event.state() == IdleState.READER_IDLE){
                loss_connect_time++;
                if(loss_connect_time > 2){
                    System.out.println("接收消息超时");
                    System.out.println("关闭不活动的链接");
                    ctx.channel().close();
                }
            }else{
                super.userEventTriggered(ctx,evt);
            }
        }
    }

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


}
  1. HeartBeatServer类
public class HeartBeatServer {

    private int port;

    public HeartBeatServer(int port) {
        this.port = port;
    }

    public void start(){
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap server = new ServerBootstrap().group(bossGroup,workGroup)
                .channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast("handler",new IdleStateHandler(4, 0, 0, TimeUnit.SECONDS));
                        pipeline.addLast("decoder", new StringDecoder());
                        pipeline.addLast("encoder", new StringEncoder());
                        pipeline.addLast(new HeartBeatServerHandler());

                    }
                }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);

        try {
            ChannelFuture future = server.bind(port).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}
  1. HeartBeatClientHandler类
public class HeartBeatClientHandler extends ChannelInboundHandlerAdapter {

    private HeartBeatsClient heartBeatsClient;

    private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Heartbeat",
            CharsetUtil.UTF_8));

    private static final int TRY_TIMES = 3;

    private int currentTime = 1;

    public HeartBeatClientHandler(HeartBeatsClient heartBeatsClient){
        this.heartBeatsClient=heartBeatsClient;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("激活时间是:"+new Date());
        System.out.println("链接已经激活");
        ctx.fireChannelActive();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("停止时间是:"+new Date());
        System.out.println("关闭链接");
        System.out.println("将进行重连");
        final EventLoop loop = ctx.channel().eventLoop();
        loop.schedule(new Runnable() {
            public void run() {
                try {
                    System.out.println("lalallal");
                    heartBeatsClient.reConnectServer(new Bootstrap(),loop.parent());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },10L,TimeUnit.SECONDS);
        super.channelInactive(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("当前轮询时间:"+new Date());
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.WRITER_IDLE) {
                if(currentTime <= TRY_TIMES){
                    System.out.println("currentTime:"+currentTime);
                    currentTime++;
                    ctx.channel().writeAndFlush(HEARTBEAT_SEQUENCE.duplicate());
                }
            }
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String message = (String) msg;
        System.out.println(message);
        if (message.equals("Heartbeat")) {
            ctx.write("has read message from server");
            ctx.flush();
        }
        ReferenceCountUtil.release(msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
  1. HeartBeatsClient类
public class HeartBeatsClient  {

    private  int port;
    private  String address;
    private EventLoopGroup eventExecutors=new NioEventLoopGroup();

    public HeartBeatsClient(int port, String address) {
        this.port = port;
        this.address = address;
    }

    public HeartBeatsClient reConnectServer(Bootstrap bootstrap,EventLoopGroup eventExecutors) throws InterruptedException {
        System.out.println("lalalall2");
        final HeartBeatsClient heartBeatsClient=this;
        synchronized (this){
            bootstrap.group(eventExecutors).channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline channelPipeline=socketChannel.pipeline();
                            channelPipeline.addLast(new IdleStateHandler(0, 3, 0, TimeUnit.SECONDS));
                            channelPipeline.addLast(new StringDecoder());
                            channelPipeline.addLast(new StringEncoder());
                            channelPipeline.addLast(new HeartBeatClientHandler(heartBeatsClient));
                        }
                    });
        }
        bootstrap.remoteAddress(address, port);
        ChannelFuture channelFuture=bootstrap.connect().sync();
        channelFuture.addListener(new ConnectListener(this));
       channelFuture.channel().closeFuture().sync();
        return this;
    }

    public void start() throws InterruptedException {
        reConnectServer(new Bootstrap(),eventExecutors);
    }
}

  1. ConnectListener类
public class ConnectListener implements ChannelFutureListener {
    private HeartBeatsClient heartBeatsClient;

    public ConnectListener(HeartBeatsClient heartBeatsClient){
        this.heartBeatsClient=heartBeatsClient;
    }

    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        if (!channelFuture.isSuccess()){
            System.out.println("将进行重连");
            final EventLoop loop = channelFuture.channel().eventLoop();
            loop.schedule(new Runnable() {
                public void run() {
                    try {
                        heartBeatsClient.reConnectServer(new Bootstrap(),loop);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },1, TimeUnit.SECONDS);
        }else {
            System.out.println("连接成功");
        }
    }
}

注意事项

   IdleStateHandler类需要放在第一个加入ChannelPipline里面,否则会不成功,Demo下载

猜你喜欢

转载自blog.csdn.net/hackersuye/article/details/88076023