【Netty】断线重连 详解

shadowLogo

前言:

日常生活 以及 企业应用 中,我们经常遇到一种情况 —— 长连接 场景下,因为 各种原因,使得 连接断开

就像我们玩 网络游戏 一样,可能有时候 “网不好”,就会看到页面上有个 “圈圈” 在转,这就是 断线自动重连 机制!

那么,使用 Netty,如何 实现 断线自动重连 呢?


我们先来思考下,断线自动重连 需要在 哪些情况 下实现呢?

应用场景:

  • 客户端启动 连接服务端 时
    如果 网络服务端 有问题,客户端连接失败
    可以 重连重连逻辑 加在 客户端
  • 系统运行过程 中
    网络故障服务端故障,导致客户端与服务端 断开连接 了也 需要重连
    可以在 客户端 处理数据的 HandlerchannelInactive()方法 中进行重连

接下来,本人就依据上述思想,来实现下 断线自动重连 机制:

代码实现:

首先是 服务端

服务端:

package edu.youzg.demo.reconnect;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-07 08:54
 * @Description: 带你深究Java的本质!
 */
public class NettyServerDemo {
    private int port;

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

    public static void main(String[] args) {
        NettyServerDemo nettyServer = new NettyServerDemo(9000);
        nettyServer.startUp();
    }

    private void startUp() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new ReconnectServerHandler());
                        }
                    });

            System.out.println("Netty Server start...");
            ChannelFuture channelFuture = bootstrap.bind(this.port).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    class ReconnectServerHandler extends ChannelInboundHandlerAdapter {

        /**
         * 读取客户端发送的数据
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         * @param msg 就是客户端发送的数据
         * @throws Exception
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
        }

        /**
         * 数据读取完毕处理方法
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         * @throws Exception
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ByteBuf buf = Unpooled.copiedBuffer("Yes, Youzg is a cool man!".getBytes(CharsetUtil.UTF_8));
            ctx.writeAndFlush(buf);
        }

        /**
         * 处理异常, 一般是需要关闭通道
         *
         * @param ctx   上下文对象, 含有通道channel,管道pipeline
         * @param cause 异常信息
         * @throws Exception
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }

    }

}

接下来是 客户端

客户端:

package edu.youzg.demo.reconnect;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;

import java.util.concurrent.TimeUnit;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-07 09:02
 * @Description: 带你深究Java的本质!
 */
public class NettyClientDemo {
    private String ip;
    private int port;
    private Bootstrap bootstrap;

    public NettyClientDemo(String ip, int port) {
        this.ip = ip;
        this.port = port;
        initClient();
    }

    public static void main(String[] args) throws InterruptedException {
        NettyClientDemo nettyClient = new NettyClientDemo("127.0.0.1", 9000);
        nettyClient.connect();
    }

    public void connect() throws InterruptedException {
        System.err.println("connect to Netty Server...");
        ChannelFuture channelFuture = this.bootstrap.connect(ip, port);
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {  // 没有连接成功,进行 “重连”
                    future.channel().eventLoop().schedule(() -> {
                        System.err.println("Connection Netty Server failed, trying to reconnect...");
                        try {
                            connect();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }, 3000, TimeUnit.MILLISECONDS);    // 设置 3s 进行一次 “重连”
                } else {
                    System.out.println("Connect Netty Server successfully!");
                }
            }
        });

        // 关闭对 当前通道 的监听
        channelFuture.channel().closeFuture().sync();
    }

    private void initClient() {
        NioEventLoopGroup group = new NioEventLoopGroup();

        this.bootstrap = new Bootstrap();
        this.bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new ReconnectClientHandler(NettyClientDemo.this));
                    }
                });
    }

    class ReconnectClientHandler extends ChannelInboundHandlerAdapter {
        private NettyClientDemo nettyClient;

        public ReconnectClientHandler(NettyClientDemo nettyClient) {
            this.nettyClient = nettyClient;
        }

        /**
         * 当客户端连接服务器完成就会触发该方法
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         * @throws Exception
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ByteBuf buf = Unpooled.copiedBuffer("Yuozg is a good man!".getBytes(CharsetUtil.UTF_8));
            ctx.writeAndFlush(buf);
        }

        /**
         * 当通道有读取事件时会触发,即服务端发送数据给客户端
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         * @param msg 服务端消息
         * @throws Exception
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            System.out.println("收到服务端的消息:" + buf.toString(CharsetUtil.UTF_8));
        }

        /**
         * channel 处于不活动状态时调用
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         * @throws Exception
         */
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.err.println("正在断线重连中...");
            this.nettyClient.connect();
        }

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

最后,本人来展示下 运行结果

运行结果:

客户端启动 连接服务端 时:

连接时

可以看到:

客户端启动 连接服务端 时,实现了 连接失败重连


系统运行过程 中

运行时

可以看到:

系统运行过程 中,实现了 断线自动重连


那么,至此,Netty断线自动重连 机制,就实现了!
溜了

posted @ 2021-05-07 09:52  在下右转,有何贵干  阅读(1065)  评论(0编辑  收藏  举报