NIO的channel中什么是 fd(File Descriptor,文件描述符)
一、直接答fd 是 Linux 内核给打开的文件/网络连接分配的整数 IDfd File Descriptor文件描述符——是Linux 内核给每一个打开的文件 / 网络连接 / 设备分配的一个非负整数 ID。一切 I/O文件 / 网络 / 设备都用 fd 操作。核心fd 不是文件本身——是指向内核数据结构的句柄fd 0 标准输入stdinfd 1 标准输出stdoutfd 2 标准错误stderrfd 3 你打开的文件 / 网络连接二、fd 在 Linux 内核的 4 大真相2.1 fd 是非负整数// C 语言Linux 内核系统调用 int fd open(test.txt, O_RDONLY); // 返回 33 是 fd int socket_fd socket(AF_INET, SOCK_STREAM, 0); // 返回 44 是 fd老哥注意fd 是整数0, 1, 2, 3, 4, ...不是对象不是类操作系统自动分配2.2 fd 是进程级的# Linux 进程 fd 列表 # /proc/pid/fd/ 目录里看到进程的所有 fd ls -la /proc/1234/fd/ 0 - /dev/null # 标准输入 1 - /dev/null # 标准输出 2 - /dev/null # 标准错误 3 - /home/test.txt # 用户打开的文件 4 - socket:[12345] # 网络连接注意每个进程都有自己的 fd 表fd 3 在进程 A 和进程 B 是不同的2.3 fd 是一切 I/O 的入口┌─────────────────────────────────────────────┐ │ Linux 内核视角一切 I/O 都是 fd │ ├─────────────────────────────────────────────┤ │ │ │ 文件 I/O → fdopen/read/write │ │ 网络 I/O → fdsocket/accept/send │ │ 设备 I/O → fdopen/read/write │ │ 管道 I/O → fdpipe │ │ 事件通知 → fdepoll │ │ │ │ ⚠️ 一切 I/O 都是 fd │ │ │ └─────────────────────────────────────────────┘2.4 fd 是有限的资源# Linux 默认限制 ulimit -n # 输出 1024默认 1024 个 fd # 修改 ulimit -n 65535 # 改成 65535 个注意单个进程最多打开 1024 个 fd默认每个 TCP 连接占 1 个 fd1w 并发连接 1w 个 fd必须调大 ulimit -n三、fd 在 NIO 中的真实角色老哥最关心3.1 NIO Channel fd 的 Java 包装// Java NIO 底层 ServerSocketChannel channel ServerSocketChannel.open(); // 1. 调用 OS 的 socket() 系统调用 // 2. 拿到一个 fd比如 5 // 3. JDK 把 fd 包装成 ServerSocketChannel 对象 // 真实代码OpenJDK 源码 public static ServerSocketChannel open() throws IOException { return SelectorProvider.provider().openServerSocketChannel(); // 内部调用net.openServerSocketChannel() → SocketDispatcher.open() // → 调 OS 的 socket() 系统调用 → 拿到 fd }关键NIO Channel 不是装多个 BIO老哥之前问的NIO Channel 是 fd 的 Java 包装每个 Channel 1 个 fd3.2 NIO Selector 是 fd 集合的管理器// NIO Selector 真实结构 public abstract class Selector { // 1. 内部维护一个 fd 集合 // 2. 调用 epoll_wait() 系统调用 // 3. 当某个 fd 有事件时回调通知 } // 真实使用 Selector selector Selector.open(); channel.register(selector, SelectionKey.OP_ACCEPT); // 1. channel 的 fd 被加到 selector 内部 // 2. selector 内部维护 epoll fd 集合老哥注意Selector 内部维护一个 fd 集合epoll_wait() 等待 fd 事件不是遍历所有 fd是事件驱动四、fd 4 大经典场景4.1文件 I/O// C 语言打开文件 int fd open(test.txt, O_RDONLY); // 拿到 fd char buf[1024]; read(fd, buf, sizeof(buf)); // 用 fd 读 close(fd); // 关闭 fdJava 对应FileInputStream fis new FileInputStream(test.txt); // 内部调 open() 拿 fd读完调 close() // 老哥用 Java 看不到 fd但 fd 在底层4.2网络 I/O// C 语言TCP 服务端 int server_fd socket(AF_INET, SOCK_STREAM, 0); // 拿到 fd bind(server_fd, ...); listen(server_fd, 5); int client_fd accept(server_fd, ...); // 接受连接拿到新的 fd read(client_fd, buf, sizeof(buf)); close(client_fd);Java NIO 对应ServerSocketChannel serverChannel ServerSocketChannel.open(); // 内部调 socket() 拿 fd // accept() 返回 SocketChannel1 个新 fd4.3事件通知epoll// C 语言epoll 监听多个 fd int epoll_fd epoll_create(1); // 创建 epoll fd epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, event); // 注册 fd epoll_wait(epoll_fd, events, 100, -1); // 等待事件 // 当 server_fd 有事件时epoll_wait() 立即返回Java NIO 对应Selector selector Selector.open(); channel.register(selector, SelectionKey.OP_READ); // 内部调 epoll_ctl()把 fd 加到 epoll selector.select(); // 内部调 epoll_wait()4.4标准 I/O// C 语言标准输入输出 int fd 0; // 标准输入 int fd 1; // 标准输出 int fd 2; // 标准错误老哥注意Java 的System.in fd 0Java 的System.out fd 1Java 的System.err fd 2五、fd 在 NIO 中完整流程┌──────────────────────────────────────────────────┐ │ Java NIO 完整 I/O 流程fd 全程跟踪 │ ├──────────────────────────────────────────────────┤ │ │ │ 1. Channel 创建 │ │ ↓ Java: ServerSocketChannel.open() │ │ ↓ 调 OS: socket() 系统调用 │ │ ↓ 拿到 fd 5 │ │ ↓ JDK 把 fd 包装成 ServerSocketChannel 对象 │ │ │ │ 2. Channel 注册到 Selector │ │ ↓ Java: channel.register(selector, OP_ACCEPT) │ │ ↓ 调 OS: epoll_ctl(ADD, fd5, ...) │ │ ↓ 把 fd5 加到 epoll 监听集合 │ │ │ │ 3. Selector 监听 │ │ ↓ Java: selector.select() │ │ ↓ 调 OS: epoll_wait() │ │ ↓ 阻塞等待 fd5 有事件 │ │ │ │ 4. 新连接到达 │ │ ↓ OS 内核fd5 有 ACCEPT 事件 │ │ ↓ 唤醒 epoll_wait() │ │ ↓ Java: selectedKeys() 返回 SelectionKey │ │ │ │ 5. 接受新连接 │ │ ↓ Java: serverChannel.accept() │ │ ↓ 调 OS: accept() 系统调用 │ │ ↓ 拿到新 fd6客户端连接 │ │ ↓ JDK 把 fd6 包装成 SocketChannel 对象 │ │ ↓ 把 fd6 注册到 selector │ │ │ │ 6. 读数据 │ │ ↓ Java: channel.read(buffer) │ │ ↓ 调 OS: read(fd6, buffer) │ │ ↓ 阻塞读数据 │ │ │ │ 7. 关闭连接 │ │ ↓ Java: channel.close() │ │ ↓ 调 OS: close(fd6) │ │ │ └──────────────────────────────────────────────────┘六、fd 在 NIO 中 4 大核心要点6.1每个连接 1 个 fd1w 个 TCP 连接 ↓ 1w 个 fd0-10000 ↓ BIO1w 个 Socket 对象 1w 个线程 NIO1w 个 Channel 对象 1w 个 fd 1 个 Selector6.2fd 是有限资源# 默认 1024 个 fd / 进程 # 1w 并发必须调大 ulimit -n 65535老哥 Spring Cloud Gateway 实战Linux 必须调大 ulimit -n生产环境一般 65535 或 100 万6.3fd 是 OS 资源// fd 数量 / 进程 // fd 数量 / 系统 // ulimit -n # 进程级 // cat /proc/sys/fs/file-max # 系统级老哥注意fd 数量受 3 层限制硬件内存 / CPUOS系统级 file-max进程ulimit -n6.4fd 是 I/O 的核心抽象Linux 内核视角 - 一切都是文件 - 一切 I/O 都是 fd - 网络连接 fd - 文件 fd - 设备 fd - 管道 fd