Netty从0到1系列之CloseFuture


推荐阅读:

【01】Netty从0到1系列之I/O模型
【02】Netty从0到1系列之NIO
【03】Netty从0到1系列之Selector
【04】Netty从0到1系列之Channel
【05】Netty从0到1系列之Buffer(上)
【06】Netty从0到1系列之Buffer(下)
【07】Netty从0到1系列之零拷贝技术
【08】Netty从0到1系列之整体架构、入门程序
【09】Netty从0到1系列之EventLoop
【10】Netty从0到1系列之EventLoopGroup
【11】Netty从0到1系列之Future
【12】Netty从0到1系列之Promise
【13】Netty从0到1系列之Netty Channel
【14】Netty从0到1系列之ChannelFuture


一、CloseFuture

1.1 CloseFuture是啥?

CloseFuture 是 Netty 中一种特殊的 ChannelFuture,专门用于表示 Channel 关闭操作的异步结果。它允许开发者监听通道关闭事件,并在关闭完成时执行清理操作或状态更新。

CloseFuture 是 Channel 生命周期管理的关键组件,确保了资源释放和状态同步的可靠性。

CloseFuture 的核心特性:

  • 关闭状态监控:提供通道关闭状态的查询和监控
  • 异步通知机制:支持添加监听器在关闭完成时接收回调
  • 线程安全:可以在任何线程中安全地添加监听器或检查状态
  • 不可逆操作:一旦通道开始关闭,过程不可逆
Channel 创建
获取 CloseFuture
连接活跃
连接关闭?
CloseFuture 完成
通知所有监听器
执行清理逻辑

🌟 核心职责

  • 表示 Channel 关闭事件的完成
  • 提供连接关闭后的回调机制
  • 用于优雅关闭资源
  • 是 Channel 生命周期的“终点站”

💡 关键特性

  • 每个 Channel 都有一个唯一的 closeFuture()
  • Future 不可写(不能手动设置成功/失败)
  • 只能由 Netty 内部在 Channel 关闭时自动完成
  • 一旦 Channel 关闭,CloseFuture 立即变为 success

CloseFutuer与Channel关闭流程的关系

用户代码 Channel CloseFuture EventLoop 调用close()方法 提交关闭任务 返回CloseFuture 执行关闭操作 更新关闭状态 通知监听器或唤醒等待线程 用户代码 Channel CloseFuture EventLoop

1.2 CloseFuture 的继承体系与核心方法

Future<Void>
    -> ChannelFuture
        -> CloseFuture (ChannelFuture 的特殊类型)

在这里插入图片描述

CloseFuture 是 ChannelFuture 的一种特殊形式,专门用于处理通道关闭操作。它继承了 ChannelFuture 的所有方法,并提供了一些关闭特定的行为。

底层实现

  • CloseFutureChannel 内部的一个私有 ChannelFuture 实例
  • 当调用 channel.close() 或连接被对端关闭时,Netty 内部会触发 closeFuture().setSuccess()
  • 外部无法手动修改其状态

为了更好地理解 CloseFuture 在通道生命周期中的作用,请看下面的状态图:

通道活跃
调用close()方法
开始关闭流程
关闭完成
关闭失败
重试关闭
通道完全销毁
CHANNEL_ACTIVE
CLOSE_INITIATED
CLOSING
CLOSED
CLOSE_FAILED
Channel.close()返回CloseFuture
添加的监听器会在关闭完成时被调用

CloseFuture 的生命周期与 Channel 的关闭过程紧密相关

Channel创建时
Channel成功关闭
Channel关闭失败
PENDING
SUCCESS
FAILURE

1.3 CloseFuture 的核心 API 与用法

1.3.1 基础CloseFuture使用

package cn.tcmeta.demo06;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: CloseFuture基础使用
 * @version: 1.0.0
 */
@Slf4j
public class BasicCloseFutureExample {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(2);

        try{
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // 简单处理一下
                        }
                    });

            // 绑定端口并启动服务器, 阻塞的方式启动服务器
            Channel channel = bootstrap.bind(8080).sync().channel();
            log.info("🚀 服务器启动,监听 8080 端口 ~~~~");

            // 模拟运行一段时间
            try {
                TimeUnit.MILLISECONDS.sleep(5000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            // 开头Channel
            ChannelFuture closeFuture = channel.close();
            log.info("🚀 关闭Future开始,此时【尚未关闭】:{}", closeFuture);

            // 检查关闭状态
            log.info("🐦‍🔥 关闭是否完成:{}", closeFuture.isDone());
            log.info("🐦‍🔥 关闭是否成功:{}", closeFuture.isSuccess());

            // 同步等待关闭完成
            closeFuture.sync();
            log.info("🚀 关闭Future结束,此时【已关闭】:{}", closeFuture);

        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();

            // 等待EventLoopGroup完全关闭
            bossGroup.awaitTermination(5, TimeUnit.SECONDS);
            workerGroup.awaitTermination(5, TimeUnit.SECONDS);
        }
    }
}

在这里插入图片描述

1.3.2 使用监听器处理关闭事件

package cn.tcmeta.demo06;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

public class CloseFutureListenerExample {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            // 添加处理器
                        }
                    });

            // 连接到服务器
            Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
            System.out.println("连接到服务器");

            // 1. 获取通道的CloseFuture
            ChannelFuture closeFuture = channel.closeFuture();

            // 2. 添加关闭监听器
            closeFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
                @Override
                public void operationComplete(Future<? super Void> future) throws Exception {
                    if (future.isSuccess()) {
                        System.out.println("通道关闭成功");
                    } else {
                        System.err.println("通道关闭失败: " + future.cause().getMessage());
                    }

                    // 可以在这里执行清理操作
                    performCleanup();
                }
            });

            // 3. 使用lambda表达式添加多个监听器
            closeFuture.addListener(future -> {
                System.out.println("第一个监听器: 关闭完成");
            }).addListener(future -> {
                System.out.println("第二个监听器: 执行额外清理");
                releaseResources();
            });

            // 模拟运行一段时间
            Thread.sleep(3000);

            // 4. 主动关闭通道
            System.out.println("准备关闭通道");
            channel.close().sync();
            System.out.println("主线程: 通道已关闭");

        } finally {
            group.shutdownGracefully();
        }
    }

    private static void performCleanup() {
        System.out.println("执行清理操作...");
        // 例如: 关闭数据库连接、释放文件句柄等
    }

    private static void releaseResources() {
        System.out.println("释放资源...");
        // 例如: 释放内存、清除缓存等
    }
}

1.3.3 在 ChannelHandler 中处理关闭事件

package cn.tcmeta.demo06;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

public class CloseAwareHandler extends ChannelInboundHandlerAdapter {
    private volatile boolean channelClosed = false;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("通道已激活: " + ctx.channel());
        // 获取CloseFuture并添加监听器
        ctx.channel().closeFuture().addListener(future -> {
            channelClosed = true;
            System.out.println("通道已关闭,清理处理器状态");
            cleanup();
        });

        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (channelClosed) {
            System.err.println("警告: 尝试在已关闭的通道上读取数据");
            return;
        }
        // 处理数据
        System.out.println("收到数据: " + msg);
        // 模拟业务处理
        processMessage(msg);
        super.channelRead(ctx, msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.err.println("处理异常: " + cause.getMessage());

        // 发生异常时关闭通道
        ctx.close().addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("因异常关闭通道成功");
            } else {
                System.err.println("因异常关闭通道失败: " + future.cause().getMessage());
            }
        });
    }

    private void processMessage(Object msg) {
        // 模拟消息处理
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void cleanup() {
        // 清理处理器持有的资源
        System.out.println("清理处理器资源");
    }
}

1.4 CloseFuture的底层实现原理

✅ 1. 自动完成机制

  • CloseFuture 的完成由 Netty 内部自动触发
  • 触发时机:
    • 调用 channel.close()
    • 对端关闭连接(FIN 包)
    • 连接异常中断
  • 触发后,CloseFuture 状态变为 success,并通知所有监听器
// 伪代码:Netty 内部实现
void closeChannel() {
    // ... 关闭资源
    closeFuture.setSuccess(null); // 自动完成
}

✅ 2. 不可变性与只读性

  • CloseFuture只读的
  • 无法通过 addListener() 之外的方式修改其状态
  • 保证了连接关闭事件的唯一性和可靠性

✅ 3. 与 EventLoop 的协同

  • 所有 CloseFuture 的监听器在 Channel 绑定的 EventLoop 线程 中执行
  • 保证无锁串行化,避免竞态条件

1.4.1 CloseFuture 与 Channel 生命周期管理

CloseFuture 的实现与 Channel 的生命周期紧密相关。当调用 Channel.close() 时,Netty 会启动一个复杂的关闭流程:

// 简化的关闭流程实现原理
public abstract class AbstractChannel implements Channel {
    private final CloseFuture closeFuture = new CloseFuture(this);
    
    @Override
    public ChannelFuture close() {
        // 1. 检查是否已开始关闭
        if (closeFuture.isDone()) {
            return closeFuture;
        }
        
        // 2. 标记关闭开始
        beginClose();
        
        // 3. 执行实际的关闭操作(异步)
        doClose();
        
        // 4. 返回CloseFuture,允许调用者监听关闭完成
        return closeFuture;
    }
    
    protected abstract void doClose() throws Exception;
    
    // 当实际关闭完成时调用
    protected final void closeComplete() {
        // 标记CloseFuture为成功完成
        closeFuture.setSuccess();
        
        // 通知所有监听器
        notifyAllListeners();
        
        // 执行后续清理操作
        performPostCloseCleanup();
    }
    
    // 当关闭失败时调用
    protected final void closeFailed(Throwable cause) {
        // 标记CloseFuture为失败
        closeFuture.setFailure(cause);
        
        // 通知所有监听器
        notifyAllListeners();
        
        // 可能需要重试或执行错误处理
        handleCloseFailure(cause);
    }
}

1.4.2 CloseFuture 的线程安全实现

CloseFuture 需要确保在多线程环境下的线程安全性:

// 简化的线程安全实现
public class CloseFuture extends DefaultChannelFuture {
    private final Object lock = new Object();
    private volatile boolean closing = false;
    
    public CloseFuture(Channel channel) {
        super(channel);
    }
    
    @Override
    public boolean isDone() {
        // 使用volatile变量确保可见性
        return super.isDone() || closing;
    }
    
    public boolean beginClose() {
        synchronized (lock) {
            if (isDone()) {
                return false; // 已经关闭或正在关闭
            }
            closing = true;
            return true;
        }
    }
    
    @Override
    public ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener) {
        // 如果已经完成,立即通知监听器
        if (isDone()) {
            try {
                listener.operationComplete(this);
            } catch (Exception e) {
                // 处理监听器异常
            }
            return this;
        }
        
        // 否则添加到监听器列表
        return super.addListener(listener);
    }
}

1.5🌰 最佳实践

1.5.1 资源清理与状态管理最佳实践

✅ 推荐实践

实践说明
用于优雅关闭等待所有连接关闭后再关闭服务器
资源清理在监听器中释放 ByteBuf、数据库连接等
状态监控监控连接存活状态
避免在 ChannelInboundHandler中阻塞 sync()除非是顶层处理器
package cn.tcmeta.demo06;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelId;
import io.netty.util.AttributeKey;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ResourceCleanupManager {
    // 使用AttributeKey在Channel上存储资源引用
    private static final AttributeKey<DatabaseConnection> DB_CONN_KEY =
            AttributeKey.valueOf("databaseConnection");
    private static final AttributeKey<FileHandle> FILE_HANDLE_KEY =
            AttributeKey.valueOf("fileHandle");

    // 全局Channel跟踪
    private static final ConcurrentMap<ChannelId, Channel> activeChannels =
            new ConcurrentHashMap<>();

    // 通道激活时调用
    public static void channelActive(Channel channel) {
        activeChannels.put(channel.id(), channel);

        // 初始化通道关联的资源
        DatabaseConnection dbConn = createDatabaseConnection();
        FileHandle fileHandle = openFileHandle();

        channel.attr(DB_CONN_KEY).set(dbConn);
        channel.attr(FILE_HANDLE_KEY).set(fileHandle);

        // 添加关闭监听器进行资源清理
        channel.closeFuture().addListener((ChannelFutureListener) future -> {
            cleanupChannelResources(channel);
            activeChannels.remove(channel.id());
        });
    }

    // 清理通道关联的资源
    private static void cleanupChannelResources(Channel channel) {
        System.out.println("清理通道资源: " + channel.id());

        // 获取并关闭数据库连接
        DatabaseConnection dbConn = channel.attr(DB_CONN_KEY).getAndSet(null);
        if (dbConn != null) {
            try {
                dbConn.close();
            } catch (Exception e) {
                System.err.println("关闭数据库连接失败: " + e.getMessage());
            }
        }

        // 获取并关闭文件句柄
        FileHandle fileHandle = channel.attr(FILE_HANDLE_KEY).getAndSet(null);
        if (fileHandle != null) {
            try {
                fileHandle.close();
            } catch (Exception e) {
                System.err.println("关闭文件句柄失败: " + e.getMessage());
            }
        }
    }

    // 关闭所有活跃通道
    public static void shutdownAll() {
        for (Channel channel : activeChannels.values()) {
            if (channel.isActive()) {
                channel.close().addListener(future -> {
                    if (!future.isSuccess()) {
                        System.err.println("关闭通道失败: " + channel.id());
                    }
                });
            }
        }
    }

    // 模拟资源类
    static class DatabaseConnection implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("数据库连接已关闭");
        }
    }

    static class FileHandle implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("文件句柄已关闭");
        }
    }

    private static DatabaseConnection createDatabaseConnection() {
        return new DatabaseConnection();
    }

    private static FileHandle openFileHandle() {
        return new FileHandle();
    }
}

1.5.2 优雅关闭最佳实践

package cn.tcmeta.demo06;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class GracefulShutdownExample {
    private static final AtomicInteger activeConnections = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            activeConnections.incrementAndGet();

                            ch.closeFuture().addListener(future -> {
                                activeConnections.decrementAndGet();
                                System.out.println("连接关闭,当前活跃连接: " + activeConnections.get());
                            });
                        }
                    });

            Channel serverChannel = bootstrap.bind(8080).sync().channel();
            System.out.println("服务器启动成功");

            // 添加JVM关闭钩子
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                System.out.println("收到关闭信号,开始优雅关闭");

                // 1. 先关闭服务器通道,停止接受新连接
                serverChannel.close();

                // 2. 等待所有活跃连接处理完毕或超时
                long startTime = System.currentTimeMillis();
                long timeout = 30000; // 30秒超时

                while (activeConnections.get() > 0) {
                    long elapsed = System.currentTimeMillis() - startTime;
                    if (elapsed >= timeout) {
                        System.err.println("优雅关闭超时,强制关闭剩余连接");
                        break;
                    }

                    try {
                        Thread.sleep(1000);
                        System.out.println("等待连接关闭,剩余: " + activeConnections.get());
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }

                // 3. 关闭EventLoopGroup
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();

                try {
                    bossGroup.awaitTermination(10, TimeUnit.SECONDS);
                    workerGroup.awaitTermination(10, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

                System.out.println("服务器完全关闭");
            }));

            // 等待服务器通道关闭
            serverChannel.closeFuture().sync();

        } finally {
            // 正常关闭流程
            if (!bossGroup.isShutdown()) {
                bossGroup.shutdownGracefully();
            }
            if (!workerGroup.isShutdown()) {
                workerGroup.shutdownGracefully();
            }
        }
    }
}

⚠️ 常见错误

// ❌ 错误:在 ChannelHandler 中阻塞 sync(),可能导致死锁
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ctx.channel().closeFuture().sync(); // ❌ 危险!
}

// ✅ 正确:使用 addListener
ctx.channel().closeFuture().addListener(f -> {
    // 执行清理
});

1.6 CloseFutuer优缺点总结

✅ 优点

优点说明
连接关闭的可靠通知无论何种方式关闭,都能收到通知
支持优雅关闭实现服务平滑下线
资源清理保障确保连接相关资源被释放
线程安全监听器在正确线程执行
简化生命周期管理统一处理连接终结事件

❌ 缺点

维度说明
核心思想连接关闭的“终结通知”机制
关键技术ChannelFuture、事件驱动、自动完成
核心价值优雅关闭、资源清理、生命周期管理
设计精髓“连接终将关闭,但我们可以做好准备”
适用场景服务端优雅关闭、客户端等待退出、资源清理

1.7 CloseFutuer的核心价值

维度说明
核心思想连接关闭的“终结通知”机制
关键技术ChannelFuture、事件驱动、自动完成
核心价值优雅关闭、资源清理、生命周期管理
设计精髓“连接终将关闭,但我们可以做好准备”
适用场景服务端优雅关闭、客户端等待退出、资源清理

1.8 一句话总结

💡 一句话总结

CloseFuture 是 Netty 的“生命终结信使” ——

  • 它通过 自动完成机制监听器模式,确保在连接关闭时能够可靠地执行清理和通知
  • 是实现高可用、优雅退出网络应用的关键组件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值