What's the best way to reconnect after connection closed in Netty

后端 未结 3 1606
青春惊慌失措
青春惊慌失措 2021-02-01 10:11

Simple scenario:

  1. A lower level class A that extends SimpleChannelUpstreamHandler. This class is the workhorse to send the message and received the response.
相关标签:
3条回答
  • 2021-02-01 11:03

    Channel.closeFuture() returns a ChannelFuture that will notify you when the channel is closed. You can add a ChannelFutureListener to the future in B so that you can make another connection attempt there.

    You probably want to repeat this until the connection attempt succeeds finally:

    private void doConnect() {
        Bootstrap b = ...;
        b.connect().addListener((ChannelFuture f) -> {
            if (!f.isSuccess()) {
                long nextRetryDelay = nextRetryDelay(...);
                f.channel().eventLoop().schedule(nextRetryDelay, ..., () -> {
                    doConnect();
                }); // or you can give up at some point by just doing nothing.
            }
        });
    }
    
    0 讨论(0)
  • 2021-02-01 11:08

    I don't know if this is the right solution but to fix the thread leak of trustin's solution I found I could shutdown the event loop after the scheduler had triggered:

    final EventLoop eventloop = f.channel().eventLoop();
    b.connect().addListener((ChannelFuture f) -> {
        if (!f.isSuccess()) {
            long nextRetryDelay = nextRetryDelay(...);
            eventloop.schedule(() -> {
                doConnect();
                eventloop.shutdownGracefully();
            }, nextRetryDelay, ...);
        }
    });
    
    0 讨论(0)
  • 2021-02-01 11:15

    Here's another version encapsulating the reconnect behavior in a small helper class

    Bootstrap clientBootstrap...
    EventLoopGroup group = new NioEventLoopGroup();
    
    Session session = new Session(clientBootstrap,group);
    Disposable shutdownHook = session.start();    
    
    interface Disposable {
       void dispose();
    }
    class Session implements Disposable{    
        private final EventLoopGroup scheduler;
        private final Bootstrap clientBootstrap;
    
        private int reconnectDelayMs;
        private Channel activeChannel;
        private AtomicBoolean shouldReconnect;
    
        private Session(Bootstrap clientBootstrap, EventLoopGroup scheduler) {
            this.scheduler = scheduler;
            this.clientBootstrap = clientBootstrap;
            this.reconnectDelayMs = 1;
            this.shouldReconnect = new AtomicBoolean(true);
        }
    
        public Disposable start(){
            //Create a new connectFuture
            ChannelFuture connectFuture = clientBootstrap.connect();
    
            connectFuture.addListeners( (ChannelFuture cf)->{
                if(cf.isSuccess()){
                    L.info("Connection established");
                    reconnectDelayMs =1;                    
                    activeChannel = cf.channel();
    
                    //Listen to the channel closing
                    var closeFuture =activeChannel.closeFuture();
                    closeFuture.addListeners( (ChannelFuture closeFut)->{
                        if(shouldReconnect.get()) {
                            activeChannel.eventLoop().schedule(this::start, nextReconnectDelay(), TimeUnit.MILLISECONDS);
                        }
                        else{
                            L.info("Session has been disposed won't reconnect");
                        }
                    });
                }
                else{
                    int delay =nextReconnectDelay();
                    L.info("Connection failed will re-attempt in {} ms",delay);
                    cf.channel().eventLoop().schedule(this::start,delay , TimeUnit.MILLISECONDS);
                }
            });
            
            return this;
        }
    
        /**
         * Call this to end the session
         */
        @Override
        public void dispose() {
            try {
                shouldReconnect.set(false);
                scheduler.shutdownGracefully().sync();
                if(activeChannel !=null) {
                    activeChannel.closeFuture().sync();
                }
            }catch(InterruptedException e){
                L.warn("Interrupted while shutting down TcpClient");
            }
        }
    
        private int nextReconnectDelay(){
            this.reconnectDelayMs = this.reconnectDelayMs*2;
            return Math.min(this.reconnectDelayMs, 5000);
        }
    }
    
    0 讨论(0)
提交回复
热议问题