BIO、NIO、AIO 的区别
BIO
包括基于字节流的 InputStream 和 OutputStream,以及基于字符流的 Reader 和Writer。 ## NIO NonBlock-IO:构建多路复用的、同步非阻塞的 IO 操作 - Channels 其中 FileChannels 有 transferTo 和 transferFrom 两个方法,适合大文件的拷贝,避免了两次用户态和内核态的上下文切换,即"零拷贝",效率高
- Buffers
- Selectors 允许单线程处理多个 Channels。底层调用的是系统级别的 select
使用单线程的轮询事件的机制,可以高效定位到就绪的 channel,只有 select 阶段是阻塞的,可以避免大量客户端连接时频繁切换线程的开销。
select、poll 和 epoll 的区别
- select 单个进程所能够打开的最大连接数由 FD_SETSIZE 宏定义,大小是 32 个整数的大小(在 32 为机器上,大小是3232,64 位机器上,大小是 3264),我们可以对其进行修改,然后重新编译内核,但是性能无法保证,需要做进一步测试
- poll 本质上与 select 没有区别,大师它没有最大连接数的限制,因为它是基于链表来存储的
- epoll 虽然连接数有上限,但是很大,1G 内存可以打开 10 万左右的连接
FD 剧增后带来的 IO 效率问题
消息传递方式
AIO
Asynchronous IO,基于事件和回调机制,异步非阻塞 - 基于回调:实现 Completionhandler 接口,调用时触发回调函数 - 返回 Future:通过 isDone() 查看是否准备好,通过 get() 等待返回数据
BIO 例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public class BIOPlainEchoServer { public void serve(int port) throws IOException { //将 ServerSocket 绑定到指定的端口 final ServerSocket socket = new ServerSocket(port); while (true) { //阻塞直到收到客户端的连接 final Socket clientSocket = socket.accept(); System.out.println("Accepted connection from " + clientSocket); //创建一个子线程去处理客户端的请求 new Thread(new Runnable() { @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter writer = new PrintWriter(clientSocket.getOutputStream()); //从客户端读取数据并原封不动写回去 while (true) { writer.println(reader.readLine()); writer.flush(); } } catch (IOException e) { e.printStackTrace(); } } }).start();
} }
public void improvedServe(int port) throws IOException { //将 ServerSocket 绑定到指定的端口 final ServerSocket socket = new ServerSocket(port); //创建一个线程池 ExecutorService service = Executors.newFixedThreadPool(6); while (true) { //阻塞直到收到客户端的连接 final Socket clientSocket = socket.accept(); System.out.println("Accepted connection from " + clientSocket); service.execute(() -> { try { BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter writer = new PrintWriter(clientSocket.getOutputStream()); //从客户端读取数据并原封不动写回去 while (true) { writer.println(reader.readLine()); writer.flush(); } } catch (IOException e) { e.printStackTrace(); } });
} } }
|
NIO 例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public class NIOPlainEchoServer {
public void serve(int port) throws IOException { System.out.println("Listening for connection on port: " + port); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ServerSocket ss = serverSocketChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); //将 ServerSocket 绑定到指定的端口 ss.bind(address); serverSocketChannel.configureBlocking(false); Selector selector = Selector.open(); //将 channel 注册到 selector 里,并说明 selector 关注的点,这里是关注建立连接这个事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { //阻塞等待就绪的 channel,即没有与客户端建立连接前就一直轮询 selector.select(); //获取到selector里所有就绪的 SelectionKey 实例,每将一个 channel 注册到 selector 就会产生一个 SelectionKey Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); //将就绪的 SelectionKey 从 selector 中移除,因为马上就要去处理它,防止重复执行 iterator.remove();
//若 SelectionKey 处于 Acceptable 状态 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = server.accept(); System.out.println("Accepted connection from " + clientChannel); clientChannel.configureBlocking(false); //向 selector 注册 clientChannel,主要关注读写事件,并传入一个ByteBuffer实例供读写缓存 clientChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, ByteBuffer.allocate(100)); }
//若 SelectionKey 处于可读状态 if (key.isReadable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer out = (ByteBuffer) key.attachment(); //从 channel 里读取数据存入 ByteBuffer clientChannel.read(out); }
//若 SelectionKey 处于可写状态 if (key.isWritable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer out = (ByteBuffer) key.attachment(); out.flip(); //将 ByteBuffer 的数据写入 channel clientChannel.write(out); out.compact(); } } } } }
|
BIO、NIO、AIO 对比
blocking |
阻塞并同步 |
非阻塞但同步 |
非阻塞并异步 |
线程数(server:client) |
1:1 |
1:N |
0:N |
复杂度 |
简单 |
较复杂 |
复杂 |
吞吐量 |
低 |
高 |
高 |