在Netty连接关闭后重新连接的最佳方式是什么

时间:2013-11-02 04:50:22

标签: java netty

简单方案:

  1. 扩展SimpleChannelUpstreamHandler的较低级别的A类。这个类是发送消息并收到响应的主力。
  2. 顶级B类,系统的其他部分可以使用它来发送和接收消息(可以同时模拟同步和异步)。此类创建ClientBootstrap,设置管道工厂,调用bootstrap.connect()并最终获得A类的句柄/引用,用于发送和接收消息。类似的东西:

    ChannelFuture future = bootstrap.connect();
    Channel channel = future.awaitUninterruptibly().getChannel();
    

    处理程序= channel.getPipeline()。get(A.class);

  3. 我知道在A班,我可以覆盖     public void channelClosed(ChannelHandlerContext ctx,ChannelStateEvent e); 这样当远程服务器关闭时,我会收到通知。

    由于在关闭通道之后,B类中的原始类A引用(上面的处理程序)不再有效,因此我需要用新引用替换它。

    理想情况下,我希望类A具有在上面覆盖的channelClosed方法中通知B类的机制,以便可以在B类中再次调用bootstrap.connect。一种方法是在A类中引用一个为此,我需要将B类引用传递给PipelineFactory,然后让PipelineFactory将B的引用传递给A。

    任何其他更简单的方法来实现同样的目标?

    感谢,

4 个答案:

答案 0 :(得分:15)

Channel.closeFuture()会返回ChannelFuture,会在频道关闭时通知您。您可以在B中为将来添加ChannelFutureListener,以便您可以在那里进行另一次连接尝试。

您可能希望重复此操作,直到最后连接尝试成功:

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.
        }
    });
}

答案 1 :(得分:0)

我经过长时间尝试使其手动执行。线程是另一种野兽。无论如何,这就是我如何应对这一挑战。

这是客户:

import java.util.logging.Logger;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyClient implements Runnable {

    Logger logger = Logger.getLogger(NettyClient.class.getName());

    boolean done = false;

    public void run() {

        String host = "10.99.1.249";
        int port = 7973;
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.handler(new ChannelInitializer<SocketChannel>() {

                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new StringEncoder(), new StringDecoder(),
                            new DelimiterBasedFrameDecoder(8192, false, Unpooled.wrappedBuffer("</tnt>".getBytes())),
                            new ClientHandler(getClient()));
                }
            });

            ChannelFuture f = b.connect(host, port).sync();
            Channel ch = f.channel();

            ChannelFuture f5 = b.bind(ch.localAddress());

            while(!done) {
                Thread.sleep(5000);
            }
            System.out.println("Done.");
        }
        catch (InterruptedException e) {
            logger.info(e.getMessage());;
        }
        finally {
            workerGroup.shutdownGracefully();
        }
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    private NettyClient getClient() {
        return this;
    }
}

这是对应的ClientHandler,它完成重新连接的实际工作:

import java.net.SocketAddress;
import java.util.logging.Logger;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ClientHandler extends ChannelInboundHandlerAdapter {

    Logger logger = Logger.getLogger(ClientHandler.class.getName());

    private NettyClient client;

    public ClientHandler(NettyClient client) {
        super();
        this.client = client;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel ch = ctx.channel();
        ChannelFuture f2 = ch.writeAndFlush("</tnt>");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // invoked when there is a problem connecting to remote server; needs to be enhanced to properly handle thread shutdown
        client.setDone(true);
        SocketAddress sa = ctx.channel().remoteAddress();
        while(true) {
            try {
                NettyClient nc = new NettyClient();
                nc.run();
                break;
            } catch(Exception ex) {
                logger.info("Reconnecting...");
            }
            Thread.sleep(5000);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // incoming notifications are handled here
        System.out.println((String)msg);
    }

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

实际的重新连接由channelInactive()方法处理。

答案 2 :(得分:0)

这是将重新连接行为封装在一个小的帮助程序类中的另一个版本

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);
    }
}

答案 3 :(得分:0)

我不知道这是否是正确的解决方案,但为了修复 trustin 解决方案的线程泄漏,我发现我可以在调度程序触发后关闭事件循环:

final EventLoop eventloop = f.channel().eventLoop();
b.connect().addListener((ChannelFuture f) -> {
    if (!f.isSuccess()) {
        long nextRetryDelay = nextRetryDelay(...);
        eventloop.schedule(() -> {
            doConnect();
            eventloop.shutdownGracefully();
        }, nextRetryDelay, ...);
    }
});