Java I/O 全解:从 BIO、NIO、AIO 到多路复用与 Netty
关键词:BIO、NIO、AIO、select/poll/epoll/kqueue/IOCP、多路复用、Reactor、Netty
📑 目录
- [一、Java I/O 体系总览](#一、Java I/O 体系总览)
- [二、BIO:阻塞 I/O](#二、BIO:阻塞 I/O)
- [2.1 工作原理](#2.1 工作原理)
- [2.2 源码示例:BIO 服务端 + 客户端](#2.2 源码示例:BIO 服务端 + 客户端)
- [2.3 BIO 的致命问题](#2.3 BIO 的致命问题)
- [三、NIO:非阻塞 I/O + 多路复用](#三、NIO:非阻塞 I/O + 多路复用)
- [3.1 三大核心:Buffer / Channel / Selector](#3.1 三大核心:Buffer / Channel / Selector)
- [3.2 源码示例:NIO 多路复用服务端](#3.2 源码示例:NIO 多路复用服务端)
- [四、AIO:异步 I/O](#四、AIO:异步 I/O)
- [4.1 原理与适用平台](#4.1 原理与适用平台)
- [4.2 源码示例:AIO(AsynchronousChannel)服务端](#4.2 源码示例:AIO(AsynchronousChannel)服务端)
- [4.3 为什么 Linux 下 AIO 几乎被弃用](#4.3 为什么 Linux 下 AIO 几乎被弃用)
- 五、多路复用原理:从操作系统层面讲透
- [5.1 为什么需要「多路复用」](#5.1 为什么需要「多路复用」)
- [5.2 select(Linux/macOS 通用)](#5.2 select(Linux/macOS 通用))
- [5.3 poll(Linux/macOS 通用)](#5.3 poll(Linux/macOS 通用))
- [5.4 epoll(Linux 专属,终极方案)](#5.4 epoll(Linux 专属,终极方案))
- [5.5 kqueue(macOS / FreeBSD)](#5.5 kqueue(macOS / FreeBSD))
- [5.6 IOCP(Windows 专属)](#5.6 IOCP(Windows 专属))
- [5.7 五种机制对照表](#5.7 五种机制对照表)
- [六、从 NIO 到 Netty:为什么要造轮子](#六、从 NIO 到 Netty:为什么要造轮子)
- [6.1 原生 NIO 的痛点](#6.1 原生 NIO 的痛点)
- [6.2 Netty 是怎么解决的](#6.2 Netty 是怎么解决的)
- [七、Netty 核心原理](#七、Netty 核心原理)
- [7.1 主从 Reactor 线程模型](#7.1 主从 Reactor 线程模型)
- [7.2 EventLoop 与 Pipeline](#7.2 EventLoop 与 Pipeline)
- [7.3 ByteBuf:替代 ByteBuffer](#7.3 ByteBuf:替代 ByteBuffer)
- [八、Netty 实战示例:Echo 服务端 + 客户端](#八、Netty 实战示例:Echo 服务端 + 客户端)
- [8.1 pom.xml 依赖](#8.1 pom.xml 依赖)
- [8.2 Netty Echo Server](#8.2 Netty Echo Server)
- [8.3 Netty Echo Client](#8.3 Netty Echo Client)
- [8.4 运行结果](#8.4 运行结果)
- 九、全景对比与选型建议
- 十、总结
一、Java I/O 体系总览
Java 的 I/O 体系经历了三个阶段:
| 模型 | 引入版本 | 核心思想 | 典型 API |
|---|---|---|---|
| BIO (Blocking I/O) | JDK 1.0 | 一个连接一个线程,连接阻塞 | ServerSocket / Socket |
| NIO (Non-blocking I/O) | JDK 1.4 | 多路复用,一个线程管多个连接 | Channel / Selector / Buffer |
| AIO (Asynchronous I/O) | JDK 7 | 真正异步,回调通知 | AsynchronousServerSocketChannel |
一句话演进:BIO 用线程换连接 → NIO 用选择器换线程 → AIO 用回调换等待。
#mermaid-svg-zpWUCWkg1XWEylL5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zpWUCWkg1XWEylL5 .error-icon{fill:#552222;}#mermaid-svg-zpWUCWkg1XWEylL5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zpWUCWkg1XWEylL5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zpWUCWkg1XWEylL5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zpWUCWkg1XWEylL5 .marker.cross{stroke:#333333;}#mermaid-svg-zpWUCWkg1XWEylL5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zpWUCWkg1XWEylL5 p{margin:0;}#mermaid-svg-zpWUCWkg1XWEylL5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zpWUCWkg1XWEylL5 .cluster-label text{fill:#333;}#mermaid-svg-zpWUCWkg1XWEylL5 .cluster-label span{color:#333;}#mermaid-svg-zpWUCWkg1XWEylL5 .cluster-label span p{background-color:transparent;}#mermaid-svg-zpWUCWkg1XWEylL5 .label text,#mermaid-svg-zpWUCWkg1XWEylL5 span{fill:#333;color:#333;}#mermaid-svg-zpWUCWkg1XWEylL5 .node rect,#mermaid-svg-zpWUCWkg1XWEylL5 .node circle,#mermaid-svg-zpWUCWkg1XWEylL5 .node ellipse,#mermaid-svg-zpWUCWkg1XWEylL5 .node polygon,#mermaid-svg-zpWUCWkg1XWEylL5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zpWUCWkg1XWEylL5 .rough-node .label text,#mermaid-svg-zpWUCWkg1XWEylL5 .node .label text,#mermaid-svg-zpWUCWkg1XWEylL5 .image-shape .label,#mermaid-svg-zpWUCWkg1XWEylL5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-zpWUCWkg1XWEylL5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zpWUCWkg1XWEylL5 .rough-node .label,#mermaid-svg-zpWUCWkg1XWEylL5 .node .label,#mermaid-svg-zpWUCWkg1XWEylL5 .image-shape .label,#mermaid-svg-zpWUCWkg1XWEylL5 .icon-shape .label{text-align:center;}#mermaid-svg-zpWUCWkg1XWEylL5 .node.clickable{cursor:pointer;}#mermaid-svg-zpWUCWkg1XWEylL5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zpWUCWkg1XWEylL5 .arrowheadPath{fill:#333333;}#mermaid-svg-zpWUCWkg1XWEylL5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zpWUCWkg1XWEylL5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zpWUCWkg1XWEylL5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zpWUCWkg1XWEylL5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zpWUCWkg1XWEylL5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zpWUCWkg1XWEylL5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zpWUCWkg1XWEylL5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zpWUCWkg1XWEylL5 .cluster text{fill:#333;}#mermaid-svg-zpWUCWkg1XWEylL5 .cluster span{color:#333;}#mermaid-svg-zpWUCWkg1XWEylL5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zpWUCWkg1XWEylL5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zpWUCWkg1XWEylL5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-zpWUCWkg1XWEylL5 .icon-shape,#mermaid-svg-zpWUCWkg1XWEylL5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zpWUCWkg1XWEylL5 .icon-shape p,#mermaid-svg-zpWUCWkg1XWEylL5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zpWUCWkg1XWEylL5 .icon-shape .label rect,#mermaid-svg-zpWUCWkg1XWEylL5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zpWUCWkg1XWEylL5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zpWUCWkg1XWEylL5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zpWUCWkg1XWEylL5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} BIO(1996)
一连接一线程
NIO(2002)
多路复用
一线程管多连接
AIO(2011)
回调通知
不阻塞不轮询
Netty(2008)
封装NIO
生产级框架
二、BIO:阻塞 I/O
2.1 工作原理
BIO 的模型非常直白:
- 服务端
ServerSocket.accept()阻塞,直到有客户端连接进来; - 每来一个连接,新建一个线程专门处理它;
- 线程在读 / 写时也会阻塞,直到数据到达或对端关闭。
#mermaid-svg-4wNZCIi6kfvZ54k1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4wNZCIi6kfvZ54k1 .error-icon{fill:#552222;}#mermaid-svg-4wNZCIi6kfvZ54k1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4wNZCIi6kfvZ54k1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .marker.cross{stroke:#333333;}#mermaid-svg-4wNZCIi6kfvZ54k1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4wNZCIi6kfvZ54k1 p{margin:0;}#mermaid-svg-4wNZCIi6kfvZ54k1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .cluster-label text{fill:#333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .cluster-label span{color:#333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .cluster-label span p{background-color:transparent;}#mermaid-svg-4wNZCIi6kfvZ54k1 .label text,#mermaid-svg-4wNZCIi6kfvZ54k1 span{fill:#333;color:#333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .node rect,#mermaid-svg-4wNZCIi6kfvZ54k1 .node circle,#mermaid-svg-4wNZCIi6kfvZ54k1 .node ellipse,#mermaid-svg-4wNZCIi6kfvZ54k1 .node polygon,#mermaid-svg-4wNZCIi6kfvZ54k1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4wNZCIi6kfvZ54k1 .rough-node .label text,#mermaid-svg-4wNZCIi6kfvZ54k1 .node .label text,#mermaid-svg-4wNZCIi6kfvZ54k1 .image-shape .label,#mermaid-svg-4wNZCIi6kfvZ54k1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-4wNZCIi6kfvZ54k1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4wNZCIi6kfvZ54k1 .rough-node .label,#mermaid-svg-4wNZCIi6kfvZ54k1 .node .label,#mermaid-svg-4wNZCIi6kfvZ54k1 .image-shape .label,#mermaid-svg-4wNZCIi6kfvZ54k1 .icon-shape .label{text-align:center;}#mermaid-svg-4wNZCIi6kfvZ54k1 .node.clickable{cursor:pointer;}#mermaid-svg-4wNZCIi6kfvZ54k1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .arrowheadPath{fill:#333333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4wNZCIi6kfvZ54k1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4wNZCIi6kfvZ54k1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4wNZCIi6kfvZ54k1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4wNZCIi6kfvZ54k1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4wNZCIi6kfvZ54k1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4wNZCIi6kfvZ54k1 .cluster text{fill:#333;}#mermaid-svg-4wNZCIi6kfvZ54k1 .cluster span{color:#333;}#mermaid-svg-4wNZCIi6kfvZ54k1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4wNZCIi6kfvZ54k1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4wNZCIi6kfvZ54k1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-4wNZCIi6kfvZ54k1 .icon-shape,#mermaid-svg-4wNZCIi6kfvZ54k1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4wNZCIi6kfvZ54k1 .icon-shape p,#mermaid-svg-4wNZCIi6kfvZ54k1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4wNZCIi6kfvZ54k1 .icon-shape .label rect,#mermaid-svg-4wNZCIi6kfvZ54k1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4wNZCIi6kfvZ54k1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4wNZCIi6kfvZ54k1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4wNZCIi6kfvZ54k1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} accept 阻塞
accept 阻塞
ServerSocket 监听 8080
客户端1 连入
客户端2 连入
新建线程1 读/写 阻塞
新建线程2 读/写 阻塞
客户端1 断开 → 线程1 结束
客户端2 断开 → 线程2 结束
致命问题:1 万个连接 = 1 万个线程 → 线程上下文切换、内存(每线程默认 1MB 栈)直接把系统压垮。
2.2 源码示例:BIO 服务端 + 客户端
服务端(BioServer.java):
java
import java.io.*;
import java.net.*;
/**
* BIO 服务端:一连接一线程
* 运行:java BioServer.java
*/
public class BioServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
System.out.println("[BIO] 服务端启动,监听 8080");
while (true) {
// ① 阻塞,直到有客户端连入
Socket client = server.accept();
System.out.println("[BIO] 客户端连入: " + client.getRemoteSocketAddress());
// ② 每个连接开一个新线程处理
new Thread(() -> handle(client)).start();
}
}
static void handle(Socket client) {
try (
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(client.getOutputStream()), true)
) {
String line;
// ③ readLine 阻塞,直到读到一行或连接断开
while ((line = in.readLine()) != null) {
System.out.println("[BIO] 收到: " + line);
out.println("ECHO: " + line); // 回显
}
} catch (IOException e) {
System.err.println("[BIO] 连接异常: " + e.getMessage());
} finally {
System.out.println("[BIO] 客户端断开");
}
}
}
客户端(BioClient.java):
java
import java.io.*;
import java.net.*;
/**
* BIO 客户端
* 运行:java BioClient.java
*/
public class BioClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
String input;
while ((input = console.readLine()) != null) {
out.println(input); // 发给服务端
String resp = in.readLine(); // 读服务端回显(阻塞)
System.out.println("[Client] " + resp);
if ("bye".equalsIgnoreCase(input)) break;
}
socket.close();
}
}
2.3 BIO 的致命问题
| 问题 | 说明 |
|---|---|
| 线程爆炸 | C10K 连接 → 1 万线程,内存 + 上下文切换不可接受 |
| 连接空闲也占线程 | 客户端连着但不说话,线程照样阻塞在 read 上 |
| 无法扩展 | 加线程池只是缓兵之计,本质没变 |
结论:BIO 只适合连接数少、且每个连接都很活跃的场景(如内部 RPC)。高并发网络服务必须换模型。
三、NIO:非阻塞 I/O + 多路复用
3.1 三大核心:Buffer / Channel / Selector
NIO 的三个基石:
| 组件 | 作用 | 类比 |
|---|---|---|
| Buffer | 读写数据的容器,可读写切换 | 快递箱 |
| Channel | 双向数据通道(可读可写) | 高速公路 |
| Selector | 多路复用器,一个线程监控多个 Channel 的就绪事件 | 交警指挥台 |
#mermaid-svg-zCS9RXk6iDDYV3RR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zCS9RXk6iDDYV3RR .error-icon{fill:#552222;}#mermaid-svg-zCS9RXk6iDDYV3RR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zCS9RXk6iDDYV3RR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zCS9RXk6iDDYV3RR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zCS9RXk6iDDYV3RR .marker.cross{stroke:#333333;}#mermaid-svg-zCS9RXk6iDDYV3RR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zCS9RXk6iDDYV3RR p{margin:0;}#mermaid-svg-zCS9RXk6iDDYV3RR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zCS9RXk6iDDYV3RR .cluster-label text{fill:#333;}#mermaid-svg-zCS9RXk6iDDYV3RR .cluster-label span{color:#333;}#mermaid-svg-zCS9RXk6iDDYV3RR .cluster-label span p{background-color:transparent;}#mermaid-svg-zCS9RXk6iDDYV3RR .label text,#mermaid-svg-zCS9RXk6iDDYV3RR span{fill:#333;color:#333;}#mermaid-svg-zCS9RXk6iDDYV3RR .node rect,#mermaid-svg-zCS9RXk6iDDYV3RR .node circle,#mermaid-svg-zCS9RXk6iDDYV3RR .node ellipse,#mermaid-svg-zCS9RXk6iDDYV3RR .node polygon,#mermaid-svg-zCS9RXk6iDDYV3RR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zCS9RXk6iDDYV3RR .rough-node .label text,#mermaid-svg-zCS9RXk6iDDYV3RR .node .label text,#mermaid-svg-zCS9RXk6iDDYV3RR .image-shape .label,#mermaid-svg-zCS9RXk6iDDYV3RR .icon-shape .label{text-anchor:middle;}#mermaid-svg-zCS9RXk6iDDYV3RR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zCS9RXk6iDDYV3RR .rough-node .label,#mermaid-svg-zCS9RXk6iDDYV3RR .node .label,#mermaid-svg-zCS9RXk6iDDYV3RR .image-shape .label,#mermaid-svg-zCS9RXk6iDDYV3RR .icon-shape .label{text-align:center;}#mermaid-svg-zCS9RXk6iDDYV3RR .node.clickable{cursor:pointer;}#mermaid-svg-zCS9RXk6iDDYV3RR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zCS9RXk6iDDYV3RR .arrowheadPath{fill:#333333;}#mermaid-svg-zCS9RXk6iDDYV3RR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zCS9RXk6iDDYV3RR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zCS9RXk6iDDYV3RR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zCS9RXk6iDDYV3RR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zCS9RXk6iDDYV3RR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zCS9RXk6iDDYV3RR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zCS9RXk6iDDYV3RR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zCS9RXk6iDDYV3RR .cluster text{fill:#333;}#mermaid-svg-zCS9RXk6iDDYV3RR .cluster span{color:#333;}#mermaid-svg-zCS9RXk6iDDYV3RR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zCS9RXk6iDDYV3RR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zCS9RXk6iDDYV3RR rect.text{fill:none;stroke-width:0;}#mermaid-svg-zCS9RXk6iDDYV3RR .icon-shape,#mermaid-svg-zCS9RXk6iDDYV3RR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zCS9RXk6iDDYV3RR .icon-shape p,#mermaid-svg-zCS9RXk6iDDYV3RR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zCS9RXk6iDDYV3RR .icon-shape .label rect,#mermaid-svg-zCS9RXk6iDDYV3RR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zCS9RXk6iDDYV3RR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zCS9RXk6iDDYV3RR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zCS9RXk6iDDYV3RR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 单线程
监听 ACCEPT
监听 READ
监听 READ
Selector 多路复用器
Channel-1
Channel-2
Channel-3
Buffer
Buffer
Buffer
关键点:Selector 在底层就是操作系统的多路复用系统调用 (Linux 上是 epoll,macOS 上是 kqueue,Windows 上是 WSAPoll)。一个线程调用一次 select(),就能知道上千个 Channel 里哪些「有数据可读」「可写」「有新连接」。
3.2 源码示例:NIO 多路复用服务端
java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
/**
* NIO 多路复用服务端:单线程处理多个连接
* 运行:java NioServer.java
*/
public class NioServer {
public static void main(String[] args) throws IOException {
// 1) 打开 Selector(底层在 Linux 上就是 epoll)
Selector selector = Selector.open();
// 2) 打开 ServerSocketChannel,绑定端口,设为非阻塞,注册 ACCEPT 事件
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(8080));
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("[NIO] 服务端启动,监听 8080");
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 3) 事件循环:一次 select() 拿到所有就绪事件
while (true) {
selector.select(); // 阻塞,直到至少一个事件就绪
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove(); // 必须手动移除,否则重复处理
if (key.isAcceptable()) {
// ① 新连接到达
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("[NIO] 新连接: " + client.getRemoteSocketAddress());
} else if (key.isReadable()) {
// ② 某个连接有数据可读
SocketChannel client = (SocketChannel) key.channel();
buffer.clear();
int n = client.read(buffer); // 非阻塞读
if (n == -1) { // 对端关闭
key.cancel();
client.close();
System.out.println("[NIO] 连接断开");
continue;
}
buffer.flip();
String msg = StandardCharsets.UTF_8.decode(buffer).toString();
System.out.println("[NIO] 收到: " + msg.trim());
// 回显
buffer.rewind();
client.write(buffer);
}
}
}
}
}
与 BIO 的本质区别:
- 单线程就能管理成千上万个连接------
selector.select()一次性返回所有就绪事件; - 不读不写时不占 CPU、不占线程;
- 但数据处理(编解码、业务逻辑)仍然在这个单线程里做,如果某个 handler 很慢,会拖垮整个事件循环------这正是 Netty 要解决的。
四、AIO:异步 I/O
4.1 原理与适用平台
AIO(JDK 7 引入的 AsynchronousChannel)是「真正的异步 I/O」:
- 调用
read/write时传入一个CompletionHandler回调,方法立即返回; - 操作系统在后台完成 I/O,完成后回调
completed(); - 应用线程既不阻塞、也不轮询。
| 平台 | 底层实现 | 效果 |
|---|---|---|
| Windows | IOCP(真异步) | 性能极佳 |
| Linux | epoll 模拟(非真 IOCP) | 效果 ≈ NIO,且 API 更复杂 |
| macOS | kqueue 模拟 | 效果 ≈ NIO |
这就是为什么 Linux 服务端几乎没人用 Java AIO------底层没有真正的 IOCP,只是用 epoll 模拟,性能没优势、代码还更难写。Netty 也因此选择了 NIO 而非 AIO 作为主路线。
4.2 源码示例:AIO(AsynchronousChannel)服务端
java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
/**
* AIO 服务端:回调式异步
* 运行:java AioServer.java
*/
public class AioServer {
public static void main(String[] args) throws Exception {
AsynchronousServerSocketChannel server =
AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(8080));
System.out.println("[AIO] 服务端启动,监听 8080");
// ① 异步 accept:传入回调,方法立即返回
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel client, Void attachment) {
// 继续接受下一个连接(关键!否则只处理一个)
server.accept(null, this);
ByteBuffer buffer = ByteBuffer.allocate(1024);
// ② 异步 read:传入回调
client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer n, ByteBuffer buf) {
if (n == -1) {
close(client);
return;
}
buf.flip();
String msg = StandardCharsets.UTF_8.decode(buf).toString();
System.out.println("[AIO] 收到: " + msg.trim());
// 异步 write:回显
buf.rewind();
client.write(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer w, ByteBuffer b) {
b.clear();
// 继续读下一轮
client.read(b, b, this);
}
@Override
public void failed(Throwable e, ByteBuffer b) {
close(client);
}
});
}
@Override
public void failed(Throwable e, ByteBuffer buf) {
close(client);
}
});
}
@Override
public void failed(Throwable e, Void attachment) {
e.printStackTrace();
}
});
// 主线程不退出(AIO 由后台线程池驱动)
Thread.currentThread().join();
}
static void close(AsynchronousSocketChannel c) {
try { c.close(); } catch (Exception ignored) {}
System.out.println("[AIO] 连接断开");
}
}
4.3 为什么 Linux 下 AIO 几乎被弃用
- Linux 没有 IOCP ,JDK 的 AIO 在 Linux 上是用
epoll+ 线程池模拟的,性能不比 NIO 好; - 回调地狱:如上代码,嵌套回调写起来非常痛苦,可读性差;
- Netty 不主推 AIO :Netty 曾经有过
AioEventLoopGroup,后来直接移除了------因为收益太小、维护成本太高。
结论:Windows 平台用 AIO 有真 IOCP 加持;Linux 上推荐直接用 NIO 或 Netty。
五、多路复用原理:从操作系统层面讲透
5.1 为什么需要「多路复用」
假设有 10000 个连接,怎么知道哪些「有数据可读」?
- 笨办法 :每个连接调一次
read,非阻塞地轮询一遍 → 10000 次系统调用,CPU 飞起。 - 多路复用 :把所有连接的 fd(文件描述符)一次性交给内核,内核告诉你哪些就绪 → 1 次系统调用搞定。
这就是「多路复用」:用一个系统调用,同时监控多个 fd。
不同操作系统提供了不同的系统调用来实现它:
| 系统调用 | 平台 | 复杂度 | 机制 |
|---|---|---|---|
| select | Linux / macOS | O(n) | 位图 + 线性遍历,有 1024 上限 |
| poll | Linux / macOS | O(n) | 链表替代位图,无上限但仍遍历 |
| epoll | Linux | O(1) | 红黑树 + 就绪链表,事件驱动 |
| kqueue | macOS / FreeBSD | O(1) | 类似 epoll,filter 更丰富 |
| IOCP | Windows | O(1) | 完成端口,真异步通知 |
下面逐个讲。
5.2 select(Linux / macOS 通用)
select 是最古老的多路复用调用(1983 年 BSD UNIX)。
原理:
- 用户把关心的 fd 集合装进一个 位图 (
fd_set),每个 fd 对应一个 bit; - 调用
select(maxfd, &readfds, &writefds, &exceptfds, &timeout),用户态→内核态拷贝位图; - 内核遍历所有 fd,检查是否有就绪事件,有的话修改位图,返回就绪数量;
- 用户态再线性遍历位图,找到哪些 bit 被置位,逐个处理。
致命缺陷:
- fd 数量上限 :
FD_SETSIZE默认 1024,改要重编内核; - 每次都要拷贝整个位图到内核,fd 多了开销大;
- 返回后要线性遍历全部 fd 找就绪的,O(n);
- 即使只有 1 个 fd 就绪,也要扫全部 10000 个。
c
// select 伪代码
fd_set readfds;
FD_ZERO(&readfds);
for (int i = 0; i < 10000; i++)
FD_SET(fds[i], &readfds); // 装入位图
select(maxfd + 1, &readfds, NULL, NULL, &timeout); // 交给内核
for (int i = 0; i < 10000; i++) { // 回来还要全扫一遍
if (FD_ISSET(fds[i], &readfds)) {
read(fds[i], buf, sizeof(buf));
}
}
5.3 poll(Linux / macOS 通用)
poll 是 select 的改进版(1986 年 UNIX SVR3)。
改进点 :用 struct pollfd 数组代替位图,取消了 1024 上限。
c
struct pollfd {
int fd; // 关心的 fd
short events; // 关心的事件(POLLIN / POLLOUT)
short revents; // 内核返回的就绪事件
};
但核心缺陷没变:
- 仍然要把整个数组拷贝到内核;
- 返回后仍然要线性遍历 整个数组找
revents != 0的项,O(n)。
结论:poll 只是去掉了 fd 上限,复杂度仍是 O(n),大规模并发下仍不行。
5.4 epoll(Linux 专属,终极方案)
epoll 是 Linux 2.6(2002 年)引入的,是 Linux 高并发的终极方案。
核心创新 :用「红黑树 + 就绪链表」替代了 select/poll 的「遍历全量」。
#mermaid-svg-Alj6WTrQ7bI3JPxr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Alj6WTrQ7bI3JPxr .error-icon{fill:#552222;}#mermaid-svg-Alj6WTrQ7bI3JPxr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Alj6WTrQ7bI3JPxr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .marker.cross{stroke:#333333;}#mermaid-svg-Alj6WTrQ7bI3JPxr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Alj6WTrQ7bI3JPxr p{margin:0;}#mermaid-svg-Alj6WTrQ7bI3JPxr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .cluster-label text{fill:#333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .cluster-label span{color:#333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .cluster-label span p{background-color:transparent;}#mermaid-svg-Alj6WTrQ7bI3JPxr .label text,#mermaid-svg-Alj6WTrQ7bI3JPxr span{fill:#333;color:#333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .node rect,#mermaid-svg-Alj6WTrQ7bI3JPxr .node circle,#mermaid-svg-Alj6WTrQ7bI3JPxr .node ellipse,#mermaid-svg-Alj6WTrQ7bI3JPxr .node polygon,#mermaid-svg-Alj6WTrQ7bI3JPxr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Alj6WTrQ7bI3JPxr .rough-node .label text,#mermaid-svg-Alj6WTrQ7bI3JPxr .node .label text,#mermaid-svg-Alj6WTrQ7bI3JPxr .image-shape .label,#mermaid-svg-Alj6WTrQ7bI3JPxr .icon-shape .label{text-anchor:middle;}#mermaid-svg-Alj6WTrQ7bI3JPxr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Alj6WTrQ7bI3JPxr .rough-node .label,#mermaid-svg-Alj6WTrQ7bI3JPxr .node .label,#mermaid-svg-Alj6WTrQ7bI3JPxr .image-shape .label,#mermaid-svg-Alj6WTrQ7bI3JPxr .icon-shape .label{text-align:center;}#mermaid-svg-Alj6WTrQ7bI3JPxr .node.clickable{cursor:pointer;}#mermaid-svg-Alj6WTrQ7bI3JPxr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .arrowheadPath{fill:#333333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Alj6WTrQ7bI3JPxr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Alj6WTrQ7bI3JPxr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Alj6WTrQ7bI3JPxr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Alj6WTrQ7bI3JPxr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Alj6WTrQ7bI3JPxr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Alj6WTrQ7bI3JPxr .cluster text{fill:#333;}#mermaid-svg-Alj6WTrQ7bI3JPxr .cluster span{color:#333;}#mermaid-svg-Alj6WTrQ7bI3JPxr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Alj6WTrQ7bI3JPxr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Alj6WTrQ7bI3JPxr rect.text{fill:none;stroke-width:0;}#mermaid-svg-Alj6WTrQ7bI3JPxr .icon-shape,#mermaid-svg-Alj6WTrQ7bI3JPxr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Alj6WTrQ7bI3JPxr .icon-shape p,#mermaid-svg-Alj6WTrQ7bI3JPxr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Alj6WTrQ7bI3JPxr .icon-shape .label rect,#mermaid-svg-Alj6WTrQ7bI3JPxr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Alj6WTrQ7bI3JPxr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Alj6WTrQ7bI3JPxr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Alj6WTrQ7bI3JPxr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} epoll 内核态
fd 就绪时
epoll_wait 取走
红黑树: 存所有注册的 fd
就绪链表: 只存就绪的 fd
用户态
三个系统调用:
| 调用 | 作用 |
|---|---|
epoll_create |
创建 epoll 实例,返回 fd |
epoll_ctl(ADD/MOD/DEL) |
往红黑树里增删改一个 fd(注册一次即可,不重复传) |
epoll_wait |
只从「就绪链表」取就绪事件,O(1) |
两种触发模式:
- LT(水平触发,默认) :只要 fd 上有数据可读,
epoll_wait就会一直通知你。你一次没读完,下次还通知。不会丢事件,但可能重复通知。 - ET(边缘触发) :fd 从「无数据」变成「有数据」时只通知一次 。你必须一次性把数据读完(用非阻塞 I/O + 循环 read 直到
EAGAIN),否则剩余数据不再通知。效率更高,但编程更难,漏读会丢数据。
c
// epoll 伪代码(LT 模式)
int epfd = epoll_create1(0);
struct epoll_event ev;
ev.events = EPOLLIN; // 水平触发(默认)
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev); // 注册到红黑树
struct epoll_event events[1024];
while (1) {
// 只返回就绪的 fd,O(1)
int n = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < n; i++) { // 只遍历 n 个就绪的,不是全量
if (events[i].data.fd == listen_fd) {
int cfd = accept(listen_fd, ...);
ev.events = EPOLLIN;
ev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); // 新连接注册
} else {
read(events[i].data.fd, buf, sizeof(buf));
}
}
}
Java NIO 在 Linux 上的底层就是 epoll (Selector.open() → EPollSelectorImpl)。
为什么 epoll 快:
epoll_ctl只在注册时调一次,不像 select/poll 每次都拷贝全量;epoll_wait只返回就绪的 fd,不扫描全量,O(1);- 红黑树保证插入 / 删除 O(log n)。
5.5 kqueue(macOS / FreeBSD)
kqueue 是 FreeBSD 4.1(2000 年)引入,macOS 也支持。设计理念与 epoll 类似,但更通用。
c
int kq = kqueue();
struct kevent change;
EV_SET(&change, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &change, 1, NULL, 0, NULL); // 注册
struct kevent events[1024];
int n = kevent(kq, NULL, 0, events, 1024, NULL); // 取就绪
与 epoll 的区别:
| 维度 | epoll | kqueue |
|---|---|---|
| 平台 | Linux | macOS / FreeBSD |
| 监控对象 | fd | filter(更通用,可监控信号、定时器、子进程等) |
| 触发模式 | LT / ET | LT / ET |
| 性能 | O(1) | O(1) |
Java NIO 在 macOS 上的底层就是 kqueue (KQueueSelectorImpl)。
5.6 IOCP(Windows 专属)
IOCP(I/O Completion Port,I/O 完成端口)是 Windows 独有的真异步 I/O模型,也是 Windows 上性能最高的方案。
核心理念与 epoll 相反:
- epoll / kqueue 是「就绪通知」:告诉你「fd 有数据了,你自己去 read」;
- IOCP 是「完成通知 」:你发一个 read 请求就不管了,操作系统帮你读好,完成后通知你「数据已经在 buffer 里了」。
c
// IOCP 伪代码
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// 把 socket 绑到 IOCP
CreateIoCompletionPort(socket, iocp, 0, 0);
// 发起异步读(立刻返回)
WSARecv(socket, &buf, ...); // OS 后台帮你读
// 工作线程循环等待「完成通知」
DWORD bytes;
ULONG_PTR key;
OVERLAPPED *ov;
GetQueuedCompletionStatus(iocp, &bytes, &key, &ov, INFINITE);
// 到这里说明 I/O 已完成,buf 里已经有数据
Java AIO 在 Windows 上的底层就是 IOCP,所以在 Windows 上 Java AIO 性能很好。但 Linux 没有 IOCP,Java AIO 只能用 epoll 模拟。
5.7 五种机制对照表
| 机制 | 平台 | fd 上限 | 复杂度 | 通知方式 | 触发模式 |
|---|---|---|---|---|---|
| select | Linux / macOS | 1024 | O(n) | 就绪通知(遍历) | LT |
| poll | Linux / macOS | 无上限 | O(n) | 就绪通知(遍历) | LT |
| epoll | Linux | 无上限 | O(1) | 就绪通知(链表) | LT / ET |
| kqueue | macOS / FreeBSD | 无上限 | O(1) | 就绪通知(filter) | LT / ET |
| IOCP | Windows | 无上限 | O(1) | 完成通知(真异步) | --- |
Java NIO 的
Selector会根据运行平台自动选择:Linux → epoll,macOS → kqueue,Windows → WSAPoll。你写的代码不变,底层自动适配。
六、从 NIO 到 Netty:为什么要造轮子
6.1 原生 NIO 的痛点
虽然 NIO 解决了 BIO 的线程爆炸问题,但直接用它写生产级代码,痛苦不堪:
| 痛点 | 说明 |
|---|---|
| API 复杂 | 要手动管 Buffer 的 flip/rewind/clear,少一个就出 Bug |
| 半包 / 粘包 | TCP 是流,没有消息边界,你要自己拆包------LengthFieldBasedFrameDecoder 这种逻辑自己写太累 |
| 空轮询 Bug | JDK 的 epoll 在某些情况下 select() 会空转不阻塞,CPU 100%(著名的 JDK Bug,Netty 有兜底) |
| 线程模型 | 事件循环里做耗时业务会卡死,你要自己拆「IO 线程」和「业务线程池」 |
| 断线重连 | 客户端断线重连、心跳检测都要自己实现 |
6.2 Netty 是怎么解决的
| 痛点 | Netty 的解法 |
|---|---|
| API 复杂 | 自研 ByteBuf(读写指针分离,不用 flip),Channel 封装 |
| 半包 / 粘包 | 内置 LineBasedFrameDecoder / LengthFieldBasedFrameDecoder 等拆包器 |
| 空轮询 Bug | 检测到空轮询自动重建 Selector(NioEventLoop 兜底) |
| 线程模型 | 主从 Reactor,IO 线程和业务线程分离 |
| 断线重连 | 内置 IdleStateHandler 心跳 + 重连机制 |
一句话:Netty 是对 NIO 的工程级封装,把「能用」变成「好用」。
七、Netty 核心原理
7.1 主从 Reactor 线程模型
Netty 采用「主从 Reactor」模型:
#mermaid-svg-JoJkaQE9rO8IbgUU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JoJkaQE9rO8IbgUU .error-icon{fill:#552222;}#mermaid-svg-JoJkaQE9rO8IbgUU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JoJkaQE9rO8IbgUU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JoJkaQE9rO8IbgUU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JoJkaQE9rO8IbgUU .marker.cross{stroke:#333333;}#mermaid-svg-JoJkaQE9rO8IbgUU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JoJkaQE9rO8IbgUU p{margin:0;}#mermaid-svg-JoJkaQE9rO8IbgUU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JoJkaQE9rO8IbgUU .cluster-label text{fill:#333;}#mermaid-svg-JoJkaQE9rO8IbgUU .cluster-label span{color:#333;}#mermaid-svg-JoJkaQE9rO8IbgUU .cluster-label span p{background-color:transparent;}#mermaid-svg-JoJkaQE9rO8IbgUU .label text,#mermaid-svg-JoJkaQE9rO8IbgUU span{fill:#333;color:#333;}#mermaid-svg-JoJkaQE9rO8IbgUU .node rect,#mermaid-svg-JoJkaQE9rO8IbgUU .node circle,#mermaid-svg-JoJkaQE9rO8IbgUU .node ellipse,#mermaid-svg-JoJkaQE9rO8IbgUU .node polygon,#mermaid-svg-JoJkaQE9rO8IbgUU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JoJkaQE9rO8IbgUU .rough-node .label text,#mermaid-svg-JoJkaQE9rO8IbgUU .node .label text,#mermaid-svg-JoJkaQE9rO8IbgUU .image-shape .label,#mermaid-svg-JoJkaQE9rO8IbgUU .icon-shape .label{text-anchor:middle;}#mermaid-svg-JoJkaQE9rO8IbgUU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JoJkaQE9rO8IbgUU .rough-node .label,#mermaid-svg-JoJkaQE9rO8IbgUU .node .label,#mermaid-svg-JoJkaQE9rO8IbgUU .image-shape .label,#mermaid-svg-JoJkaQE9rO8IbgUU .icon-shape .label{text-align:center;}#mermaid-svg-JoJkaQE9rO8IbgUU .node.clickable{cursor:pointer;}#mermaid-svg-JoJkaQE9rO8IbgUU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JoJkaQE9rO8IbgUU .arrowheadPath{fill:#333333;}#mermaid-svg-JoJkaQE9rO8IbgUU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JoJkaQE9rO8IbgUU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JoJkaQE9rO8IbgUU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JoJkaQE9rO8IbgUU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JoJkaQE9rO8IbgUU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JoJkaQE9rO8IbgUU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JoJkaQE9rO8IbgUU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JoJkaQE9rO8IbgUU .cluster text{fill:#333;}#mermaid-svg-JoJkaQE9rO8IbgUU .cluster span{color:#333;}#mermaid-svg-JoJkaQE9rO8IbgUU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JoJkaQE9rO8IbgUU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JoJkaQE9rO8IbgUU rect.text{fill:none;stroke-width:0;}#mermaid-svg-JoJkaQE9rO8IbgUU .icon-shape,#mermaid-svg-JoJkaQE9rO8IbgUU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JoJkaQE9rO8IbgUU .icon-shape p,#mermaid-svg-JoJkaQE9rO8IbgUU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JoJkaQE9rO8IbgUU .icon-shape .label rect,#mermaid-svg-JoJkaQE9rO8IbgUU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JoJkaQE9rO8IbgUU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JoJkaQE9rO8IbgUU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JoJkaQE9rO8IbgUU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WorkerGroup 处理 IO
BossGroup 接受连接
accept
accept
accept
accept
BossEventLoop
1个线程
WorkerEventLoop-1
WorkerEventLoop-2
WorkerEventLoop-3
WorkerEventLoop-4
Pipeline → Handler
Pipeline → Handler
Pipeline → Handler
Pipeline → Handler
- BossGroup (1 个线程):只负责
accept新连接; - WorkerGroup(N 个线程):负责已建立连接的读写和业务处理;
- 每个连接被分配到一个 Worker 的
EventLoop上,整个生命周期都在同一个线程里处理,无锁。
java
// 对应代码
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接收连接
EventLoopGroup workerGroup = new NioEventLoopGroup(4); // 处理 IO
7.2 EventLoop 与 Pipeline
- EventLoop = 一个线程 + 一个任务队列 + 一个 Selector。它既做 IO(
select + read/write),也做任务(定时任务、业务任务)。 - Pipeline = 每个
Channel绑一条双向链表,入站事件(读)从 Head 往后走,出站事件(写)从 Tail 往前走。
#mermaid-svg-xmiHnZ85dZyeW0lC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xmiHnZ85dZyeW0lC .error-icon{fill:#552222;}#mermaid-svg-xmiHnZ85dZyeW0lC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xmiHnZ85dZyeW0lC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xmiHnZ85dZyeW0lC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xmiHnZ85dZyeW0lC .marker.cross{stroke:#333333;}#mermaid-svg-xmiHnZ85dZyeW0lC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xmiHnZ85dZyeW0lC p{margin:0;}#mermaid-svg-xmiHnZ85dZyeW0lC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xmiHnZ85dZyeW0lC .cluster-label text{fill:#333;}#mermaid-svg-xmiHnZ85dZyeW0lC .cluster-label span{color:#333;}#mermaid-svg-xmiHnZ85dZyeW0lC .cluster-label span p{background-color:transparent;}#mermaid-svg-xmiHnZ85dZyeW0lC .label text,#mermaid-svg-xmiHnZ85dZyeW0lC span{fill:#333;color:#333;}#mermaid-svg-xmiHnZ85dZyeW0lC .node rect,#mermaid-svg-xmiHnZ85dZyeW0lC .node circle,#mermaid-svg-xmiHnZ85dZyeW0lC .node ellipse,#mermaid-svg-xmiHnZ85dZyeW0lC .node polygon,#mermaid-svg-xmiHnZ85dZyeW0lC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xmiHnZ85dZyeW0lC .rough-node .label text,#mermaid-svg-xmiHnZ85dZyeW0lC .node .label text,#mermaid-svg-xmiHnZ85dZyeW0lC .image-shape .label,#mermaid-svg-xmiHnZ85dZyeW0lC .icon-shape .label{text-anchor:middle;}#mermaid-svg-xmiHnZ85dZyeW0lC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xmiHnZ85dZyeW0lC .rough-node .label,#mermaid-svg-xmiHnZ85dZyeW0lC .node .label,#mermaid-svg-xmiHnZ85dZyeW0lC .image-shape .label,#mermaid-svg-xmiHnZ85dZyeW0lC .icon-shape .label{text-align:center;}#mermaid-svg-xmiHnZ85dZyeW0lC .node.clickable{cursor:pointer;}#mermaid-svg-xmiHnZ85dZyeW0lC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xmiHnZ85dZyeW0lC .arrowheadPath{fill:#333333;}#mermaid-svg-xmiHnZ85dZyeW0lC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xmiHnZ85dZyeW0lC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xmiHnZ85dZyeW0lC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xmiHnZ85dZyeW0lC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xmiHnZ85dZyeW0lC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xmiHnZ85dZyeW0lC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xmiHnZ85dZyeW0lC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xmiHnZ85dZyeW0lC .cluster text{fill:#333;}#mermaid-svg-xmiHnZ85dZyeW0lC .cluster span{color:#333;}#mermaid-svg-xmiHnZ85dZyeW0lC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xmiHnZ85dZyeW0lC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xmiHnZ85dZyeW0lC rect.text{fill:none;stroke-width:0;}#mermaid-svg-xmiHnZ85dZyeW0lC .icon-shape,#mermaid-svg-xmiHnZ85dZyeW0lC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xmiHnZ85dZyeW0lC .icon-shape p,#mermaid-svg-xmiHnZ85dZyeW0lC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xmiHnZ85dZyeW0lC .icon-shape .label rect,#mermaid-svg-xmiHnZ85dZyeW0lC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xmiHnZ85dZyeW0lC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xmiHnZ85dZyeW0lC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xmiHnZ85dZyeW0lC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Outbound 出站 写
Tail
Encoder
Head
Inbound 入站 读
Head
Decoder
BusinessHandler
Tail
每个 Handler 只管自己的事(解码、业务、编码),互不耦合------这就是 Netty 的「责任链模式」。
7.3 ByteBuf:替代 ByteBuffer
JDK 的 ByteBuffer 有个反人类设计:读写共用一个 position 指针,读写切换必须手动 flip(),忘写就出 Bug。
Netty 的 ByteBuf 用两个独立指针 readerIndex / writerIndex 解决了这个问题:
| 操作 | JDK ByteBuffer | Netty ByteBuf |
|---|---|---|
| 读到哪 | position(与写共用) |
readerIndex(独立) |
| 写到哪 | position(与读共用) |
writerIndex(独立) |
| 读写切换 | 必须调 flip() |
不用,各走各的 |
| 堆外内存 | allocateDirect() 手动释放 |
Unpooled.directBuffer() 引用计数自动释放 |
| 池化 | 不支持 | PooledByteBufAllocator 对象池 |
java
// JDK ByteBuffer:必须 flip
ByteBuffer buf = ByteBuffer.allocate(100);
buf.put("hello".getBytes());
buf.flip(); // ← 忘了这行就读不到
byte[] data = new byte[buf.remaining()];
buf.get(data);
// Netty ByteBuf:不用 flip
ByteBuf buf = Unpooled.buffer(100);
buf.writeBytes("hello".getBytes());
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data); // 直接读,不冲突
八、Netty 实战示例:Echo 服务端 + 客户端
8.1 pom.xml 依赖
xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>netty-echo</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.108.Final</version>
</dependency>
</dependencies>
</project>
8.2 Netty Echo Server
java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* Netty Echo 服务端
*/
public class NettyEchoServer {
public static void main(String[] args) throws InterruptedException {
// ① 主从 Reactor:Boss 接连接,Worker 处理 IO
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup(4);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(boss, worker)
.channel(NioServerSocketChannel.class) // 使用 NIO(底层 epoll/kqueue)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
// ② 用分隔符解决粘包/半包(以 $ 结尾为一条消息)
ByteBuf delimiter = Unpooled.copiedBuffer("$", CharsetUtil.UTF_8);
p.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
p.addLast(new StringDecoder(CharsetUtil.UTF_8)); // 入站:ByteBuf → String
p.addLast(new StringEncoder(CharsetUtil.UTF_8)); // 出站:String → ByteBuf
// ③ 业务 Handler
p.addLast(new EchoServerHandler());
}
});
// ④ 绑定端口,同步等待启动
ChannelFuture f = b.bind(8080).sync();
System.out.println("[Netty] 服务端启动,监听 8080");
// ⑤ 等待关闭
f.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
/**
* 业务 Handler:继承 SimpleChannelInboundHandler,泛型是入站消息类型
* 因为前面 StringDecoder 已把 ByteBuf 转成 String,这里直接收 String
*/
static class EchoServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("[Netty] 收到: " + msg);
// 回显:写回会经过 StringEncoder 编码
ctx.writeAndReturn("ECHO: " + msg + "$");
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("[Netty] 客户端连入: " + ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("[Netty] 客户端断开");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
}
上面
ctx.writeAndReturn是示意写法。实际回显应写ctx.writeAndFlush("ECHO: " + msg + "$"),flush会触发出站链刷新。本段为了突出「Encoder 编码 + 分隔符」的核心,用writeAndFlush。
修正后的 Handler 回显写法:
java
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("[Netty] 收到: " + msg);
ctx.writeAndFlush("ECHO: " + msg + "$"); // ← 正确写法:flush 立即发送
}
8.3 Netty Echo Client
java
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
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;
import io.netty.util.CharsetUtil;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
* Netty Echo 客户端
*/
public class NettyEchoClient {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup(1);
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
ByteBuf delimiter = Unpooled.copiedBuffer("$", CharsetUtil.UTF_8);
p.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
p.addLast(new StringDecoder(CharsetUtil.UTF_8));
p.addLast(new StringEncoder(CharsetUtil.UTF_8));
p.addLast(new EchoClientHandler());
}
});
Channel ch = b.connect("127.0.0.1", 8080).sync().channel();
System.out.println("[Client] 已连接服务端,输入消息回车发送(bye 退出)");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = in.readLine()) != null) {
if ("bye".equalsIgnoreCase(line)) break;
ch.writeAndFlush(line + "$"); // 以 $ 结尾,匹配服务端分隔符
}
ch.close();
} finally {
group.shutdownGracefully();
}
}
static class EchoClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("[Client] " + msg);
}
}
}
8.4 运行结果
# 服务端
[Netty] 服务端启动,监听 8080
[Netty] 客户端连入: /127.0.0.1:54321
[Netty] 收到: hello
[Netty] 收到: world
# 客户端
[Client] 已连接服务端,输入消息回车发送(bye 退出)
hello
[Client] ECHO: hello
world
[Client] ECHO: world
注意几个关键细节:
- 粘包处理 :客户端发
hello$world$,服务端的DelimiterBasedFrameDecoder会自动拆成hello和world两条消息------这在原生 NIO 里要你自己写拆包逻辑。 - 编解码 :
StringDecoder/Encoder把ByteBuf ↔ String自动转换,Handler 里直接操作 String,不用碰 Buffer。 - 线程模型:Boss 1 线程接连接,Worker 4 线程处理 IO,连接数再多也不会 1 连接 1 线程。
九、全景对比与选型建议
| 维度 | BIO | NIO | AIO | Netty |
|---|---|---|---|---|
| 连接模型 | 一连接一线程 | 多路复用 | 回调异步 | 多路复用(封装) |
| 并发能力 | 差(千级) | 高(万级) | 高(万级) | 高(万级) |
| 编码难度 | 简单 | 复杂 | 复杂(回调地狱) | 简单(Handler 链) |
| 粘包处理 | 自己写 | 自己写 | 自己写 | 内置拆包器 |
| 空轮询 Bug | 无 | 有(JDK Bug) | 有 | 自动兜底 |
| 适用场景 | 连接少、内部通信 | 学习原理、轻量级 | Windows 平台 | 生产级网络服务 |
选型建议:
- 学习原理:写 NIO 代码理解多路复用;
- 内部 RPC / 连接数少:BIO + 线程池够用;
- 生产级网络服务 (IM、网关、游戏服务器):直接上 Netty,别自己造 NIO 轮子;
- Windows 平台且需要极致异步:可以考虑 AIO,但社区生态不如 Netty。
十、总结
一图回顾全篇:
#mermaid-svg-ejCXg9sxK1cZr6nT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ejCXg9sxK1cZr6nT .error-icon{fill:#552222;}#mermaid-svg-ejCXg9sxK1cZr6nT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ejCXg9sxK1cZr6nT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ejCXg9sxK1cZr6nT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ejCXg9sxK1cZr6nT .marker.cross{stroke:#333333;}#mermaid-svg-ejCXg9sxK1cZr6nT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ejCXg9sxK1cZr6nT p{margin:0;}#mermaid-svg-ejCXg9sxK1cZr6nT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ejCXg9sxK1cZr6nT .cluster-label text{fill:#333;}#mermaid-svg-ejCXg9sxK1cZr6nT .cluster-label span{color:#333;}#mermaid-svg-ejCXg9sxK1cZr6nT .cluster-label span p{background-color:transparent;}#mermaid-svg-ejCXg9sxK1cZr6nT .label text,#mermaid-svg-ejCXg9sxK1cZr6nT span{fill:#333;color:#333;}#mermaid-svg-ejCXg9sxK1cZr6nT .node rect,#mermaid-svg-ejCXg9sxK1cZr6nT .node circle,#mermaid-svg-ejCXg9sxK1cZr6nT .node ellipse,#mermaid-svg-ejCXg9sxK1cZr6nT .node polygon,#mermaid-svg-ejCXg9sxK1cZr6nT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ejCXg9sxK1cZr6nT .rough-node .label text,#mermaid-svg-ejCXg9sxK1cZr6nT .node .label text,#mermaid-svg-ejCXg9sxK1cZr6nT .image-shape .label,#mermaid-svg-ejCXg9sxK1cZr6nT .icon-shape .label{text-anchor:middle;}#mermaid-svg-ejCXg9sxK1cZr6nT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ejCXg9sxK1cZr6nT .rough-node .label,#mermaid-svg-ejCXg9sxK1cZr6nT .node .label,#mermaid-svg-ejCXg9sxK1cZr6nT .image-shape .label,#mermaid-svg-ejCXg9sxK1cZr6nT .icon-shape .label{text-align:center;}#mermaid-svg-ejCXg9sxK1cZr6nT .node.clickable{cursor:pointer;}#mermaid-svg-ejCXg9sxK1cZr6nT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ejCXg9sxK1cZr6nT .arrowheadPath{fill:#333333;}#mermaid-svg-ejCXg9sxK1cZr6nT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ejCXg9sxK1cZr6nT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ejCXg9sxK1cZr6nT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ejCXg9sxK1cZr6nT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ejCXg9sxK1cZr6nT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ejCXg9sxK1cZr6nT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ejCXg9sxK1cZr6nT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ejCXg9sxK1cZr6nT .cluster text{fill:#333;}#mermaid-svg-ejCXg9sxK1cZr6nT .cluster span{color:#333;}#mermaid-svg-ejCXg9sxK1cZr6nT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ejCXg9sxK1cZr6nT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ejCXg9sxK1cZr6nT rect.text{fill:none;stroke-width:0;}#mermaid-svg-ejCXg9sxK1cZr6nT .icon-shape,#mermaid-svg-ejCXg9sxK1cZr6nT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ejCXg9sxK1cZr6nT .icon-shape p,#mermaid-svg-ejCXg9sxK1cZr6nT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ejCXg9sxK1cZr6nT .icon-shape .label rect,#mermaid-svg-ejCXg9sxK1cZr6nT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ejCXg9sxK1cZr6nT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ejCXg9sxK1cZr6nT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ejCXg9sxK1cZr6nT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 并发不够
底层 OS
异步回调
工程封装
BIO: 一连接一线程
阻塞 accept/read
NIO: 多路复用
Selector + Channel + Buffer
Linux: epoll
macOS: kqueue
Windows: IOCP/WSAPoll
AIO: CompletionHandler
Linux 用 epoll 模拟
Netty: 主从 Reactor
Pipeline + ByteBuf
拆包/心跳/重连 全内置
核心记忆点:
- BIO = 线程换连接,简单但不可扩展;
- NIO = 多路复用(Selector),一个线程管万连接,底层是 epoll/kqueue;
- AIO = 回调式真异步,Windows 用 IOCP,Linux 用 epoll 模拟,生态弱;
- 多路复用 = select(O(n),1024 上限)→ poll(O(n),无上限)→ epoll/kqueue(O(1),事件驱动)→ IOCP(完成通知,真异步);
- Netty = 对 NIO 的工程级封装,主从 Reactor + Pipeline + ByteBuf + 内置拆包器,是 Java 高并发网络编程的事实标准。