Netty网络框架学习笔记-17(客户端断线重连)

Netty网络框架学习笔记-17(客户端断线重连_2022-06-27)

使用网络编程, 就不可避免客户端存在, 断网, 设备断电, 导致客户端与服务端的连接中断, 在或者启动时候就失败了!

所以需要有重连机制

netty的重连本质上就是在调多一次 bootstrap.connect(remoteAddress).sync()

1.0 最简单的固定间隔时间重连 (不建议使用)

客户端连接成功后, 当前连接线程会阻塞,

当客户端连接失败, 会抛出一个 ConnectException 异常, 当拦截到这个异常就开启重连

public static void main(String[] args) {
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap = bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                       //..........省略
                    }
                });

        InetSocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 8888);
        // 重试次数
        int retryCount = 100;
    	// 一个条件死循环, 里面第一步会不断的连接服务端
        for (; ; ) {
            try {
                ChannelFuture channelFuture = bootstrap.connect(remoteAddress).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                // 当是连接异常时候, 继续循环
                if (e instanceof ConnectException){
                    retryCount--;
                    log.error("NettyTcpClient-发生异常, 信息:", e);
                    ThreadUtil.sleep(15, TimeUnit.SECONDS);
                }else {
                    break;
                }
            }
            // 重试到达100次数则不在重试 , 或者可以自定义次数, 或者可以无限每隔15秒重试一次
            if (retryCount <= 0) {
                break;
            }
        }
        // 关闭线程组
        workerGroup.shutdownGracefully();
    }

上面这种方法很简单, 也有缺点, 有可能客户端第一次启动, 连接地址写错了, 但确实有这个地址, 也可能会报 ConnectException 异常, 也会一直触发重连, 而且由于连接地址是错误的 , 所以会一直连接不上。 所以这种方法不建议使用

如果非要使用, 得配置, 连接失败时候, 会通知开发人员, 例如: 钉钉、 电话、 邮件等通知

2.0 建议使用方式,

这个方式, 本质是已经在建立连接成功后, 使用netty提供的处理器事件, 当通道不活跃的时候触发的事件channelInactive(ChannelHandlerContext ctx) 里面进行重试连接。

2.1.1 重试策略类RetryStrategy

/**
 * @Author: ZhiHao
 * @Date: 2022/6/27 16:31
 * @Description:
 * @Versions 1.0
 **/
@Slf4j
public class RetryStrategy {

    // 启动引导类
    private Bootstrap bootstrap;
    // 最大重试次数
    private Integer retryMaxCount = Integer.MAX_VALUE;
    // 最长间隔重试时间
    private Integer retryMaxTime = 60;
    // 每次重试增加多少秒
    private Integer retryAddTime = 0;
    // 重试成功触发的事件
    private Consumer<Boolean> consumer;
    // 初始重试次数
    private int currentRetryCount = 0;
    // 初始重试间隔 1秒
    private long delay = 1L;

    public Bootstrap buildBootstrapAndReturnBootstrap(Bootstrap bootstrap) {
        this.bootstrap = bootstrap;
        return bootstrap;
    }

    public void setRetryMaxCount(Integer retryMaxCount) {
        this.retryMaxCount = retryMaxCount;
    }

    public void setRetryMaxTime(Integer retryMaxTime) {
        this.retryMaxTime = retryMaxTime;
    }

    public void setRetryAddTime(Integer retryAddTime) {
        this.retryAddTime = retryAddTime;
    }

    public void setConsumer(Consumer<Boolean> consumer) {
        this.consumer = consumer;
    }

    public void processRetryConnect(ChannelHandlerContext ctx) {
        if (Objects.isNull(ctx)){
            log.error("RetryStrategy===处理器都不存在!!!");
            // 关闭整个客户端, 是整个netty应用停止
            bootstrap.config().group().shutdownGracefully();
            return;
        }
        
        if (currentRetryCount >= retryMaxCount) {
            log.error("RetryStrategy===重试已达最大次数, 取消重试, 关闭客户端!!!");
            // 关闭整个客户端, 是整个netty应用停止
            bootstrap.config().group().shutdownGracefully();
            return;
        }
        
 		long delay = this.getDelay();
        EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(() -> {
            try {
                currentRetryCount++;
                long delayTime = this.delay;
                int count = this.currentRetryCount;

                ChannelFuture channelFuture = bootstrap.connect();
                channelFuture.addListener(future -> {
                    // 触发事件
                    boolean futureSuccess = future.isSuccess();
                    // 失败后重调递归调
                    if (!futureSuccess) {
                        this.processRetryConnect(ctx);
                    } else {
                        // 成功后重置次数和延迟时间
                        this.currentRetryCount = 0;
                        this.delay = 0L;
                        Optional.ofNullable(consumer).ifPresent(el -> el.accept(Boolean.TRUE));
                    }
                    log.info("===RetryStrategy===, 重连, 当前次数:{}, 当前延迟重试间隔:{}秒, 重试结果:{}", count, delay, futureSuccess);
                });
                channelFuture.sync();
            } catch (Exception e) {
                log.error("NettyRetryClientHandle====重连失败, 原因: ", e);
            }
        }, delay, TimeUnit.SECONDS);
        // 传递给下一个处理器
        ctx.fireChannelInactive();
    }

    private long getDelay() {
     	if (delay >= retryMaxTime) {
            return retryMaxTime;
        }
        if (currentRetryCount != 0) {
            delay += retryAddTime;
        }
        return delay;
    }
}

2.1.2 自定义重试处理器

@Slf4j
public class NettyRetryClientHandle extends ChannelInboundHandlerAdapter {

    private RetryStrategy retryStrategy;

    public NettyRetryClientHandle(RetryStrategy retryStrategy) {
        this.retryStrategy = retryStrategy;
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.error("NettyRetryClientHandle===触发通道不活跃, 进行重连!");
        retryStrategy.setConsumer(el->{
            log.info("NettyRetryClientHandle===重连成功,  触发做自己的事情!!!!!!!");
        });
        retryStrategy.processRetryConnect(ctx);
    }
}

2.1.3 客户端

@Slf4j
public class NettyRetryClientTwo {

    public static void main(String[] args) {
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        RetryStrategy retryStrategy = new RetryStrategy();
        retryStrategy.setRetryAddTime(1);
        retryStrategy.setRetryMaxCount(3);
        Bootstrap bootstrap = retryStrategy.buildBootstrapAndReturnBootstrap(new Bootstrap());

        bootstrap = bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new NettyTcpClientHandle());
                        // 添加自定义重试连接处理器
                        pipeline.addLast(new NettyRetryClientHandle(retryStrategy));
                    }
                });

        bootstrap.remoteAddress(new InetSocketAddress("127.0.0.1", 8888));
        //bootstrap.remoteAddress(new InetSocketAddress("47.119.137.321", 3306));
        try {
            ChannelFuture channelFuture = bootstrap.connect();
            // 添加监听, 最初启动时候是否成功!
            channelFuture.addListener(el->{
                // 失败则进行重试!
                if (!el.isSuccess()) {
                    ChannelHandlerContext context = channelFuture.channel().pipeline().context(NettyRetryClientHandle.class);
                    retryStrategy.processRetryConnect(context);
                }else {
                    log.error("NettyTcpClient-启动成功了!!!");
                }
            });

            channelFuture.sync().channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("NettyTcpClient-发生异常, 信息:", e);
        }
    }
}

2.1 进行测试, 客户端一启动就报错

##  一直重试到最大次数都没有成功, 关闭客户端!
18:20:03.786 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-启动就连接失败了, 进行重试!!!
18:20:03.786 [main] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-发生异常, 信息:
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:20:06.833 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:20:06.833 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:1, 当前延迟重试间隔:1秒, 重试结果:false
18:20:10.886 [nioEventLoopGroup-2-3] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:2, 当前延迟重试间隔:2秒, 重试结果:false
18:20:10.886 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:20:15.947 [nioEventLoopGroup-2-4] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - RetryStrategy===重试已达最大次数, 取消重试, 关闭客户端!!!
18:20:15.947 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:20:15.948 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:3, 当前延迟重试间隔:3秒, 重试结果:false
### -----------------------------------------------------------------------------------------------------
###  重试成功!
18:29:50.378 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-启动就连接失败了, 进行重试!!!
18:29:50.379 [main] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-发生异常, 信息:
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:29:53.437 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:29:53.437 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:1, 当前延迟重试间隔:1秒, 重试结果:false
18:29:57.488 [nioEventLoopGroup-2-3] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:2, 当前延迟重试间隔:2秒, 重试结果:false
18:29:57.488 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:30:01.007 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:3, 当前延迟重试间隔:3秒, 重试结果:true
18:30:01.021 [nioEventLoopGroup-2-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096
18:30:01.021 [nioEventLoopGroup-2-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
18:30:01.021 [nioEventLoopGroup-2-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.chunkSize: 32
18:30:01.030 [nioEventLoopGroup-2-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
18:30:01.030 [nioEventLoopGroup-2-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
18:30:01.031 [nioEventLoopGroup-2-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@a6737b0
18:30:01.089 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到信息:74e0c2f2-c993-4831-bc62-31f709946538b508fa6f-a884-471c-994b-85179bb21a7128d57d14-a5bc-4205-8476-35d511bcaffa8096b5d0-5f7a-411a-b96d-46a1405c992823f0090e-61d0-423c-80c6-26afc46c359f
18:30:01.089 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到多少次信息:1

2.2 进行测试, 网络波动或者中途断连

18:33:11.642 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-启动成功了!!!
18:33:11.644 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到信息:2e0ad20d-81a4-4a76-8065-259a57e247ff5e587c61-c96a-45d3-81d5-dd69dcd94f1457700452-3074-4bd6-be94-b862842a098104963210-7f46-40eb-9fb1-00a7d02918aa6e43e648-e965-474f-a319-bdf155f4a7bc
18:33:11.644 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到多少次信息:1
18:33:35.399 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientHandle - NettyRetryClientHandle===触发通道不活跃, 进行重连!
18:33:38.451 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:1, 当前延迟重试间隔:1秒, 重试结果:false
18:33:38.452 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:33:42.512 [nioEventLoopGroup-2-3] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:2, 当前延迟重试间隔:2秒, 重试结果:false
18:33:42.512 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
Caused by: java.net.ConnectException: Connection refused: no further information
18:33:46.524 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.NettyRetryClientHandle - NettyRetryClientHandle===重连成功,  触发做自己的事情!!!!!!!
18:33:46.525 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:3, 当前延迟重试间隔:3秒, 重试结果:true
18:33:46.584 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到信息:fe3de0ab-6c65-45bd-8852-0f92805bb23d
18:33:46.584 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到多少次信息:8

总结:

第一种方式使用场景有限!

第二种方式, 可以自定义扩展的情况更全一点!!!

1

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Netty 中,实现客户端重连可以通过以下几个步骤: 1. 创建一个连接管理器类来管理客户端的连接状态。 2. 在连接管理器中,创建一个定时任务,用于定期检查连接状态并重连。 3. 当连接断开时,通过监听器或回调方法得到通知。 4. 在断开的情况下,触发重连逻辑,重新连接到服务器。 下面是一个简单的示例代码,演示了如何在 Netty 客户端实现重连: ```java import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.util.concurrent.TimeUnit; public class ReconnectClient { private final String host; private final int port; private final Bootstrap bootstrap; private final EventLoopGroup eventLoopGroup; private volatile boolean reconnecting; public ReconnectClient(String host, int port) { this.host = host; this.port = port; this.bootstrap = new Bootstrap(); this.eventLoopGroup = new NioEventLoopGroup(); this.reconnecting = false; bootstrap.group(eventLoopGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new MyHandler()); } }); } public void connect() { try { ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { // 处理异常 } finally { if (!reconnecting) { eventLoopGroup.shutdownGracefully(); } } } public void reconnect() { if (reconnecting) { return; } reconnecting = true; eventLoopGroup.schedule(() -> { try { ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { // 处理异常 } finally { reconnecting = false; reconnect(); // 递归调用重新连接 } }, 5, TimeUnit.SECONDS); } public static void main(String[] args) { ReconnectClient client = new ReconnectClient("localhost", 8080); client.connect(); } } class MyHandler extends ChannelInboundHandlerAdapter { @Override public void channelInactive(ChannelHandlerContext ctx) { // 连接断开时的处理逻辑 ReconnectClient client = new ReconnectClient("localhost", 8080); client.reconnect(); } // 其他处理方法... } ``` 上述示例中,`ReconnectClient` 类封装了客户端的连接管理和重连逻辑。在 `connect` 方法中,首次建立连接并等待关闭;当连接断开时,会触发 `channelInactive` 方法,在该方法中调用 `reconnect` 方法进行重连。`reconnect` 方法使用定时任务调度,在一定时间后尝试重新连接,并通过递归调用实现了持续的重连。 这只是一个简单的示例,并未考虑异常处理、连接失败的情况等。在实际应用中,你可能需要根据具体需求进行适当的修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

懵懵懂懂程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值