一、简介

java传统I/0中一个核心的概念是流(Stream),是面向流的编程。而且数据的传输是以字节为单位的。这一块相信大家相对会比较熟悉,就不做详细介绍了。java传统的I/O又叫做阻塞的I/O,这只要是相对于网络编程来说的。

java 在1.4引入了NIO。NIO中拥有3个核心概念:Selector,Channel与Buffer。而与传统I/O不同的是,NIO是面向块来编程的,也就是数据的传输的单位并不是字节流,而是基于字节数组,也就是上面提到的Buffer。

二、为什么要引入NIO?

Java既然在1.4引入了NIO,那肯定是传统的IO存在很多的不足。
java传统IO的不足:

  1. 数据的传输是以字节为单位的。然而底层操作系统都支持块(block)来传输了。这种以字节为单位的传输势必会造成一定的性能影响。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区,不够灵活。
  2. 传统的IO是阻塞的。当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
  3. 基于传统的IO的网络编程,每有一个客户端发起链接,服务端就会起一个线程来进行IO处理。当客户端很多时,服务端就会产生大量的线程,而且这些线程并不会一直处于运行状态,这样就会产生了大量的资源浪费。还有服务端能够分配的线程资源有限,不可能无限的分配线程资源来处理客户端的链接。再者大量的线程上下文的切换也会带来大量的开销影响新能。

基于以上这些原因,java引入了NIO。

三、Java NIO

Java NIO主要由三部分组成:Selector,Channel与Buffer。

1. Buffer

Buffer本身就是一块内存,底层实现上它实际上是一个数组,数组的读写都是通过Buffer来实现的。
除了数组之外,Buffer还提供了对于数据的结构化访问方式,并且可以追踪到系统的读写过程。
Java中的7中原生数据类型都有各自对应的Buffer类型,如IntBuffer,LongBuffer,CharBuffer,ByteBuffer等等,并没有BooleanBuffer类型。

关于Nio Buffer中的3个重要状态属性的含义:position,limit与capacity。position,limit与capacity存在以下关系:
0 <= mark <= position <= limit <= capacity
Buffer提供的一些API 方法,底层实现使用的就是这几个状态属性指针。API就不具体介绍了,感兴趣的可自行查看Java API。
有一个特别值得注意的是,Buffer在进行读写模式切换时,必须要调用它的flip方法。至于为什么,这个就需要理解上面提到的几个变量的含义了。

2. Channel

Channel指的是可以向其写入数据或是从中读取数据的对象,它类似于java.io中最为核心的一个概念是流(Stream)。只不过传统IO中流(stream)要么是输入流(只能从中读取数据),要么是输出流(只能向其写出数据),不可能既是输入流又是输出流。而Channel则不同,它是一种全双工的,既可以从中读取数据,也可以向其写出数据。

并且,所有数据的读写都是通过Buffer来进行的,永远不会出现直接向Channel写入数据的情况,或是直接从Channel读取数据的情况。

可以这么理解,Channel就是用来传输数据的,而Buffer就是Channel传输数据的载体。

3. Selector

该组件主要会被网络编程而用到。Selector是Java的非阻塞IO实现的关键。Selector在进行select操作时,操作系统会找出已经准备好的IO进行处理,没有准备好的不处理也不会产生阻塞。所以只需要一个线程,不断循环的使用Selector对注册其上的Channel进行select操作,对准备好的IO进行处理。这样就实现了一种非阻塞的IO。并且在这种模式下,服务端只需要一个线程就能处理多个客户端发起的连接。这也就解决了前面说的传统IO在连接的客户端太多而产生服务端线程过多的问题。

下面有两个原理图,分别讲述了传统IO和NIO的网络编程模型。

传统IO网络编程模型图:

io和nio原理 java java中io和nio_客户端

NIO网络编程模型图

io和nio原理 java java中io和nio_io和nio原理 java_02

下面在给出NIO网络编程服务端代码(客户端类似):

public class NioTest {

    public static void main(String[] args) throws Exception {
        int[] ports = new int[5];

        ports[0] = 5000;
        ports[1] = 5001;
        ports[2] = 5002;
        ports[3] = 5003;
        ports[4] = 5004;

        Selector selector = Selector.open();

        for (int i = 0; i < ports.length; i++) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 配置非阻塞的方式
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();
            InetSocketAddress address = new InetSocketAddress(ports[i]);
            serverSocket.bind(address);

            // 通道注册到Selector上
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("监听端口: " + ports[i]);
        }

        while (true) {
            int numbers = selector.select();
            System.out.println("numbers: " + numbers);

            if (numbers <= 0)
                continue;

            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            System.out.println("selectedKeys: " + selectionKeys);

            Iterator<SelectionKey> iter = selectionKeys.iterator();

            while (iter.hasNext()) {
                SelectionKey selectionKey = iter.next();
                // 事件是accept
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 连接好了,将这个读通道注册到Selector上
                    socketChannel.register(selector, SelectionKey.OP_READ);

                    System.out.println("获得客户端连接: " + socketChannel);
                }

                if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    int bytesRead = 0;
                    while (true) {
                        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

                        byteBuffer.clear();

                        int read = socketChannel.read(byteBuffer);
                        if (read <= 0) {
                            break;
                        }

                        byteBuffer.flip();

                        socketChannel.write(byteBuffer);

                        bytesRead += read;
                    }

                    System.out.println("读取: " + bytesRead + ", 来自于: " + socketChannel);

                }
                iter.remove();
            }
        }

    }
}

public class NioTest {

    public static void main(String[] args) throws Exception {
        int[] ports = new int[5];

        ports[0] = 5000;
        ports[1] = 5001;
        ports[2] = 5002;
        ports[3] = 5003;
        ports[4] = 5004;

        Selector selector = Selector.open();

        for (int i = 0; i < ports.length; i++) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 配置非阻塞的方式
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();
            InetSocketAddress address = new InetSocketAddress(ports[i]);
            serverSocket.bind(address);

            // 通道注册到Selector上
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("监听端口: " + ports[i]);
        }

        while (true) {
            int numbers = selector.select();
            System.out.println("numbers: " + numbers);

            if (numbers <= 0)
                continue;

            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            System.out.println("selectedKeys: " + selectionKeys);

            Iterator<SelectionKey> iter = selectionKeys.iterator();

            while (iter.hasNext()) {
                SelectionKey selectionKey = iter.next();
                // 事件是accept
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 连接好了,将这个读通道注册到Selector上
                    socketChannel.register(selector, SelectionKey.OP_READ);

                    System.out.println("获得客户端连接: " + socketChannel);
                }

                if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    int bytesRead = 0;
                    while (true) {
                        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

                        byteBuffer.clear();

                        int read = socketChannel.read(byteBuffer);
                        if (read <= 0) {
                            break;
                        }

                        byteBuffer.flip();

                        socketChannel.write(byteBuffer);

                        bytesRead += read;
                    }

                    System.out.println("读取: " + bytesRead + ", 来自于: " + socketChannel);

                }
                iter.remove();
            }
        }

    }
}