网络IO

背景与动机#

Java IO 基础 主要讲字节流、字符流和 Files,适合文件读写入门。

一旦进入高并发网络通信,就要从“流怎么读文件”切换到“IO 模型怎么等数据”:

  • BIO:一个连接一个线程,简单但连接多时开销大
  • NIO:一个线程通过 Selector 监听多个连接
  • AIO:把 IO 完成通知交给操作系统,业务侧回调处理
  • Netty:在 NIO 之上封装事件驱动框架,简化高性能网络编程

核心原理拆解#

1. 四个概念先分清#

这是两套不同维度,不要混为一谈:

2. BIO、NIO、AIO 是什么#

模型含义典型实现适用场景
BIO同步阻塞 IOSocket + InputStream连接少、逻辑简单
NIO同步非阻塞 IOChannel + Buffer + Selector连接多、轻量读写(聊天、网关)
AIO异步非阻塞 IOAsynchronousSocketChannel(NIO.2)连接多、重 IO(部分文件/网络场景)

BIO 代码特征:server.accept()in.read() 都会阻塞当前线程。

NIO 代码特征:通道设为非阻塞,由 Selector 轮询哪些连接可读/可写。

AIO 代码特征:调用异步 API 后注册 CompletionHandler,IO 完成后回调。

3. BIO、NIO、AIO 核心区别#

对比项BIONIOAIO
阻塞性阻塞非阻塞(同步)异步非阻塞
编程难度较高
连接模型常一连接一线程一线程多连接(多路复用)有效请求一线程(由 OS 通知)
数据访问面向流,单向面向缓冲区 Buffer,双向 Channel同 NIO 体系
JDK1.0 起1.4 起1.7 起

4. 五种 IO 模型(操作系统层面)#

模型说明对应 Java 常见说法
阻塞 IO没数据就一直等BIO
非阻塞 IO没数据立刻返回,应用轮询NIO 的非阻塞模式
IO 多路复用一个调用监听多个 fd,谁就绪处理谁NIO Selector
信号驱动 IO数据就绪时发信号通知Java 较少直接使用
异步 IO内核完成数据拷贝后再通知应用AIO

Selector 本质是多路复用:一个线程监听多个 Channel,避免“每个连接一个阻塞线程”。

img
img

5. 用户空间与内核空间#

应用程序不能直接访问磁盘/网卡,IO 请求通常经过:

  1. 应用在自己的缓冲区准备数据
  2. 系统调用进入内核
  3. 内核与硬件交互完成读写
  4. 数据回到用户空间

理解这点有助于解释:为什么 BIO 会占着线程等内核、为什么 NIO 可以先知道“可读”再读。

img
img

6. NIO 三大核心组件#

Channel(通道)

  • 类似流,但可双向读写
  • 常见:SocketChannelServerSocketChannel

Buffer(缓冲区)

  • 数据先进入 Buffer,再与 Channel 交互
  • 底层是数组,有 positionlimitcapacity
  • 读写前注意 flip()clear()

Selector(选择器)

  • 把多个 Channel 注册到同一个 Selector
  • 通过 select() 获取就绪事件(OP_ACCEPTOP_READOP_WRITE
  • 一个线程可处理多个连接

Selector selector = Selector.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select() == 0) continue;
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) { /* accept 新连接 */ }
if (key.isReadable()) { /* read buffer */ }
}
}

7. 网络 IO 编程怎么演进#

阶段做法问题
BIO 单线程accept + read 都在一个线程一个客户端阻塞,其他客户端进不来
BIO 多线程每来一个连接 new Thread连接多时线程爆炸、上下文切换开销大
BIO 线程池连接交给线程池缓解创建线程成本,但线程仍大量阻塞在 IO 上
NIOSelector 多路复用少量线程处理大量连接,但 API 繁琐
Netty封装 NIO + 事件驱动开发效率高,生产级网络框架

结论:连接数少用 BIO 可以;高并发网络服务应理解 NIO 模型,实际项目多用 Netty

8. Netty 是什么#

Netty 是基于 NIO 的异步事件驱动网络框架,用于快速开发高性能、高可靠的服务端/客户端。

它解决的是原生 NIO 的问题:

  • Selector 空轮询、连接断线重连等边界要自己处理
  • Buffer 拆分粘包/半包要自己设计协议
  • 线程模型、Pipeline 编排复杂

9. Netty 核心概念#

组件作用
EventLoopGroup事件循环线程组,boss 接连接,worker 处理 IO
ServerBootstrap / Bootstrap服务端/客户端启动辅助类
Channel网络连接抽象
ChannelPipelineHandler 链,像过滤器/拦截器
ChannelHandler真正业务逻辑,如解码、鉴权、路由

img
img

典型服务端骨架:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleServerHandler());
}
});
b.bind(8000).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}

业务代码通常写在继承 ChannelInboundHandlerAdapter 的 Handler 里,类似 Web 框架里的 Controller。

常见陷阱与错误示例#

1. 把 NIO 当成“异步 IO”#

NIO 的 read()/write() 本身仍是同步的,只是通过多路复用减少阻塞等待;真正异步是 AIO / Netty 的事件回调模型。

2. Buffer 忘记 flip#

写满 Buffer 后直接读,或读完不 clear(),会导致数据错乱。

3. Selector 处理完不 remove key#

selectedKeys 里的 key 处理完要 iterator.remove(),否则可能重复处理。

4. 高并发仍用 BIO 一连接一线程#

连接上千后,线程栈内存和上下文切换会成为瓶颈。

面试速查(35 题精简版)#

IO 与模型#

  1. 什么是 IO:程序通过流/通道与外部设备(文件、网络)交换数据。
  2. 同步 vs 异步:同步需自己等结果;异步提交后由系统回调通知。
  3. 阻塞 vs 非阻塞:阻塞时线程挂起等待;非阻塞时立刻返回,可干别的。
  4. 什么是 BIO:同步阻塞,常一连接一线程。
  5. 什么是 NIO:同步非阻塞,Channel + Buffer + Selector 多路复用。
  6. 什么是 AIO:异步非阻塞,JDK 1.7+ NIO.2,基于回调。
  7. 什么是 Netty:基于 NIO 的事件驱动网络框架。
  8. BIO/NIO/AIO 区别:见上文对比表;BIO 面向流,NIO 面向 Buffer/Channel。
  9. IO 流怎么分类:流向、字节/字符、节点流/处理流(详见 IO 基础笔记)。
  10. 五种 IO 模型:阻塞、非阻塞、多路复用、信号驱动、异步。
  11. 内核空间:OS 高权限内存,应用 IO 需系统调用进入内核。

基础 IO 补充#

  1. Bit / Byte / Char:Bit 最小二进制位;Byte 8 bit;Java char 16 bit Unicode。
  2. 序列化 / 反序列化:对象 ↔ 二进制;实现 Serializable,建议自定义 serialVersionUID
  3. serialVersionUID 作用:反序列化时校验类版本,不一致抛 InvalidClassException
  4. BufferedReader 属于哪种流:字符处理流,带缓冲,常用 readLine()
  5. 流超类InputStreamOutputStreamReaderWriter
  6. 二进制为何用字节流:文件/图片/音视频底层都是字节存储。

网络 IO 演进#

  1. BIO 网络问题accept/read 阻塞,单线程时其他连接饿死。
  2. 多线程 BIO:每连接一线程,连接多时资源耗尽。
  3. 线程池 BIO:缓解线程创建,但线程仍阻塞在 IO 上。
  4. NIO 如何解决:Selector 监听多连接,就绪再处理。
  5. Channel 特点:双向、可非阻塞、配合 Buffer。
  6. Selector 作用:注册多个 Channel,轮询就绪事件。
  7. Buffer 作用:数据中转数组,减少直接碰内核次数。

Netty#

  1. Netty 与 NIO 关系:Netty 封装并增强 NIO,简化 API。
  2. 为什么用 Netty:高性能、成熟线程模型、Pipeline、粘包半包、社区生态。
  3. EventLoopGroup 做什么:事件循环线程,处理 IO 事件。
  4. ChannelPipeline 做什么:Handler 责任链,编解码 + 业务。
  5. ChannelHandler 做什么:处理读/写/异常等事件,写业务逻辑。
  6. Boss / Worker 分工:Boss 接连接,Worker 处理 IO 与业务(经典双 Group 模型)。
  7. Netty 是否异步:是事件驱动异步模型,底层基于 NIO。

选型#

  1. 什么时候用 BIO:连接少、开发简单、内网低并发。
  2. 什么时候用 NIO:高并发、短连接、轻量请求。
  3. 什么时候用 AIO:连接多且 IO 重,且运行环境对 AIO 支持好(实际 Netty 更常见)。
  4. 生产环境常见选择:网络服务多数用 Netty(或 Spring WebFlux 等基于 NIO 的框架)。

面试高频问题#

1. BIO、NIO、AIO 有什么区别#

BIO 同步阻塞,一连接一线程;NIO 同步非阻塞,Selector 多路复用;AIO 异步非阻塞,内核完成后回调。

2. 为什么高并发网络服务不推荐 BIO#

线程阻塞在 IO 上,连接数增长会导致大量线程和上下文切换,CPU 浪费在等待而非计算。

3. NIO 三大组件是什么#

Channel、Buffer、Selector。

4. Netty 解决了 NIO 的什么问题#

简化多路复用、线程模型、协议编解码、连接生命周期和粘包半包等工程问题。

一句话总结#

文件 IO 先学流和 Files;网络 IO 要分清 BIO/NIO/AIO 模型,理解 Selector 多路复用,生产级高并发网络开发重点掌握 Netty 的事件驱动与 Pipeline 模型。

文章目录

文章目录