1.NIO的基本概念和三大外围组件

2.NIO的缓冲区(Buffer)介绍

3.NIO的通道(Channel)介绍

4.NIO的选择器(Selector)介绍

5.NIO的原理剖析图

6.NIO的编程案例

1.NIO的基本概念和三大外围组件

1.1)NIO的基本概念
在咱们学习过后面的Netty网络编程——Netty的根本介绍与BIO当前,咱们来学习NIO。
NIO的概念:同步非阻塞

在NIO的模式中,所有都是非阻塞的

也就是说,客户端调用服务器端,仅仅能获取到目前可用的数据,如果有数据则返回数据,如果没有数据间接返回空而不是放弃线程阻塞,非要等到服务器端解决实现再返回客户端。所以直至数据变得可读以前,该线程能够做其它的事件。

而且,在NIO模式中,一个线程是能够解决多个操作的
当一个客户端与服务器进行链接,这个socket连贯就会退出到一个数组中,隔一段时间遍历一次,这样一个线程就能解决多个客户端的链接和数据了,不像之前的阻塞IO那样,一个连贯调配一个线程。

1.2)NIO的三大外围组件

其实NIO是面向缓冲区,或者面向块编程的。把数据读取到缓冲区,而后让客户端线程从缓冲区读取数据,这就减少了处理过程中的灵活性。

NIO 有三大外围局部:Channel(通道),Buffer(缓冲区), Selector(选择器)。

咱们先来看一张图:

1.2.1)服务端和客户端进行连贯之后,通过buffer(缓冲区)进行数据的交互。
1.2.2)服务端和客户端进行连贯之后,通过各自的channel(通道)进行操作缓冲区。
1.2.3)在一个Channel有事件收回后(读、写、连贯等等),selector就会切换到对应的channel进行解决数据。
1.2.4)Selector会依据不同的事件,在各个通道上进行切换
1.2.5)Buffer就是一个内存块,底层是一个数组。
1.2.6)Buffer能够读也能够写,须要调用flip办法进行切换。

2.NIO的缓冲区(Buffer)介绍
缓冲区(Buffer):
缓冲区实质上是一个能够读写数据的内存块,能够了解成是一个蕴含数字的容器对象,该对象提供了一组api,能够让开发者轻松地应用内存块。同时缓冲区也设置了一些机制,可能跟踪和记录缓冲区的状态变动状况。

2.1)Buffer类及其子类
在Buffer中,Buffer是一个顶层的父类。

它领有各种类型的buffer,比方ByteBuffer,是存储字节数组的缓冲区。
咱们能够得出java中的根本数据类型除了boolean以外,都有一个Buffer类型与之对应。最罕用的当然是ByteBuffer。

2.2)Buffer的重要属性
Buffer定义了四个属性来提供蕴含的数据元素的信息:

属性形容
mark标记
position地位,下一个要被读写的元素的索引,每次读写缓冲区数据时值都会扭转,为下次读写做筹备。
limit示意缓冲区以后的起点,不能对超过缓冲区极限的地位进行读写操作,极限是能够批改的。
capacity容量,就是能包容的最大数据量,在缓冲区创立的时候就不能扭转。

咱们可能感觉positionlimit的概念了解有点不太容易辨别,其实有以下的区别

当往buffer对象里数据的时候,limit始终等于capacity,示意最多能往buffer对象里写的数据

当调用buffer对象的flip()办法后(切换模式,改为buffer读模式),想从buffer对象读取数据的时候,position会被置为0,limit会被设置为之前是position,这个时候limit示意最多能从Buffer读多少数据。

2.3)Buffer的api

2.4)ByteBuffer的api
ByteBuffer作为最罕用的Buffer,其api的次要办法如下:

3.NIO的通道(Channel)介绍
channel代表连贯每个client连贯都会建设一个channnel,channel能够对buffer进行操作。

同时,channel能够从buffer中读数据,也能够将数据写入buffer。

罕用的channel类有FileChannel 、 DatagramChannel 、 ServerSocketChannel 和 SocketChannel。

咱们能够先拿一个常见的FileChannel(文件数据的读写)进行举例:

3.1)应用FileChannel进行本地文件写数据

咱们对一个本地文件进行写入操作。

public class NIOFileChannel01 {    public static void main(String[] args) throws Exception {        String msg = "hello,netty!";        //创立一个输入流        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");        //通过fileOutPutStream取得对应的CileChannel        //这个fileChannel实在类型是FileChannelImpl        FileChannel fileChannel = fileOutputStream.getChannel();        //创立缓冲区        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);        //把数据放入缓冲区        byteBuffer.put(msg.getBytes());        //对byteBuffer进行flip flip之后,就能够进行写操作        byteBuffer.flip();        //将byteBuffer写入到fileChannel        fileChannel.write(byteBuffer);        fileOutputStream.close();    }}

3.2)应用FileChannel进行本地文件读数据

@Slf4jpublic class NIOFileChannel02 {    public static void main(String[] args) throws Exception {        //创立一个输出        FileInputStream fileInputStream = new FileInputStream("d:\\file01.txt");        //通过fileInputStream取得对应的CileChannel        //这个fileChannel实在类型是FileChannelImpl        FileChannel fileChannel = fileInputStream.getChannel();        //创立缓冲区        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);        //将channel的数据读入缓冲区        fileChannel.read(byteBuffer);        log.info(new String(byteBuffer.array()));        fileInputStream.close();    }}

3.3)应用一个 Buffer 实现文件读取、写入

@Slf4jpublic class NIOFileChannel03 {    public static void main(String[] args) throws Exception {        //创立一个输出        FileInputStream fileInputStream = new FileInputStream("d:\\file01.txt");        FileChannel inputChannel = fileInputStream.getChannel();        //创立一个输入        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file02.txt");        FileChannel outputChannel = fileOutputStream.getChannel();        //创立一个buffer        ByteBuffer byteBuffer = ByteBuffer.allocate(512);        while(true){            byteBuffer.clear();            //把数据读入buffer            int read = inputChannel.read(byteBuffer);            log.info("read="+read);            if(read==-1){                //读完                break;            }            //转化为写            byteBuffer.flip();            outputChannel.write(byteBuffer);        }        //敞开流        fileInputStream.close();        fileOutputStream.close();    }}

3.4)应用拷贝文件 transferFrom 办法

@Slf4jpublic class NIOFileChannel04 {    public static void main(String[] args) throws Exception {        //创立一个输出        FileInputStream fileInputStream = new FileInputStream("d:\\file01.txt");        FileChannel inputChannel = fileInputStream.getChannel();        //创立一个输入        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file03.txt");        FileChannel outputChannel = fileOutputStream.getChannel();       //应用transferForm实现拷贝        outputChannel.transferFrom(inputChannel,0,inputChannel.size());        //敞开流        fileInputStream.close();        fileOutputStream.close();    }}

4.NIO的选择器(Selector)介绍

咱们在学习BIO的时候,发现每多创立一个客户端连贯,都须要创立一个线程,那么NIO时如何办到一个工作线程解决多了连贯的呢?那就是Selector

Selector能够应用一个线程,解决多个客户端连贯。首先创立的连贯都会被注册到selector上,selector可能检测多个注册的通道上是否有事件产生,如果有事件产生,便获取事件而后针对每个事件进行相应的解决。这样就能够用一个单线程去治理多个通道,也就是一个线程治理多个链接申请。如图所示:

长处:
1)能够不必为每一个连贯都创立一个线程,一个线程就能够解决N个客户端的链接和读写操作,从根本上解决了传统同步阻塞IO,一链接一线程的模型。
2)只有在连贯真正有事件产生的时候(事件驱动),才会进行读写,就大大减少了零碎开销。
3)防止了多线程之间的上下文切换开销。
4)当没有工作可用的时候,该线程就能够执行其它工作。

大抵罕用办法:

咱们会在上面进行应用。

5.NIO的原理剖析图

6.NIO的编程案例

服务器端:

@Slf4jpublic class NIOServer {    public static void main(String[] args) throws Exception {        //创立ServerSocketChannel        //客户端连贯 通过serverSocketChannel获取socketChannel,再和selector绑定        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();        //绑定一个端口,在服务器端监听        serverSocketChannel.socket().bind(new InetSocketAddress(6677));        //设置为非阻塞        serverSocketChannel.configureBlocking(false);        //取得一个selector对象        Selector selector = Selector.open();        //把这个channel注册到selector上        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);        log.info("注册后的selectionKey数量="+selector.keys().size());        //循环期待客户端连贯        while(true){            //当没有连贯产生的时候            if(selector.select(1000) == 0){                log.info("服务器期待一秒,无连贯");                continue;            }            //当有链接产生的时候            Set<SelectionKey> selectionKeys = selector.selectedKeys();            log.info("selectionKeys数量="+selectionKeys.size());            //遍历Set 应用迭代器            Iterator<SelectionKey> iterator = selectionKeys.iterator();            while(iterator.hasNext()){                //失去key                SelectionKey key = iterator.next();                //依据key的对应通道产生的事件做相应解决                //产生连贯事件                if(key.isAcceptable()){                    //如果是有新客户端连贯                    //通过该客户端生成socketChannel                    SocketChannel socketChannel = serverSocketChannel.accept();                    log.info("客户端连贯胜利,生成了一个socketChannel"+socketChannel.hashCode());                    //将socketChannel设置为非阻塞                    socketChannel.configureBlocking(false);                    //将socketChannel注册到selector,关注事件为read,同时关联一个buffer                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));                    log.info("客户端连贯后,注册的selectionKey数量"+selector.keys().size());                }                //产生读事件                if(key.isReadable()){                    //通过key 反向获取到对应channel                    SocketChannel channel = (SocketChannel)key.channel();                    //获取key绑定的buffer                    ByteBuffer buffer = (ByteBuffer)key.attachment();                    channel.read(buffer);                    log.info("客户端发送信息="+new String(buffer.array()));                }                iterator.remove();            }        }    }}

客户端:

@Slf4jpublic class NIOClient {    public static void main(String[] args) throws Exception {        //失去一个网络通道        SocketChannel socketChannel = SocketChannel.open();        //设置非阻塞        socketChannel.configureBlocking(false);        //提供服务器端的ip和端口        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 6677);        if(!socketChannel.connect(address)){            while(!socketChannel.finishConnect()){                log.info("因为连贯须要工夫,客户端不会阻塞,能够做其它工作..");            }        }        String msg = "hello,netty!";        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());        //发送数据,将 buffer写入channel        socketChannel.write(buffer);        System.in.read();    }}

流程:
5.1)创立ServerSocketChannel,监听端口
5.2)取得一个selector对象,把这个channel注册到这个selector上
5.3)循环select,查看是否有事件产生。
5.4)依据不同的事件做相应的解决。
5.5)移除key,避免有限循环。