• NIO 简介


    上文我们描述了五中IO类型。第一种同步阻塞模型我们我们称之为BIO(Blocking IO), 

    第三种IO复用模型我们称之为NIO(Nonblocking IO)。

     

      上图我们可以很容易的发现 BIO会为每个socket请求创建一个线程,而NIO可以通过一个线程处理多个请求。当然,我们可以为BIO构建一个线程池,这是一种伪异步的BIO模型。BIO和NIO最大的区别还是在阻塞上面。

    阻塞主要有两方面

    • 等待网络可读写  server.accept()
    • 读写阻塞

    通过观察InputStream的Api我们可以了解到,只有在下面三种情况下,BIO才会解除阻塞

    1.有数据可读
    2.可用数据已读取完毕
    3.发送空指针或者I/O异常

    所以,假如我们使用BIO进行网络消息传递,在网络不稳定的情况下,一次消息的传递需要花费30s,那这个bio的线程就需要阻塞30秒,假如所有的线程都阻塞30s,那系统基本就不可用了。

    基于上述的问题,java推出了NIO。我们先用一段代码看看NIO的编程

    public static void main(String[] args) throws Exception {
            // 打开一个ServerSocketChannel
            ServerSocketChannel socketChannel = ServerSocketChannel.open();
            socketChannel.configureBlocking(Boolean.FALSE);
            // 获取ServerSocketChannel绑定的Socket
            ServerSocket socket = socketChannel.socket();
            // 设置ServerSocket监听的端口
            socket.bind(new InetSocketAddress(PORT));
            System.out.println("开始等待客户端连接");
            // 打开一个选择器
            Selector selector = Selector.open();
            // 将ServerSocketChannel注册到选择器上去并监听accept事件
            socketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                // 这里会发生阻塞,等待就绪的通道
                int select = selector.select();
                // 没有就绪的通道则什么也不做
                if (select == 0) {
                    continue;
                }
                // 获取SelectionKeys上已经就绪的通道的集合
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                // 遍历每一个Key
                while (iterator.hasNext()){
                    SelectionKey next = iterator.next();
                    if (next.isAcceptable()){
                        ServerSocketChannel channel = (ServerSocketChannel) next.channel();
                        SocketChannel socketChannel1 = channel.accept();
                        socketChannel1.register(selector,SelectionKey.OP_READ);
                    }else if (next.isReadable()){
                        readDataFromSocket(next);
                    }
                    iterator.remove();
                }
            }
        }
        private static ByteBuffer bb = ByteBuffer.allocate(1024);
        private static void readDataFromSocket(SelectionKey next) throws IOException {
            SocketChannel sc = (SocketChannel)next.channel();
            bb.clear();
            while (sc.read(bb)>0){
                bb.flip();//
                //告知在当前位置和限制之间是否有元素
                while (bb.hasRemaining()){
                    System.out.println((char) bb.get());
                }
                System.out.println();
                bb.clear();
            }
        }

     java为NIO提供了全新的API,大致有以下三种

    •  缓冲区 Buffer

    一个缓冲区对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。从数据结构而言,缓冲区就是一个数组,通常是一个字节数组即ByteBuffer。每一种java基本类型都有对应的缓冲区

    •  Channel

    与socket类和SeverSocket类似。NIO提供了SocketChannelServerSocketChannel ,这两个新增的通道都支持阻塞和非阻塞模式,阻塞模式使用简单,但是性能和可靠性都不好。非阻塞模式则相反。Channel可以自由的设置阻塞对Java来说意义非常重大。试想下之前的BIO网络编程为什么一个连接必须要对应一个线程。由于NIO的channel可以设置非阻塞模式,我们完全可以通过一个线程接受多个socket请求。

    有两点需要我们注意:

    1.文件通道总是阻塞的,不能设置成非阻塞模式

    2.Channel只能往Buffer中写入

    • Selector

    选择器的作用是协调管理多个channel,selector定义了4种channel事件,每次channel注册的时候都必须定义好自己关心的是哪一种事件。注册完成后selector会一直阻塞,直到某些事件就绪。

        public static final int OP_READ = 1 << 0;
        public static final int OP_WRITE = 1 << 2;
        public static final int OP_CONNECT = 1 << 3;
        public static final int OP_ACCEPT = 1 << 4;

    在了解上述三个api之后,我们再简单分析下上述代码

    1.创建ServerSocketChannel
    2.设置ServerSocketChannel为非阻塞状态
    3.监听端口
    4.将ServerSocketChannel 注册到一个Selector
    5.等待选择接受就绪事件,一旦接收到 即可做出相应的操作

     NIO的阻塞


    如上图所示,NIO其实是有阻塞的环节的。那为什么我们仍然称NIO是同步非阻塞IO呢。这里主要涉及到一次完整的io请求是怎么进行读写的。

    所有的系统I/O都分为两个阶段:

      等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。

      对于BIO而言,如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,再阻塞的读到的数据。

      对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。

      所以,socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。这部分的阻塞相对于BIO而言,是可以忽略不计的。所以我们可以认为NIO是非阻塞的。

  • 相关阅读:
    urllib模块常用方法
    python Apscheduler持久化
    Java 递归(深度优先)寻找迷宫最短路径
    Java 访问修饰符
    Java 多态的一道例题
    性能测试基础(二)
    Update DataReader
    VS.Php Beta 4
    Free ASP.NET Web Development Tool
    使用 Microsoft .NET 的企业解决方案模式
  • 原文地址:https://www.cnblogs.com/xmzJava/p/9706672.html
Copyright © 2020-2023  润新知