本节摘要:把 Java 网络 IO 的三代方案——BIO、NIO、Netty——放进同一座"管道车间"对比:BIO 是一个连接养一个工人的作坊,NIO 是一个工人看管所有传送带但工具难用,Netty 则把工具、人手、原料管理全部标准化。理解这三代的差别,就理解了 Netty 存在的理由。
先用最原始的方式写一个回声服务。它的逻辑人人都懂:接受连接,然后反复读、写。
// 作坊式回声服务:每接受一个连接,就派一个专职工人(线程) public class BlockingEchoServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); while (true) { Socket client = serverSocket.accept(); // 阻塞点一:等连接 new Thread(() -> handle(client)).start(); // 派专职工人 } } private static void handle(Socket socket) { try (socket; BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) { String line; while ((line = in.readLine()) != null) { // 阻塞点二:等数据 out.println(line); // 回声 } } catch (IOException e) { // 连接断开,工人下班 } } }
这段代码在几百个连接的规模下毫无问题。问题出在伸缩性上:每个工人(线程)默认栈大约占用 1MB 内存,一万个连接就是一万个线程、约 10GB 的栈空间,再加上内核在这些线程之间来回切换的开销,服务还没忙业务就先被"人员编制"拖垮了。更要命的是,绝大多数工人平时都在阻塞等待——客户端不发数据,工人就干等着,纯属浪费。
这就是作坊式生产的两个死穴:编制随连接数线性膨胀,以及工人大面积空转。
JDK 1.4 引入的 NIO 指出了新方向:让一个线程通过 Selector 同时监视成百上千个连接,谁的数据到了就处理谁。人力问题解决了,但新的麻烦转到了工人手上:
// NIO 原生写法片段:一个 Selector 看管所有连接 Selector selector = Selector.open(); ServerSocketChannel server = ServerSocketChannel.open(); server.bind(new InetSocketAddress(8080)); server.configureBlocking(false); server.register(selector, SelectionKey.OP_ACCEPT); 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()) { /* 注册新连接的读事件 */ } else if (key.isReadable()) { ByteBuffer buf = ByteBuffer.allocate(1024); int n = ((SocketChannel) key.channel()).read(buf); // 半包! // 还要自己处理:粘包切分、写半包、key 取消、异常清理…… } } }
真实的 NIO 代码远比这个片段繁琐:ByteBuffer 读写指针要手动 flip,消息边界要自己切,写不完的数据要自己挂 OP_WRITE 事件续写,老版本 Linux 上还有 epoll 空轮询 bug 导致 CPU 飙满。方向对了,工具却太原始——就像给了工人一根万能扳手,却让他从零开始造整台机床。
Netty 做的事,用一句话说:把"一个工人看管所有传送带"的正确方向,包装成一套人人会用、又快又稳的标准件。它对上三代痛点的回应可以列成一张清单:
| 痛点 | BIO / 原生 NIO | Netty 的回应 |
|---|---|---|
| 线程编制膨胀 | 每连接一线程 | EventLoopGroup 固定线程数,一个线程服务一批连接 |
| API 繁琐易错 | Selector、SelectionKey 手工维护 | Channel 与 ChannelHandler 事件回调,专注业务 |
| 半包粘包 | 完全自己切 | 内置 LengthField 等 decode 拆包器 |
| ByteBuffer 难用 | flip 指针易错 | ByteBuf 双指针与引用计数,支持池化 |
| 传输不可选 | 只能 NIO | NIO、Epoll、KQueue、本地进程内传输一键切换 |

"会 Netty"之所以值钱,是因为它躲在大量基础软件的地基里:RPC 框架 Dubbo 的通信层、gRPC-Java 的传输层、消息中间件 RocketMQ 的 Remoting 模块、搜索引擎 Elasticsearch 的节点通信、Spring WebFlux 底层的 Reactor Netty,全都基于它。反过来,当你自己要做长连接网关、游戏服务器、物联网接入、即时通信推送这类"海量连接 + 自定义协议"的系统时,Netty 几乎是 Java 栈的默认答案。
⚠️ 常见误解:把 Netty 当"更快的 Tomcat"。它不是 HTTP 容器,而是造各种网络服务的地基——HTTP 服务只是它能加工的协议之一。
💡 关键直觉:BIO 死于人海战术,NIO 死于工具原始,Netty 赢在把正确的方法做成了标准件。评估任何网络框架,都可以沿用这三问:线程怎么编排?字节怎么管理?消息边界谁负责?