什么是NIO?

NIO(non blocking IO),是JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞IO;

NIO相关类都被放在 java.nio包及子包下,并且对原java.io包中的很多类进行改写;

NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器) ,在前面分别已经详细介绍过了;

NIO是面向缓冲区或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络;

NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情;

通俗理解就是NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。

BIO与NIO比较

  • BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多;
  • BIO 是阻塞的,NIO则是非阻塞的;
  • BIO基于字节流和字符流进行操作,而 NIO 基于Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道;

ServerSocketChannel API

ServerSocketChannel 在服务器端监听新的客户端Socket连接

方法名

描述

public static ServerSocketChannel open()

得到一个 ServerSocketChannel 通道

public final ServerSocketChannel bind(SocketAddress local)

设置服务器端端口号

public final SelectableChannel configureBlocking(boolean block)

设置阻塞或非阻塞模式,取值

false 表示采用非阻塞模式

public SocketChannel accept()

接收一个连接,返回代表这个连接的通道对象

public final SelectionKey register(Selector sel, int ops)

注册一个选择器并设置监听事件

SocketChannel API

SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区

方法名

描述

public static SocketChannel open()

得到一个 SocketChannel 通道

public final SelectableChannel configureBlocking(boolean block)

设置阻塞或非阻塞模式,取值false 表示采用非阻塞模式

public boolean connect(SocketAddress remote)

连接服务器

public boolean finishConnect()

如果上面的方法连接失败,接下来就要通过该方法完成连接操作

public int write(ByteBuffer src)

往通道里写数据

public int read(ByteBuffer dst)

从通道里读数据

public final SelectionKey register(Selector sel, int ops, Object att)

注册一个选择器并设置监听事件,最后一个参数可以设置共享数

public final void close()

关闭通道

NIO三大核心关系

示意图:

java 非阻塞队列 demo java nio非阻塞体现在哪_数据

关系说明:

  1. 每个channel都会对应一个Buffer
  2. Selector 对应一个线程, 一个线程对应多个channel(连接)
  3. 程序切换到哪个channel 是有事件决定的, Event就是一个重要的概念
  4. Selector 会根据不同的事件,在各个通道上切换
  5. Buffer 就是一个内存块 , 底层是有一个数组
  6. 数据的读取写入是通过Buffer, 这个和BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是NIO的Buffer是可以读也可以写, 需要flip方法切换
  7. channel 是双向的, 可以返回底层操作系统的情况, 比如Linux操作系统底层通道就是双向的.

NIO非阻塞网络编程原理分析

示意图:

java 非阻塞队列 demo java nio非阻塞体现在哪_数据_02


原理说明:

  1. 当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel
  2. Selector 进行监听 select 方法, 返回有事件发生的通道的个数
  3. 将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel
  4. 注册后返回一个 SelectionKey, 会和该Selector 关联(集合)
  5. 进一步得到各个 SelectionKey(有事件发生)
  6. 在通过SelectionKey反向获取 SocketChannel,方法 channel()
  7. 可以通过得到的 channel, 完成业务处理

代码示例:

public class NIOServer {

    public static void main(String[] args) throws Exception{
        // 创建一个ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 创建一个Selector
        Selector selector = Selector.open();

        // 绑定端口并开启服务端监听
        InetSocketAddress inetSocketAddress = new InetSocketAddress(8000);
        serverSocketChannel.socket().bind(inetSocketAddress);
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        // 将ServerSocketChannel注册到selector,事件类型为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true){ // 循环等待客户端的连接
            if(selector.select(1000) == 0){ // 阻塞1秒,如果没有事件发生,则结束此次循环
                System.out.println("还未有客户端进行连接。。。");
                continue;

            }

            // 获取关注的事件集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 根据selectionKey的事件类型分别进行处理
                if (selectionKey.isAcceptable()) { // OP_ACCEPT 有新的客户端连接
                    // 为该客户端生成一个socketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 设置通道为非阻塞
                    socketChannel.configureBlocking(false);

                    // 将socketChannel注册到Selector,事件类型为OP_READ,同时给socketChannel关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                } else if (selectionKey.isReadable()) { // OP_READ 有读的事件
                    // 通过key反向获取channel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    // 获取到该channel的buffer
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    socketChannel.read(byteBuffer);

                    System.out.println("客户端发送的内容:" + new String(byteBuffer.array()));
                }

                // 处理完后手动移除此selectionKey
                iterator.remove();
            }
        }
    }
}
public class NIOClient {

    public static void main(String[] args) throws Exception{
        // 创建一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞
        socketChannel.configureBlocking(false);

        // 服务端地址和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8000);
        if(!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()) {
                System.out.println("客户端正在连接。。。。。");
            }
        }

        String msg = "hello,2020";
        ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
        socketChannel.write(byteBuffer);

        System.in.read();
    }
}