NIO原理解析

Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。

了解NIO前先熟悉几个概念

1)阻塞(Block)和非阻塞(Non-Block):

阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式。

阻塞:需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。

非阻塞:当进程访问数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。

2)同步(Synchronization)和异步(Asynchronous)的方式:

同步和异步都是基于应用程序和操作系统处理IO 事件所采用的方式。

异步:所有的IO 读写交给操作系统去处理,应用程序只需要等待通知。

同步: 处理IO 事件的时候,必须阻塞在某个方法上面等待我们的IO 事件完成。

所以对于对于标准的IO属于同步阻塞方式,NIO属于同步非阻塞模式,AIO属于异步非阻塞模式。

在java NIO由几个核心部门组成:缓存Buffers;通道Channels;选择器Selectors。

一、缓存Buffers

   缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO 库中,所有数据都是用缓冲区处理的。

  使用Buffer读写数据一般遵循以下四个步骤:
   1)写入数据到Buffer: 直接将数据写入buffer
   2)调用flip()方法:通过flip()将buffer从写模式切换到读模式
   3)从Buffer中读取数据:
   4)调用clear()方法或者compact()方法:
    读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

  在NIO 中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer 类型与之相对应,它们之间的继承关系如下图所示:

  在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:
    position:指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer 对象时,position 被初始化为0。
    limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
    capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。

二、通道Channels

通道是一个对象,通过它可以读取和写入数据,但所有数据的读写都先通过Buffer对象来处理再到通道中。

Java NIO中几个最重要的通道的实现
FileChannel:从文件中读写数据
DatagramChannel:能通过UDP读写网络中的数据
SocketChannel:能通过TCP读写网络中的数据
ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel

他们之间的继承关系如下:

 

1)通过通道读取过程:

- 从FileInputStream 获取Channel
- 创建Buffer
- 将数据从Channel 读取到Buffer中

public class FileInputProgram {
    static public void main( String args[] ) throws Exception {
    FileInputStream fin = new FileInputStream("c:\\test.txt");
    // 获取通道
    FileChannel fc = fin.getChannel();
    // 创建缓冲区
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    // 读取数据到缓冲区
    fc.read(buffer);
    buffer.flip();
    while (buffer.remaining()>0) {
        byte b = buffer.get();
        System.out.print(((char)b));
    }
    fin.close();
}

2)使用NIO 写入数据

可以分为下面三个步骤:
- 从FileInputStream 获取Channel
-  创建缓存Buffer
- 将数据从Channel写入到Buffer中

public class FileOutputProgram {
    static private final byte message [] = { 83, 111, 109, 101, 32,
    98, 121, 116, 101, 115, 46 };
    static public void main(String args[]) throws Exception {
    FileOutputStream fout = new FileOutputStream("e:\\test.txt");
    //新建渠道
    FileChannel fc = fout.getChannel();
    //新建buffer
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    for (int i = 0; i < message.length; ++ i) {
            buffer.put(message[i]);
        }
        buffer.flip();
        //写入渠道
        fc.write(buffer);
        fout.close();
    }
}

三、selector选择器

  Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

  使用NIO 中非阻塞I/O 编写服务器处理程序,大体上可以分为下面三个步骤:
  1)向Selector 对象注册感兴趣的事件
  2)从Selector 中获取感兴趣的事件
  3)根据不同的事件进行相应的处理

注册事件:

如果想要同时注册几个事件时,可以通过“位或”操作符将常量连接起来,比如:SelectionKey.OP_READ | SelectionKey.OP_WRITE  

/*
* 向selector注册事件
* 注册的事件包含四种:
* SelectionKey.OP_CONNECT
* SelectionKey.OP_ACCEPT
* SelectionKey.OP_READ
* SelectionKey.OP_WRITE
* */
private Selector getSelector() throws IOException {
    // 创建Selector 对象
    Selector sel = Selector.open();
    // 创建可选择通道,并配置为非阻塞模式
    ServerSocketChannel server = ServerSocketChannel.open();
    server.configureBlocking(false);
    // 绑定通道到指定端口
    ServerSocket socket = server.socket();
    InetSocketAddress address = new InetSocketAddress(port);
    socket.bind(address);
    // 向Selector 中注册感兴趣的事件
    server.register(sel, SelectionKey.OP_ACCEPT);
    return sel;
}

 获取感兴趣的事件:

/*
* 开始监听
* */
public void listen() {
    System.out.println("listen on " + port);
    try {
        while(true) {
            // 该调用会阻塞,直到至少有一个事件发生
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iter = keys.iterator();
            while (iter.hasNext()) {
                SelectionKey key = (SelectionKey) iter.next();
                iter.remove();
                //最终处理事件
                process(key);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

对事件进行处理,可以针对不同的事件进行不同的处理流程:

/*
* 根据不同的事件做处理
* */
private void process(SelectionKey key) throws IOException{
    // 接收请求
    if (key.isAcceptable()) {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        SocketChannel channel = server.accept();
        channel.configureBlocking(false);
        channel.register(selector, SelectionKey.OP_READ);
    }
    // 读信息
    else if (key.isReadable()) {
        SocketChannel channel = (SocketChannel) key.channel();
        int len = channel.read(buffer);
        if (len > 0) {
            buffer.flip();
            name = new String(buffer.array(),0,len);
            SelectionKey sKey = channel.register(selector, SelectionKey.OP_WRITE);
            sKey.attach(name);
        } else {
            channel.close();
        }
        buffer.clear();
    }
    // 写事件
    else if (key.isWritable()) {
        SocketChannel channel = (SocketChannel) key.channel();
        String name = (String) key.attachment();
        ByteBuffer block = ByteBuffer.wrap(("Hello " + name).getBytes());
        if(block != null){
            channel.write(block);
        }
        else{
            channel.close();
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/kma-3/p/9625443.html