这篇文章给大家介绍java中BIO、NIO、AIO三者有什么区别,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
一、BIO(Blocking IO,也被称作old IO)
同步阻塞模型,一个客户端连接对应一个处理线程
对于每一个新的网络连接都会分配给一个线程,每隔线程都独立处理自己负责的输入和输出, 也被称为Connection Per Thread模式
缺点:
IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源
如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题
所谓c10k问题,指的是服务器同时支持成千上万个客户端的问题,也就是concurrent 10 000 connection
应用场景: BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。
示例代码如下:
Bio服务端
import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;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;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(多路复用器)
channel类似于流,每个channel对应一个buffer缓冲区,buffer底层就是个数组
channel 会注册到selector上,由selector根据channel读写事件的发生将其交由某个空闲的线程处理
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;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;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;public class NioSelectorServer { public static void main(String[] args) throws IOException { //创建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(); } } }}
客户端
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;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;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); } }
客户端
import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.AsynchronousSocketChannel;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模型 | 同步阻塞 | 同步非阻塞 | 异步非阻塞 |
编程难度 | 简单 | 复杂 | 复杂 |
可靠性 好 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
关于java中BIO、NIO、AIO三者有什么区别就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。