【JAVA NIO】java NIO

        本文是博主深入学习Netty前的一些铺垫,之前只是使用Netty,用的很粗暴,导包,上网找个DEMO就直接用,对Netty中的组件了解并不深入。

        于是再此总结下基础,并对一些核心组件作如下记录:

1. 概述

java NIO核心的APIChannel,Buffer 和 Selector 

所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。

使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

2.详述

摘抄以下详细说明,摘抄地址:https://blog.csdn.net/anxpp/article/details/51512200

    JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度。实际上,“旧”的I/O包已经使用NIO重新实现过,即使我们不显式的使用NIO编程,也能从中受益。速度的提高在文件I/O和网络I/O中都可能会发生,但本文只讨论后者。

    2.1、简介
    NIO我们一般认为是New I/O(也是官方的叫法),因为它是相对于老的I/O类库新增的(其实在JDK 1.4中就已经被引入了,但这个名词还会继续用很久,即使它们在现在看来已经是“旧”的了,所以也提示我们在命名时,需要好好考虑),做了很大的改变。但民间跟多人称之为Non-block I/O,即非阻塞I/O,因为这样叫,更能体现它的特点。而下文中的NIO,不是指整个新的I/O库,而是非阻塞I/O。

    NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。

    新增的着两种通道都支持阻塞和非阻塞两种模式。

    阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。

    对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。

    下面会先对基础知识进行介绍。

    2.2、缓冲区 Buffer
    Buffer是一个对象,包含一些要写入或者读出的数据。

    在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

    缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。

    具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。

    2.3、通道 Channel
    我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。

    底层的操作系统的通道一般都是全双工的,所以全双工的Channel比流能更好的映射底层操作系统的API。

    Channel主要分两大类:

    SelectableChannel:用户网络读写
    FileChannel:用于文件操作
    后面代码会涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子类。

    2.4、多路复用器 Selector
    Selector是Java  NIO 编程的基础。

    Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

    一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。

3.Java NIO创建的过程

         以下是java 提供的API 创建NIO server处理类的过程,各个API之间处理过程如下,可以看出比传统BIO Socket编程要多出很多对象,创建过程也比较复杂。

博主简化了关键代码用于方便阅读,完整代码链接:https://blog.csdn.net/the_fool_/article/details/80700558

//创建选择器
        Selector selector = Selector.open();
        //打开ServerSocketChannel,监听客户端连接
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
        serverChannel.configureBlocking(false);
        //绑定端口 backlog设为1024
        serverChannel.socket().bind(new InetSocketAddress(port), 1024);
        //监听客户端连接请求
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            //无论是否有读写事件发生,selector每隔1s被唤醒一次
            selector.select(1000);
            //返回已此通道已准备就绪的键集
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            SelectionKey key = null;
            //处理所有的
            while (it.hasNext()) {
                key = it.next();
                it.remove();
                //处理Key中的信息
                if (key.isValid()) {
                    //处理新接入的请求消息
                    if (key.isAcceptable()) {
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        //通过ServerSocketChannel的accept创建SocketChannel实例
                        //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
                        SocketChannel sc = ssc.accept();
                        //设置为非阻塞的
                        sc.configureBlocking(false);
                        //注册为读
                        sc.register(selector, SelectionKey.OP_READ);
                    }
                    //读消息
                    if (key.isReadable()) {
                        SocketChannel sc = (SocketChannel) key.channel();
                        //创建ByteBuffer,并开辟一个1M的缓冲区
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取请求码流,返回读取到的字节数
                        int readBytes = sc.read(buffer);
                        //读取到字节,对字节进行编解码
                        if (readBytes > 0) {
                            //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
                            buffer.flip();
                            //根据缓冲区可读字节数创建字节数组
                            byte[] bytes = new byte[buffer.remaining()];
                            //将缓冲区可读字节数组复制到新建的数组中
                            buffer.get(bytes);
                            String expression = new String(bytes, "UTF-8");
                            System.out.println("SERVER GET MSSG:" + expression);
                            //处理数据
                            String result = "server got ";
                            //发送应答消息
                            responseToClient(sc, result);
                        }

                        //链路已经关闭,释放资源

                    }
                }
            }
        }


 

猜你喜欢

转载自blog.csdn.net/the_fool_/article/details/83000648