dev

Java - NIO

/wrote/note/dev/java/nio/

Java NIO

IO v.s. NIO

功能

  • IO 面向流 Stream ;NIO 面向缓冲区 Buffer
  • IO 是阻塞式的;NIO 是非阻塞的

使用场景

  • IO 适用,连接数目比较小,并且一次发送大量数据的场景
  • NIO 适用,服务器需要支持大量长连接。比如10000个连接以上,并且每个客户端并不会频繁地发送太多数据

NIO 原理

实现 NIO 的主要原理是轮询。即,一个线程,同时监听多个接口,如果这个接口有数据就读取,没有就检查下一个。

但是,轮询的缺点是浪费CPU资源,因为大部分时间可能都是无数据可读的。 所以,一种常用的改进方案是I/O多路复用(IOmultiplexing),即监听时,由事件驱动,当某个接口的数据好了,再过去读。

NIO 的三种模型

  • Reactor 单线程模型
  • Reactor 多线程模型
  • 主从 Reactor 多线程模型

参考 -> https://blog.csdn.net/jiyiqinlovexx/article/details/51204726

NIO API

Core components

  • Channels
    • Channels read data into Buffers
  • Buffers
    • Buffers write data into Channels
  • Selectors
    • Selector allows a single thread to handle multiple Channels

Multiple sources read/write from multiple channels into buffer(s), thus non-blocking.

Channel

Channel is similar to Stream, it is the gate for read/write.

Channels implementation

  • FileChannel (File NIO)
  • DatagramChannel (UDP NIO)
  • SocketChannel (TCP NIO)
  • ServerSocketChannel (TCP NIO)
//create channel
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

//read from channel into buffer
int bytesRead = inChannel.read(buf); 
while (bytesRead != -1) {
  buf.flip();  //filp the buffer mode from write to read
  while(buf.hasRemaining()){
    System.out.print((char) buf.get()); // read 1 byte at a time
  }
  buf.clear(); //filp the buffer mode from read to write
  bytesRead = inChannel.read(buf);
}
aFile.close();

Buffer

A buffer is essentially a block of memory into which you can write data, which you can then later read again.

Usage

  • Write data into the Buffer
  • Call buffer.flip()
  • Read data out of the Buffer
  • Call buffer.clear() or buffer.compact()

Buffer implementation

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

Selector

  • It can examine one or more NIO Channel’s, and determine which channels are ready for reading/writing.
  • This way a single thread can manage multiple channels, and thus multiple network connections.

Usage

  • open a Selector
  • register Channels for this Selector
  • select ready Channels from Selector by calling select()
    • select() will block until there is an event ready for one of the registered Channels
  • process the return results
    • get a set of SelectionKey, this key contains
      • the interest set
      • the ready set
      • the Channel
      • the Selector
      • an attached object (optional)
    • iterate the set to do operations
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set<SelectionKey> selectedKeys = selector.selectedKeys();
  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();
  }
}