【Java基础】IO多路复用
2025/12/24...大约 3 分钟
BIO、NIO、IO多路复用
一、BIO
即Blocking IO,同步阻塞通信,每次有新的链接与服务端链接后,服务端链接的线程会阻塞住,一直到通信完后才进行其他线程。
二、NIO
即Non-Blocking IO,同步非阻塞通信,这是基于Selector和Epoll来实现的,Selector代替了线程本身轮询IO事件,避免了阻塞同时减少了不必要的线程消耗。
- 初始创建
Selector selector = Selector.open(),这个会一直调用操作系统底层epoll_create创建数组并返回epfd(ep:数组,fd:文件描述,epfd就是这个数组的一个描述,供后面调用epoll_ctl时在操作系统查找数组),然后将服务端服务注册到selector里面去并设置监听服务端服务的链接事件(epoll_ctl,操作系统实现,根据epfd找到list,监听对应链接的对应事件,若有链接发生事件了(如链接,读写等),将对应链接移动到就绪事件列表)。 - 每次有新的客户端链接,都会被注册到Selector的服务端服务监听到,会把当前这个客户端链接(fd:链接的文件描述符)注册到selecor中的List(List根据epfd去操作系统找)。
- 在步骤1将服务端注册到epoll中并监听链接建立事件后,就会
selector.slect()阻塞等待有链接、读等事件发生(不会循环消耗CPU)。什么时候能停止阻塞,读取有事件的链接?- 在监听到事件(如新链接、读写等)后,selector会停止阻塞,取出有事件发生的链接,进行循环处理。若是链接事件,将此链接注册回去并监听此链接的读事件。若是读事件,就直接进行读取操作。
Selector底层原理(Linux epoll),epoll的三个核心系统,epoll_create,epoll_ctl,epoll_wait。
程序启动:
1. Selector.open() → epoll_create() // 创建epoll实例
2. ServerSocketChannel.register() → epoll_ctl() // 注册监听事件
>
事件循环:
3. selector.select() → epoll_wait() // 阻塞等待事件
4. epoll返回就绪的fd列表
5. 处理就绪事件
- 如果是ACCEPT事件:注册新连接的读事件
- 如果是READ事件:读取数据并处理1、什么时候能停止阻塞,读取有事件的链接? 操作系统的
epoll_wait会一直等到就绪事件列表有链接后。2、selector是基于epoll实现多路复用的,epoll是操作系统的。
最终实现服务端一个线程实现多个客户端链接,复用一个线程,底层就靠操作系统的epoll函数。
// NIO Java示例
public class SimpleNioServer {
public static void main(String[] args) throws IOException {
// 1. 创建Selector
Selector selector = Selector.open();
// 2. 创建ServerSocketChannel并配置
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8888));
// 3. 注册ACCEPT事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Server started on port 8888");
// 4. 事件循环
while (true) {
selector.select(); // 阻塞等待事件
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
try {
if (key.isAcceptable()) {
handleAccept(key, selector);
}
if (key.isReadable()) {
handleRead(key);
}
if (key.isWritable()) {
handleWrite(key);
}
} catch (IOException e) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
}
}