图解Java NIO

目录:

NIO结构

NIO与传统IO异同

NIO使用步骤

NIO代码

ByteBuffer难点解析


1:NIO结构:

Channel:通道,连接客户端和服务端的一个管道,管道内可以双向传输数据。

Selector:选择器,可以想象成一个环状传送带,上面可以接入很多管道,selector还可以对每个管道设置感兴趣的颜色(连接(红色),读(黄色),写(蓝色),接收数据)。当Selector开始轮询的时候Selector这个传送带就一直转动,当某个管道被传送到感兴趣事件检查点的时候,selector会检查改管道当前颜色(即事件)之前是否被注册成了感兴趣颜色(事件),如果感兴趣,那么Selector就可以对这个管道做处理了,比如把管道传给别的线程,让别的线程完成读写操作。

ByteBuffer:字节缓冲区,本质上是一个连续的字节数组,Selector感兴趣的事件发生后对管道的读操作所读到的数据都存储在ByteBuffer中,而对管道的写操作也是以ByteBuffer为源头,值得注意的是ByteBuffer可以有多个。想想看,我们使用的所有基本类型都可以转换成byte字节,比如Integer类型占4字节,那么传递数字 1 ByteBuffer底层数组被占用了4个格子。


2:与传统IO比较:

1:传统IO一般是一个线程等待连接,连接过来之后分配给processor线程,processor线程与通道连接后如果通道没有数据过来就会阻塞(线程被动挂起)不能做别的事情。NIO则不同,首先:在Selector线程轮询的过程中就已经过滤掉了不感兴趣的事件,其次:在processor处理感兴趣事件的read和write都是非阻塞操作即直接返回的,线程没有被挂起。

2:传统IO的管道是单向的,NIO的管道是双向的

3:两者都是同步的,也就是Java程序亲力亲为的去读写数据,不管传统IO还是NIo都需要read和write方法,这些都是Java程序调用的而不是系统帮我们调用的。NIO2.0里这点得到了改观,即使用异步非阻塞AsynchronousXXX四个类来处理。

3:使用NIO步骤:(服务端)

首先:创建一个传送带

然后:创建一个管道,设置管道为非阻塞,绑定端口

然后:把管道放到传送带上

再然后:启动传送带

其次:传送带感兴趣事件检查点查获一个感兴趣管道,转给其他线程对管道进行非阻塞读写

最后:全使用完,关闭管道

过程很清晰,跟我们现实世界中的传送带效果一样。


4:代码体现:

注意:只写服务器端关键步骤,客户端可以参考这些代码

public class Server implements Runnable {

private Selector selector;

private ByteBuffer buffer = ByteBuffer.allocate(1024);

public Server(int port) {

        try {

            //1 创建一个传送带

            selector = Selector.open();

            //2 创建一个管道

            ServerSocketChannel ssc = ServerSocketChannel.open();

            //3 设置服务器通道为非阻塞方式

            ssc.configureBlocking(false);

            //4 绑定TCP地址

            ssc.bind(new InetSocketAddress(port));

            //5 把管道放到传送带上,并在传送带上注册一个感兴趣事件,此处传送带感兴趣事件为连接事件

            ssc.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Server start, port:" + port);

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

public void run() { 

 while (true) {

    try {

                //1 启动传送带,开始轮询

                selector.select();

                //2 所有感兴趣事件的keys

               SelectionKey Iterator keys = selector.selectedKeys().iterator();

                //3 遍历所有感兴趣事件集合

                while (keys.hasNext()) {

                    SelectionKey key = keys.next();

                    keys.remove();

                    if(key.isValid()) { //如果key的状态是有效的

                        if(key.isAcceptable()) { //如果key是阻塞状态,则调用accept()方法

                            accept(key);

                        }

                        if(key.isReadable()) { //如果key是可读状态,则调用read()方法

                            read(key);

                        }

                    }

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

    private void accept(SelectionKey key) {

        try {

            //1 获取服务器通道

            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

            //2 执行阻塞方法

            SocketChannel sc = ssc.accept();

            //3 设置阻塞模式为非阻塞

            sc.configureBlocking(false);

            //4 注册到多路复用选择器上,并设置读取标识

            sc.register(selector, SelectionKey.OP_READ);

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    private void read(SelectionKey key) {

        try {

            //1 清空缓冲区中的旧数据

            buffer.clear();

            //2 获取之前注册的SocketChannel通道

            SocketChannel sc = (SocketChannel) key.channel();

            //3 将sc中的数据放入buffer中

            int count = sc.read(buffer);

            if(count == -1) { // == -1表示通道中没有数据

                key.channel().close();

                key.cancel();

                return;

            }

            //读取到了数据,将buffer的position复位到0

            buffer.flip();

            byte[] bytes = new byte[buffer.remaining()];

            //将buffer中的数据写入byte[]中

            buffer.get(bytes);

            String body = new String(bytes).trim();

            System.out.println("Server:" + body);

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    public static void main(String[] args) {

        new Thread(new Server(8379)).start();

    }

}

其他:

涉及到ByteBuffer分类及ByteBuffer的读写这里就不过多介绍了,就是一些指针和模式的变动,主要是flip方法,调用flip方法之后的变化从写模式变成读模式


你可能感兴趣的:(图解Java NIO)