JavaWeb开发|Java中的BIO和NIO区别


文章目录

  • Java中的NIO和BIO
    • BIO原理
    • NIO原理
  • select()与epoll()
    • select()与epoll()、poll的区别
    • select()缺点
    • epoll()优点

Java中的NIO和BIO 首先我们先了解一下,阻塞(Block)和非阻塞(Non-Block).
阻塞:往往需要等待数据缓冲区的数据准备好以后才处理其它事情,否则一致等待在哪里。
非阻塞:当进程访问我们的数据缓冲区的时候,如果数据没有准备好就立即返回,不会等待。如果数据以及准备好,也直接返回。
阻塞和非阻塞是进程在访问数据缓冲区的时候,数据是否准备就绪的一种处理方式。
同步: 在同一时间只能做一件事情。
异步: 在处理数据的时候,同一时间点能做多个处理。
NIO:同步非阻塞IO。
BIO:同步阻塞IO。
AIO:异步非阻塞IO。
BIO原理
public class Server { final static int PROT = 8765; public static void main(String[] args) {ServerSocket server = null; try { server = new ServerSocket(PROT); System.out.println(" server start .. "); //等待客户端的连接,阻塞方法 //socket对象是数据发送者在服务端的引用 Socket socket = server.accept(); //进行阻塞 //新建一个线程执行客户端的任务 new Thread(new ServerHandler(socket)).start(); //传统的TCP点对点直连接的方式,每一个连接在server端都要创建一个线程; // windows最大支持1000个线程,unix最大支持2000个线程; } catch (Exception e) { e.printStackTrace(); } finally { if(server != null){ try { server.close(); } catch (IOException e) { e.printStackTrace(); } } server = null; } }

BIO会在代码 Socket socket = server.accept(); 处进行阻塞,这个时候正在等待连接和接收数据。
NIO原理
public class Server implements Runnable{ //1 多路复用器(管理所有的通道) private Selector seletor; //2 建立缓冲区 private ByteBuffer readBuf = ByteBuffer.allocate(1024); //3 private ByteBuffer writeBuf = ByteBuffer.allocate(1024); public Server(int port){ try { //1 打开路复用器(选择器) this.seletor = Selector.open(); //2 打开服务器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //3 设置服务器通道为非阻塞模式 ssc.configureBlocking(false); //4 绑定地址 ssc.bind(new InetSocketAddress(port)); //5 把服务器通道注册到多路复用器上,并且监听阻塞事件 ssc.register(this.seletor, SelectionKey.OP_ACCEPT); System.out.println("Server start, port :" + port); } catch (IOException e) { e.printStackTrace(); } } private void accept(SelectionKey key) { try { //1 获取服务通道 ServerSocketChannel ssc =(ServerSocketChannel) key.channel(); //2 执行阻塞方法 SocketChannel sc = ssc.accept(); //3 设置阻塞模式 为非阻塞 sc.configureBlocking(false); //4 注册读事件(服务端一般不注册 可写事件) sc.register(this.seletor, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { while(true){ try { //1 必须要让多路复用器开始监听(轮询) this.seletor.select(); //2 返回多路复用器已经选择的结果集 Iterator keys = this.seletor.selectedKeys().iterator(); //3 进行遍历 while(keys.hasNext()){ //4 获取一个选择的元素 SelectionKey key = keys.next(); //5 直接从容器中移除就可以了 keys.remove(); //6 如果是有效的 if(key.isValid()){ //7 如果为阻塞状态 if(key.isAcceptable()){ this.accept(key); } //8 如果为可读状态 if(key.isReadable()){ this.read(key); } //9 写数据 //if(key.isWritable()){ //this.write(key); //ssc //} } } } catch (IOException e) { e.printStackTrace(); } } } private void write(SelectionKey key){ //ServerSocketChannel ssc =(ServerSocketChannel) key.channel(); //服务端一般不注册 可写事件 //ssc.register(this.seletor, SelectionKey.OP_WRITE); } private void read(SelectionKey key) { try { //1 清空缓冲区旧的数据 this.readBuf.clear(); //2 获取之前注册的socket通道对象 SocketChannel sc = (SocketChannel) key.channel(); //3 读取数据 int count = sc.read(this.readBuf); //4 如果没有数据 if(count == -1){ key.channel().close(); key.cancel(); return; } //5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位) this.readBuf.flip(); //6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据 byte[] bytes = new byte[this.readBuf.remaining()]; //7 接收缓冲区数据 this.readBuf.get(bytes); //8 打印结果 String body = new String(bytes).trim(); System.out.println("Server : " + body); // 9..可以写回给客户端数据 } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(new Server(8765)).start(); ; } }

NIO多了一个ByteBuffer缓冲区,所以实现了非阻塞。
NIO底层利用了select()/epoll()。
select()与epoll() 早期1.6版本,NIO底层实现用的是select()。
jdk1.8以后NIO底层用的是epoll()。
select()与epoll()、poll的区别 select():—>O(n)
它仅仅知道有I/O事件发生了,却不知道具体是哪个流。只能做轮训,找出能够读出数据或写入数据的流。
select()具有O(n)的无差别轮训的复杂度。
poll():—>O(n)
poll()本质上和select()没有区别,它将用户传入的数据从用户空间拷贝到内核空间。
相比select()它没有最大连接数限制(底层是由链表实现的)。
epoll():—>时间复杂度O(1)
epoll()会把哪个流发生了I/O事件通知我们,所以epoll()是基于事件驱动的。
1)select()、poll()、epoll()都是I/O多路复用机制,可以监视多个文件描述符。一旦描述符就绪,就能通知程序进行相应的读写操作。
2)select()、poll()、epoll() 本质上都是同步I/O,因为他们都需要在读写事件就绪后,自己负责进行读写。这个读写过程是 ‘‘阻塞的’’。非阻塞指的是,数据流在写入、读取ByteBuffer的时候。
3)AIO(异步非阻塞I/O):无需自己负责进行读写,异步I/O会把数据从内核拷贝到用户空间。
select()缺点 1)单个进程能够监听的文件描述符fd有限,默认是1024。
2)每次调用select都会把描述符fd由内核空间---->用户空间的拷贝。
3)使用轮训的方式,需要传进来所有的fd。
epoll()优点 【JavaWeb开发|Java中的BIO和NIO区别】没有监听的Fd描述符,远大于1024.一般1G内存,默认是10万。

    推荐阅读