java中BIO、NIO、AIO的区别 java中BIO、NIO、AIO都有啥区别
Java面试一问一答(公众号) 人气:0一、BIO(Blocking IO,也被称作old IO)
同步阻塞模型,一个客户端连接对应一个处理线程
对于每一个新的网络连接都会分配给一个线程,每隔线程都独立处理自己负责的输入和输出, 也被称为Connection Per Thread模式
缺点:
1、IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源
2、如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题
所谓c10k问题,指的是服务器同时支持成千上万个客户端的问题,也就是concurrent 10 000 connection
应用场景: BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。
示例代码如下:
Bio服务端
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * @Title:BIO的服务端 * @Author:wangchenggong * @Date 2021/4/13 9:41 * @Description * @Version */ public class SocketServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9000); while (true){ System.out.println("等待连接..."); Socket clientSocket = serverSocket.accept(); System.out.println("客户端"+clientSocket.getRemoteSocketAddress()+"连接了!"); handle(clientSocket); } } private static void handle(Socket clientSocket) throws IOException{ byte[] bytes = new byte[1024]; int read = clientSocket.getInputStream().read(bytes); System.out.println("read 客户端"+clientSocket.getRemoteSocketAddress()+"数据完毕"); if(read != -1){ System.out.println("接收到客户端的数据:" + new String(bytes, 0, read)); } clientSocket.getOutputStream().write("HelloClient".getBytes()); clientSocket.getOutputStream().flush(); } }
Bio客户端
import java.io.IOException; import java.net.Socket; /** * @Title:BIO的客户端 * @Author:wangchenggong * @Date 2021/4/13 9:49 * @Description * @Version */ public class SocketClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost", 9000); //向服务端发送数据 socket.getOutputStream().write("HelloServer".getBytes()); socket.getOutputStream().flush(); System.out.println("向服务端发送数据结束"); byte[] bytes = new byte[1024]; //接收服务端回传的数据 socket.getInputStream().read(bytes); System.out.println("接收到服务端的数据:" + new String(bytes)); socket.close(); } }
二、NIO(Non Blocking IO,本意也作new IO)
同步非阻塞,服务器实现模式为 一个线程可以处理多个连接请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理,是在JDK1.4开始引入的。
应用场景:NIO方式适合连接数目多且连接比较短(轻操作)的架构,比如聊天服务器、弹幕系统、服务器之间通讯,编程相对复杂。
NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)
1.channel类似于流,每个channel对应一个buffer缓冲区,buffer底层就是个数组
2.channel 会注册到selector上,由selector根据channel读写事件的发生将其交由某个空闲的线程处理
3.NIO的Buffer和Channel都是可读也可写的。
NIO的代码示例有两个
没有引入多路复用器的NIO
服务端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * @Title:Nio服务端 * @Author:wangchenggong * @Date 2021/4/14 11:04 * @Description * @Version */ public class NioServer { /** * 保存客户端连接 */ static List<SocketChannel> channelList = new ArrayList<>(); public static void main(String[] args) throws IOException { //创建Nio ServerSocketChannel ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); //设置ServerSocketChannel为非阻塞 serverSocket.configureBlocking(false); System.out.println("Nio服务启动成功"); while(true){ //非阻塞模式accept方法不会阻塞 /// NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数 SocketChannel socketChannel = serverSocket.accept(); if(socketChannel != null){ System.out.println("连接成功"); socketChannel.configureBlocking(false); channelList.add(socketChannel); } Iterator<SocketChannel> iterator = channelList.iterator(); while(iterator.hasNext()){ SocketChannel sc = iterator.next(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); //非阻塞模式read方法不会阻塞 int len = sc.read(byteBuffer); if(len > 0){ System.out.println("接收到消息:" + new String(byteBuffer.array())); }else if(len == -1){ iterator.remove(); System.out.println("客户端断开连接"); } } } } }
客户端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; /** * @Title:Nio客户端 * @Author:wangchenggong * @Date 2021/4/14 11:36 * @Description * @Version */ public class NioClient { public static void main(String[] args) throws IOException { SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("localhost", 9000)); socketChannel.configureBlocking(false); ByteBuffer writeBuffer=ByteBuffer.wrap("HelloServer1".getBytes()); socketChannel.write(writeBuffer); System.out.println("向服务端发送数据1结束"); writeBuffer = ByteBuffer.wrap("HelloServer2".getBytes()); socketChannel.write(writeBuffer); System.out.println("向服务端发送数据2结束"); writeBuffer = ByteBuffer.wrap("HelloServer3".getBytes()); socketChannel.write(writeBuffer); System.out.println("向服务端发送数据3结束"); } }
引入了多路复用器的NIO
服务端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set; /** * @Title:引入多路复用器后的NIO服务端 * @Author:wangchenggong * @Date 2021/4/14 13:57 * @Description * SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了 * SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功 * SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了) * SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作) * * 1.当向通道中注册SelectionKey.OP_READ事件后,如果客户端有向缓存中write数据,下次轮询时,则会 isReadable()=true; * * 2.当向通道中注册SelectionKey.OP_WRITE事件后,这时你会发现当前轮询线程中isWritable()一直为true,如果不设置为其他事件 * @Version */ public class NioSelectorServer { public static void main(String[] args) throws IOException { /** * 创建server端,并且向多路复用器注册,让多路复用器监听连接事件 */ //创建ServerSocketChannel ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); //设置ServerSocketChannel为非阻塞 serverSocket.configureBlocking(false); //打开selector处理channel,即创建epoll Selector selector = Selector.open(); //把ServerSocketChannel注册到selector上,并且selector对客户端的accept连接操作感兴趣 serverSocket.register(selector, SelectionKey.OP_ACCEPT); System.out.println("NioSelectorServer服务启动成功"); while(true){ //阻塞等待需要处理的事件发生 selector.select(); //获取selector中注册的全部事件的SelectionKey实例 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); //遍历selectionKeys,对事件进行处理 while (iterator.hasNext()){ SelectionKey key = iterator.next(); //如果是OP_ACCEPT事件,则进行连接和事件注册 if(key.isAcceptable()){ ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); //接受客户端的连接 SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); //把SocketChannel注册到selector上,并且selector对客户端的read操作(即读取来自客户端的消息)感兴趣 socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("客户端"+socketChannel.getRemoteAddress()+"连接成功!"); }else if(key.isReadable()){ SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); int len = socketChannel.read(byteBuffer); if(len > 0){ System.out.println("接收到客户端"+socketChannel.getRemoteAddress()+"发来的消息,消息内容为:"+new String(byteBuffer.array())); }else if(len == -1){ System.out.println("客户端断开连接"); //关闭该客户端 socketChannel.close(); } } //从事件集合里删除本次处理的key,防止下次select重复处理 iterator.remove(); } } /** * NioSelectorServer服务启动成功 * 客户端/127.0.0.1:57070连接成功! * 接收到客户端/127.0.0.1:57070发来的消息,消息内容为:HelloServer * 客户端/127.0.0.1:57121连接成功! * 接收到客户端/127.0.0.1:57121发来的消息,消息内容为:HelloServer */ } }
客户端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * @Title:引入多路复用器后的NIO客户端 * @Author:wangchenggong * @Date 2021/4/14 14:39 * @Description * @Version */ public class NioSelectorClient { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); Selector selector = Selector.open(); //要先向多路复用器注册,然后才可以跟服务端进行连接 socketChannel.register(selector, SelectionKey.OP_CONNECT); socketChannel.connect(new InetSocketAddress("localhost", 9000)); while (true){ selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iterator = keys.iterator(); while (iterator.hasNext()){ SelectionKey key = iterator.next(); iterator.remove(); if (key.isConnectable()){ SocketChannel sc = (SocketChannel) key.channel(); if (sc.finishConnect()){ System.out.println("服务器连接成功"); ByteBuffer writeBuffer=ByteBuffer.wrap("HelloServer".getBytes()); sc.write(writeBuffer); System.out.println("向服务端发送数据结束"); } } } } /** * 服务器连接成功 * 向服务端发送数据结束 */ } }
三、AIO(Asynchronous IO) 即NIO2.0
异步非阻塞,由操作系统完成后回调通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
应用场景:AIO方式适用于连接数目多且连接时间较长(重操作)的架构(应用),JDK7开始支持。
著名的异步网络通讯框架netty之所以废弃了AIO,原因是:在Linux系统上,NIO的底层实现使用了Epoll,而AIO的底层实现仍使用Epoll,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优 化,Linux上AIO还不够成熟
AIO示例代码如下:
服务端
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; /** * @Title:Aio服务端 * @Author:wangchenggong * @Date 2021/4/14 17:05 * @Description * @Version */ public class AioServer { public static void main(String[] args) throws Exception { final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000)); serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { try{ System.out.println("2--"+Thread.currentThread().getName()); //接收客户端连接 serverChannel.accept(attachment,this); System.out.println("客户端"+socketChannel.getRemoteAddress()+"已连接"); ByteBuffer buffer = ByteBuffer.allocate(128); socketChannel.read(buffer, null, new CompletionHandler<Integer, Object>() { @Override public void completed(Integer result, Object attachment) { System.out.println("3--"+Thread.currentThread().getName()); //flip方法将Buffer从写模式切换到读模式 //如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0时候的字符。通过buffer.flip();这个语句,就能把buffer的当前位置更改为buffer缓冲区的第一个位置 buffer.flip(); System.out.println(new String(buffer.array(), 0, result)); socketChannel.write(ByteBuffer.wrap("hello Aio Client!".getBytes())); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); }catch(Exception e){ e.printStackTrace(); } } @Override public void failed(Throwable exc, Object attachment) { } }); System.out.println("1‐‐main"+Thread.currentThread().getName()); Thread.sleep(Integer.MAX_VALUE); } /** * 1‐‐mainmain * 2--Thread-9 * 客户端/127.0.0.1:54821已连接 * 3--Thread-8 * hello AIO server ! * 2--Thread-9 * 客户端/127.0.0.1:54942已连接 * 3--Thread-7 * hello AIO server ! */ }
客户端
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; /** * @Title:Aio客户端 * @Author:wangchenggong * @Date 2021/4/14 16:56 * @Description * @Version */ public class AioClient { public static void main(String[] args) throws Exception { //创建Aio客户端 AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open(); socketChannel.connect(new InetSocketAddress("localhost", 9000)).get(); //发送消息 socketChannel.write(ByteBuffer.wrap("hello AIO server !".getBytes())); //接收消息 ByteBuffer buffer = ByteBuffer.allocate(128); Integer len = socketChannel.read(buffer).get(); if(len != -1){ //客户端收到消息:hello Aio Client! System.out.println("客户端收到消息:"+new String(buffer.array(), 0, len)); } } }
四、总结
BIO | NIO | AIO | |
IO模型 | 同步阻塞 | 同步非阻塞 | 异步非阻塞 |
编程难度 | 简单 | 复杂 | 复杂 |
可靠性 好 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
加载全部内容