netty重连

101 阅读1分钟

netty重连

注意点

netty连接时,如果用eventLoop线程,那么会有资源耗尽问题,就是重连操作几次后,就不会再进行重连操作。如果想一直进行重连操作,可以用独立的ScheduledExecutorService

非eventloop线程连接

微信图片_20230925194240.jpg 错误提示:关闭了这个注册的channel,因为需要eventloop线程来做注册功能,但是这次的绑定并不是。 你也可以从图中的堆栈错误信息,点进去看源码,有个验证线程是否是eventloop,如果不是,就报这个错误:Zforce-closing a channel ....

重连代码

@Slf4j
public class NettyClient {
    private NioEventLoopGroup group;
    private Bootstrap bootstrap;
    private SocketAddress address;
    
    // 定义一个独立的scheduled线程来处理重连,这样可以一直进行重连操作。如果eventLoop,会有资源耗尽问题,资源耗尽就不会再进行重连操作
    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    
    private NettyClient(){}
    
    public void start(String host, int port) {
        address = new InetSocketAddress(host, port);
        group = new NioEventLoopGroup();
        // 编解码器
        MessageCodec codec = new MessageCodec();
        bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.group(group);
        bootstrap.handler(new ChannelInitializer<SocketChannel>(){
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // ProtocolFrameDecoder是粘包处理器
                ch.pipeline().addLast(new ProtocolFrameDecoder());
                ch.pipeline().addLast(codec);
                ch.pipeline().addLast("client handler", new ClientHandler());
            }
        });
        connection();
    }
    
    public void connection() {
        InetSocketAddress socketAddress = (InetSocketAddress) address;
        ChannelFuture channelFuture = bootstrap.connect(address);
        // 添加一个监听,失败的话,继续connect
        channelFuture.addListener(future->{
            if(!future.isSUccess()){
                scheduledExecutorService.schedule(()->{
                    log.info("与服务器失去连接,正在重连 host:{},port:{}", socketAddress.getHostName(), socketAddress.getPort());
                    connect();
                }, 30L, TimeUnit.SECONDS);
            }else{
                log.info("连接成功 host:{},port:{}", socketAddress.getHostName(), socketAddress.getPort());
                Channel channel = ((DefaultChannelPromise) future).channel();
                NettySession.bind(channel);
            }
        });
        try{
            channelFuture.channel().closeFuture().sync();
        }catch(InterruptedException e){
            throw new RuntimeException(e);
        }
    }
    
    public static NettyClient getInstance(){ return LazyHolder.NETTY_CLIENT;}
    
    public static class LayzHolder{
        private static final NettyClient NETTY_CLIENT = new NettyClient();
    }
}

@Slf4j
public class Nettysession{
    private static volatile Channel serverChannel;
    
    public static Channel getChannel(){
        for(;;){
            if(serverChannel != null && serverChannel.isActive()){
                return serverChannel;
            }
        }
    }
    
    public static void disposeChannel(ChannelHandlerContext ctx, ChannelHandler handler){
        ctx.pipeline().remove(handler);
        ctx.channel().close();
        NettySession.serverChannel = null;
    }
    
    public static void bind(Channel serverChannel){
        NettySession.serverChannel = serverChannel;
    }
}

自定义心跳代码

@Slf4j
public class NettySession{
    public static void ping(Long heartbeat){
        ScheduledFuture<?> schedule = getChannel().eventLoop().schedule(()->{
            // 组装心跳数据
            HeartbeatMessage message = new HeartbeatMessage();
            getChannel().writeAndFlush(message)
        }, heartbeat, TimeUnit.SECONDS);
        schedule.addListener(future->{
            if(future.isSuccess()){
                ping(heartbeat)
            }
        });
    }
}