BIO,NIO,AIO
IO模型
一共有五种IO模型
- 阻塞IO模型
- 非阻塞IO模型
- IO多路复用模型
- IO模型之信号驱动模型
- IO 模型之异步IO(AIO)
BIO(Blocking IO)

1 package com.tuling.bio; 2 3 import java.io.IOException; 4 import java.net.ServerSocket; 5 import java.net.Socket; 6 7 public class SocketServer { 8 public static void main(String[] args) throws IOException { 9 ServerSocket serverSocket = new ServerSocket(9000); 10 while (true) { 11 System.out.println("等待连接。。"); 12 //阻塞方法 13 Socket clientSocket = serverSocket.accept(); 14 System.out.println("有客户端连接了。。"); 15 handler(clientSocket); 16 17 /*new Thread(new Runnable() { 18 @Override 19 public void run() { 20 try { 21 handler(clientSocket); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 } 26 }).start();*/ 27 } 28 } 29 30 private static void handler(Socket clientSocket) throws IOException { 31 byte[] bytes = new byte[1024]; 32 System.out.println("准备read。。"); 33 //接收客户端的数据,阻塞方法,没有数据可读时就阻塞 34 int read = clientSocket.getInputStream().read(bytes); 35 System.out.println("read完毕。。"); 36 if (read != -1) { 37 System.out.println("接收到客户端的数据:" + new String(bytes, 0, read)); 38 } 39 clientSocket.getOutputStream().write("HelloClient".getBytes()); 40 clientSocket.getOutputStream().flush(); 41 } 42 }
1 //客户端代码 2 public class SocketClient { 3 public static void main(String[] args) throws IOException { 4 Socket socket = new Socket("localhost", 9000); 5 //向服务端发送数据 6 socket.getOutputStream().write("HelloServer".getBytes()); 7 socket.getOutputStream().flush(); 8 System.out.println("向服务端发送数据结束"); 9 byte[] bytes = new byte[1024]; 10 //接收服务端回传的数据 11 socket.getInputStream().read(bytes); 12 System.out.println("接收到服务端的数据:" + new String(bytes)); 13 socket.close(); 14 } 15 }
NIO(Non Blocking IO)
1 package com.tuling.nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.ServerSocketChannel; 7 import java.nio.channels.SocketChannel; 8 import java.util.ArrayList; 9 import java.util.Iterator; 10 import java.util.List; 11 12 public class NioServer { 13 14 // 保存客户端连接 15 static List<SocketChannel> channelList = new ArrayList<>(); 16 17 public static void main(String[] args) throws IOException, InterruptedException { 18 19 // 创建NIO ServerSocketChannel,与BIO的serverSocket类似 20 ServerSocketChannel serverSocket = ServerSocketChannel.open(); 21 serverSocket.socket().bind(new InetSocketAddress(9000)); 22 // 设置ServerSocketChannel为非阻塞 23 serverSocket.configureBlocking(false); 24 System.out.println("服务启动成功"); 25 26 while (true) { 27 // 非阻塞模式accept方法不会阻塞,否则会阻塞 28 // NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数 29 SocketChannel socketChannel = serverSocket.accept(); 30 if (socketChannel != null) { // 如果有客户端进行连接 31 System.out.println("连接成功"); 32 // 设置SocketChannel为非阻塞 33 socketChannel.configureBlocking(false); 34 // 保存客户端连接在List中 35 channelList.add(socketChannel); 36 } 37 // 遍历连接进行数据读取 38 Iterator<SocketChannel> iterator = channelList.iterator(); 39 while (iterator.hasNext()) { 40 SocketChannel sc = iterator.next(); 41 ByteBuffer byteBuffer = ByteBuffer.allocate(128); 42 // 非阻塞模式read方法不会阻塞,否则会阻塞 43 int len = sc.read(byteBuffer); 44 // 如果有数据,把数据打印出来 45 if (len > 0) { 46 System.out.println("接收到消息:" + new String(byteBuffer.array())); 47 } else if (len == -1) { // 如果客户端断开,把socket从集合中去掉 48 iterator.remove(); 49 System.out.println("客户端断开连接"); 50 } 51 } 52 } 53 } 54 }
IO多路复用模型
IO多路复用就是,等到内核数据准备好了,主动通知应用进程再去进行系统调用。
IO复用模型核心思路:系统给我们提供一类函数(如我们耳濡目染的select、poll、epoll函数),它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用进程再发起recvfrom系统调用
IO多路复用之select
应用进程通过调用select函数,可以同时监控多个fd,在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时应用进程再发起recvfrom请求去读取数据。
非阻塞IO模型(NIO)中,需要N(N>=1)次轮询系统调用,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。
但是呢,select有几个缺点:
- 监听的IO最大连接数有限,在Linux系统上一般为1024。
- select函数返回后,是通过遍历fdset,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)
因为存在连接数限制,所以后来又提出了poll。与select相比,poll解决了连接数限制问题。但是呢,select和poll一样,还是需要通过遍历文件描述符来获取已经就绪的socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。
IO多路复用之epoll
为了解决select/poll存在的问题,多路复用模型epoll诞生,它采用事件驱动来实现
epoll先通过epoll_ctl()来注册一个fd(文件描述符),一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉了遍历文件描述符的坑爹操作,而是采用监听事件回调的机制。这就是epoll的亮点。
1 package com.tuling.nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SelectionKey; 7 import java.nio.channels.Selector; 8 import java.nio.channels.ServerSocketChannel; 9 import java.nio.channels.SocketChannel; 10 import java.util.Iterator; 11 import java.util.Set; 12 13 public class NioSelectorServer { 14 15 public static void main(String[] args) throws IOException, InterruptedException { 16 17 // 创建NIO ServerSocketChannel 18 ServerSocketChannel serverSocket = ServerSocketChannel.open(); 19 serverSocket.socket().bind(new InetSocketAddress(9000)); 20 // 设置ServerSocketChannel为非阻塞 21 serverSocket.configureBlocking(false); 22 // 打开Selector处理Channel,即创建epoll 23 Selector selector = Selector.open(); 24 // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣 25 serverSocket.register(selector, SelectionKey.OP_ACCEPT); 26 System.out.println("服务启动成功"); 27 28 while (true) { 29 // 阻塞等待需要处理的事件发生 30 selector.select(); 31 32 // 获取selector中注册的全部事件的 SelectionKey 实例 33 Set<SelectionKey> selectionKeys = selector.selectedKeys(); 34 Iterator<SelectionKey> iterator = selectionKeys.iterator(); 35 36 // 遍历SelectionKey对事件进行处理 37 while (iterator.hasNext()) { 38 SelectionKey key = iterator.next(); 39 // 如果是OP_ACCEPT事件,则进行连接获取和事件注册 40 if (key.isAcceptable()) { 41 ServerSocketChannel server = (ServerSocketChannel) key.channel(); 42 SocketChannel socketChannel = server.accept(); 43 socketChannel.configureBlocking(false); 44 // 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件 45 socketChannel.register(selector, SelectionKey.OP_READ); 46 System.out.println("客户端连接成功"); 47 } else if (key.isReadable()) { // 如果是OP_READ事件,则进行读取和打印 48 SocketChannel socketChannel = (SocketChannel) key.channel(); 49 ByteBuffer byteBuffer = ByteBuffer.allocate(128); 50 int len = socketChannel.read(byteBuffer); 51 // 如果有数据,把数据打印出来 52 if (len > 0) { 53 System.out.println("接收到消息:" + new String(byteBuffer.array())); 54 } else if (len == -1) { // 如果客户端断开连接,关闭Socket 55 System.out.println("客户端断开连接"); 56 socketChannel.close(); 57 } 58 } 59 //从事件集合里删除本次处理的key,防止下次select重复处理 60 iterator.remove(); 61 } 62 } 63 } 64 }

Epoll函数详解
1 struct epoll_event { 2 __uint32_t events; /* Epoll events */ 3 epoll_data_t data; /* User data variable */ 4 }; 5 6 typedef union epoll_data { 7 void *ptr; 8 int fd; 9 __uint32_t u32; 10 __uint64_t u64; 11 } epoll_data_t;
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

Redis线程模型
AIO(NIO 2.0)
1 package com.tuling.aio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.AsynchronousServerSocketChannel; 7 import java.nio.channels.AsynchronousSocketChannel; 8 import java.nio.channels.CompletionHandler; 9 10 public class AIOServer { 11 12 public static void main(String[] args) throws Exception { 13 final AsynchronousServerSocketChannel serverChannel = 14 AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000)); 15 16 serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { 17 @Override 18 public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { 19 try { 20 System.out.println("2--"+Thread.currentThread().getName()); 21 // 再此接收客户端连接,如果不写这行代码后面的客户端连接连不上服务端 22 serverChannel.accept(attachment, this); 23 System.out.println(socketChannel.getRemoteAddress()); 24 ByteBuffer buffer = ByteBuffer.allocate(1024); 25 socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { 26 @Override 27 public void completed(Integer result, ByteBuffer buffer) { 28 System.out.println("3--"+Thread.currentThread().getName()); 29 buffer.flip(); 30 System.out.println(new String(buffer.array(), 0, result)); 31 socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes())); 32 } 33 34 @Override 35 public void failed(Throwable exc, ByteBuffer buffer) { 36 exc.printStackTrace(); 37 } 38 }); 39 } catch (IOException e) { 40 e.printStackTrace(); 41 } 42 } 43 44 @Override 45 public void failed(Throwable exc, Object attachment) { 46 exc.printStackTrace(); 47 } 48 }); 49 50 System.out.println("1--"+Thread.currentThread().getName()); 51 Thread.sleep(Integer.MAX_VALUE); 52 } 53 }
1 package com.tuling.aio; 2 3 import java.net.InetSocketAddress; 4 import java.nio.ByteBuffer; 5 import java.nio.channels.AsynchronousSocketChannel; 6 7 public class AIOClient { 8 9 public static void main(String... args) throws Exception { 10 AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open(); 11 socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get(); 12 socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes())); 13 ByteBuffer buffer = ByteBuffer.allocate(512); 14 Integer len = socketChannel.read(buffer).get(); 15 if (len != -1) { 16 System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len)); 17 } 18 } 19 }
Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器(Selector)上,多路复用器论询到连接有IO请求时才启动一个线程进行处理。
Java AIO: 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解,如之前在Apache中使用。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持,如在 Nginx,Netty中使用。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持,在成长中,Netty曾经使用过,后来放弃
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南