An overview of the

NIO has three core parts: channels, buffers, and selectors.

Traditional IO based on byte streams and character stream-oriented means that one or more bytes are read from the stream at a time until all bytes are read, without being cached anywhere. In addition, it cannot move data back and forth in a stream. If data read from a stream needs to be moved back and forth, it needs to be cached into a buffer for operation.

NIO operates based on channels and buffers, and data is always read from a Channel into a Buffer or written from a Buffer into a Channel. The data is read into a buffer that it processes later and can be moved back and forth in the buffer as needed. This adds flexibility to the process. But at the same time, programmers need to pay attention to timely check and process the data in the buffer, otherwise it may lead to the problem of being cleaned or overwritten.

A Selector listens for events on multiple channels (such as a connection opening, data arrival). Thus, a single thread can listen on multiple data channels.

The various streams of IO are blocked. This means that when a thread calls read() or write(), the thread blocks until some data is read, or data is written entirely. The thread can’t do anything else in the meantime.

NIO’s non-blocking mode allows a thread to send a request from a channel to read data, but it only gets what is currently available, and if no data is currently available, it gets nothing. Instead of keeping the thread blocked, it can continue doing other things until the data becomes readable. The same is true for non-blocking writes. A thread requests to write some data to a channel, but without waiting for it to write completely, the thread can do something else in the meantime.

Threads typically spend the idle time of non-blocking IO performing IO operations on other channels, so a single thread can now manage multiple input and output channels.

Channel

First, let’s talk about Channel, which is mostly translated as “Channel” in China. A Channel is about the same level as a Stream in IO. Only the Stream is one-way, for example: InputStream, OutputStream. A Channel is bidirectional and can be used for both read and write operations.

The main implementations of channels in NIO are:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

Buffer

The key Buffer implementations in NIO are: ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer, corresponding to the basic data types respectively: Byte, char,double, float, int, long, short. Of course, there are MappedByteBuffer, HeapByteBuffer, DirectByteBuffer and so on in NIO.

Selector

Selector runs on a single thread to handle multiple channels, which can be handy if your application has multiple channels open, but traffic per connection is low. For example, in a chat server. To use a Selector, you register a Channel with a Selector and then call its select() method. This method blocks until a registered channel is ready for an event. Once the method returns, the thread can process the events, such as new connections coming in, data receiving, and so on.


FileChannel

BIO vs NIO

Bio reads files

    private String read(File file) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            String buf;
            while((buf = br.readLine()) ! =null) { sb.append(buf); }}return sb.toString();
    }
Copy the code

Nio reads the file

    private String nioRead(File file) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (FileChannel channel = new FileInputStream(file).getChannel()) {
            // Allocate a buffer of 1024 size
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int readLen = 0;
            while (-1! = (readLen = channel.read(buffer))) {// Reset the buffer pointer to the starting point
                buffer.flip();
                byte[] bytes = buffer.array();
                sb.append(new String(bytes, 0, readLen)); buffer.clear(); }}return sb.toString();
    }
Copy the code

Buffer

Buffer can be understood as the carrier of data transmission in channel. All read and write in NIO must use buffer, and there will be a special issue about buffer later.


Selector

A Selector is usually called a Selector, but you can also translate it as a multiplexer. It is one of the Java NIO core components that checks whether the state of one or more NIO channels is readable and writable. In this way, a single thread can manage multiple channels, that is, multiple network links can be managed.

The flow of using selector:

  1. To create aServerSocketChannelAnd configureBlocking modeIf this parameter is not blocked, configure the information about the ports to be bound.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8080));
Copy the code
  1. Create a selector, register serverSocketChannel with the Selector
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
Copy the code

The SelectionKey is what event is it interested in when it listens to a Channel through Selector, and there are four types of SelectionKey

type instructions
Connect Link is ready
Accept Receive ready
Read Read ready
Write Write a ready

If a selector that multiple events, monitoring the channel can use | link, such as:

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ);
Copy the code

Once one or more channels have been registered with a Selector, several overloaded select() methods can be called. These methods return channels that are ready for the event you are interested in (such as connect, receive, read, or write). Here is the select() method:

  1. int select()
  2. int select(long timeout)
  3. int selectNow()

Select () blocks until at least one channel is ready on the event you registered.

Select (long timeout) is the same as select(), except that it blocks at most timeout milliseconds.

SelectNow () does not block and returns immediately whenever any channel is ready. If no channels have become selectable since the previous selection, this method returns zero. .

The int returned by the select() method indicates how many channels are ready. That is, how many channels have become ready since the select() method was last called. If you call select(), it returns 1 because one channel is ready, and if you call select() again, it returns 1 again if the other channel is ready. If nothing is done to the first ready channel, there are now two ready channels, but only one is ready between each select() method call.

Once the select() method is called and the return value indicates that one or more channels are ready, the ready channels in the selectedKey Set can be accessed by calling the Selector selectedKeys() method. As follows:

Set selectedKeys = selector.selectedKeys();
Copy the code

Note the keyiterator.remove () call at the end of each iteration. The Selector does not remove the SelectionKey instance from the selected key set itself. It must be removed by itself when the channel is processed. The next time the channel becomes ready, the Selector puts it into the selected key set again. The channel returned by the selectionkey.channel () method needs to be converted to the type you want to work with, such as ServerSocketChannel or SocketChannel.


SelectionKey

This object contains some properties of interest to you:

  • Collection of interest
  • Ready set
  • Channel
  • Selector
  • Attached object (optional)
  1. Interest collection: As described in the Registering channels with Selector section, an interest collection is a collection of events that you select that you are interested in. Interest collections can be read and written by SelectionKey.

  2. The Ready collection is a collection of operations for which the channel is ready. After a Selection, you first access the Ready set. The ready collection can be accessed like this:

int readySet = selectionKey.readyOps();
Copy the code

You can detect what events or actions are already in place in a channel in the same way you detect interest collections. However, you can also use the following four methods, all of which return a Boolean type:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Copy the code
  1. Get the Channel and Selector from the SelectionKey. As follows:
Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();
Copy the code
  1. An object or more information can be attached to the SelectionKey to make it easy to identify a given channel. For example, you can attach a Buffer that is used with a channel, or an object that contains aggregated data. The usage method is as follows:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
Copy the code

You can also attach objects when you register a Channel with a Selector using the register() method. Such as:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Copy the code

Talk is Cheap show me the code

Knowing the basics above, you’re ready for simple programming.

In a simple example, the client sends hello server repeatedly, and the server responds to the message with Hello Client

  1. The service side
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;

public class NioServer {
    @SuppressWarnings("all")
    public static void main(String[] args) throws Exception {

        Selector serverSelector = Selector.open();
        Selector clientSelector = Selector.open();
        Thread bossThread = new Thread(() -> {
            try {
                ServerSocketChannel listenerChannel = ServerSocketChannel.open();
                listenerChannel.socket().bind(new InetSocketAddress(8000));
                listenerChannel.configureBlocking(false);
                listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);

                while (true) {
                    if (serverSelector.select(1) > 0) {
                        Set<SelectionKey> selectionKeys = serverSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

                        while (keyIterator.hasNext()) {
                            SelectionKey key = keyIterator.next();
                            if (key.isAcceptable()) {
                                try {
                                    SocketChannel clientChannel = ((ServerSocketChannel) (key.channel())).accept();
                                    clientChannel.configureBlocking(false);
                                    clientChannel.register(clientSelector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                                } finally {
                                    keyIterator.remove();
                                }
                            }
                        }
                    }
                }
            } catch(IOException e) { e.printStackTrace(); }}); bossThread.start(); Thread workerThread =new Thread(() -> {
            try {
                while (true) {
                    if (clientSelector.select(1) > 0) {
                        Set<SelectionKey> selectionKeys = clientSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        while (keyIterator.hasNext()) {
                            SelectionKey selectionKey = keyIterator.next();
                            try {
                                SocketChannel clientChannel = (SocketChannel) selectionKey.channel();

                                if (selectionKey.isReadable()) {
                                    clientChannel.read(byteBuffer);
                                    byteBuffer.flip();
                                    String msg = Charset.defaultCharset().newDecoder().decode(byteBuffer).toString();
                                    System.out.println(msg);
                                    selectionKey.interestOps(SelectionKey.OP_WRITE);
                                    selectionKey.attach("hello,client");
                                } else if (selectionKey.isWritable()) {
                                    Object attachment = selectionKey.attachment();
                                    if(Objects.nonNull(attachment)) { byteBuffer.put(((String) attachment).getBytes(StandardCharsets.UTF_8)); byteBuffer.flip(); clientChannel.write(byteBuffer); } selectionKey.interestOps(SelectionKey.OP_READ); }}finally {
                                byteBuffer.clear();
                                keyIterator.remove();
                            }
                        }
                    }
                }
            } catch(IOException e) { e.printStackTrace(); }}); workerThread.start(); }}Copy the code
  1. The client
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;

public class NioClient {

    @SuppressWarnings("all")
    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        InetSocketAddress socketAddress = new InetSocketAddress("localhost".8000);
        socketChannel.configureBlocking(false);
        socketChannel.connect(socketAddress);

        Selector selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_CONNECT);

        while (true) {
            if (selector.select() > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    try {
                        SelectionKey selectionKey = iterator.next();
                        process(selector, selectionKey);
                    } finally {
                        iterator.remove();
                    }
                }
            }
        }
    }

    private static void process(Selector selector, SelectionKey selectionKey) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        if (selectionKey.isConnectable()) {
            while(! socketChannel.finishConnect()) {try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            selectionKey.attach("hello,server");
        } else if (selectionKey.isReadable()) {
            socketChannel.read(buffer);
            buffer.flip();
            String msg = Charset.defaultCharset().newDecoder().decode(buffer).toString();
            buffer.clear();
            System.out.println(msg);
            socketChannel.register(selector, SelectionKey.OP_WRITE);
            selectionKey.attach("hello,server");
        } else if (selectionKey.isWritable()) {
            Object attachment = selectionKey.attachment();
            if(Objects.nonNull(attachment)) { buffer.put(((String) attachment).getBytes(StandardCharsets.UTF_8)); buffer.flip(); socketChannel.write(buffer); buffer.clear(); } socketChannel.register(selector, SelectionKey.OP_READ); }}}Copy the code

The above code is just a simple Demo, there are many details left unaddressed, such as disconnection handling, message unpacking, etc. And, intuitively, niO’s programming model is a bit more complex than BIO’s, which can easily lead to problems in our applications if the details are not handled properly.