[Socket and IO framework learning] 3 NiO (non blocking IO) Basics

Posted by burfo on Thu, 03 Feb 2022 21:12:06 +0100

[Socket and IO framework learning] 3 NiO (non blocking IO) Basics

I haven't seen this thing for nearly half a month, and I have nothing to do today. I continue to record the learning NIO. This N represents "new". According to my Baidu, NIO was introduced in the version of jdk 1.4 to make up for the shortcomings of the original I/O and provide a higher speed and block oriented I/O.

1. Flow and block

standard IO refers to the reading and writing of convection, and processes data in the form of stream. Each time I / O operation is performed, a stream object (such as InputStream and OutputStream) must be created. The IO operation of stream object is performed by byte, and the reading and writing operation is performed by byte. Therefore, the operation efficiency is relatively slow.

NIO abstracts IO into blocks and processes data in the form of blocks, which is similar to the reading and writing of hard disk. The unit of each IO operation is a block. After the block is stored in memory, it is a byte [] array, which can read and write multiple bytes at a time. Therefore, processing data by block is much faster than processing data by stream.

jdk1.4 in Java io.* The IO class has been re implemented based on NIO in the package, so it can take advantage of some features of NIO.

Channels and buffers

1. Access

Channel is a simulation of the stream in the original I/O package, through which data can be read and written.

The difference between channels and streams is that streams can only move in one direction (a stream must be a subclass of InputStream or OutputStream), while channels are bidirectional and can be used for reading, writing or both.

Channels include the following types:

  • FileChannel: read and write data from files;
  • Datagram channel: read and write data in the network through UDP;
  • SocketChannel: read and write data in the network through TCP;
  • ServerSocketChannel: it can listen to new TCP connections and create a SocketChannel for each new connection.

2. Buffer zone

All data sent to a channel must be put into the buffer first. Similarly, any data read from the channel must be read into the buffer first. In other words, the data will not be read or written directly to the channel, but through the buffer first.

A buffer is essentially an array, but it's not just an array. Buffers provide structured access to data and can also track the read / write process of the system.

Buffers include the following types:

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

Buffer state variable

  • Capacity: maximum capacity;
  • position: the number of bytes currently read and written;
  • limit: the number of bytes that can be read and written.

Example of change process of state variable:

① Create a buffer with a size of 8 bytes. At this time, position is 0 and limit = capacity = 8. The capacity variable will not change and will be ignored in the following discussion.

② Read 5 bytes of data from the input channel and write it into the buffer. At this time, the position movement is set to 5 and the limit remains unchanged.

③ Before writing the buffer data to the output channel, you need to call the flip() method, which sets the limit to the current position and the position to 0.

④ Take 4 bytes from the buffer to the output buffer. At this time, position is set to 4.


⑤ finally, you need to call the clear() method to empty the buffer. At this time, both position and limit are set to the initial position.

Selector

NIO is often called non blocking IO, mainly because NIO is widely used in network communication.

NIO implements the Reactor model in IO multiplexing (this is not well understood). A Thread uses a Selector selector to listen for events on multiple channels through polling, so that one Thread can handle multiple events

By configuring the monitored Channel to be non blocking, when the IP event on the Channel has not arrived, it will not enter the blocking state and wait. Continue to poll other channels to find the Channel where the IP event has arrived for execution.

Because creating and switching threads is expensive, using one thread to handle multiple events has better performance

  • Channel events
Register the channel to the selector and select the following specific events. You can"|"Compose events into event sets
 For example: int interestSet = Selection.OP_READ | Selection.OP_WRITE
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;

demo

NioServer.java

package NioDemo.SocketNioDemo;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.util.Iterator;
import java.util.Set;

/**
 * @author Mo
 * @createTime 2022/2/1 23:06
 * @description
 */
public class NioServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //  Set the channel to non blocking mode
        serverSocketChannel.configureBlocking(false);
        /**
         * Register the channel on the selector and select the following specific events to form an event set through "|"
         * For example: int interestset = selection OP_ READ | Selection. OP_ WRITE
         * public static final int OP_READ = 1 << 0;
         * public static final int OP_WRITE = 1 << 2;
         * public static final int OP_CONNECT = 1 << 3;
         * public static final int OP_ACCEPT = 1 << 4;
         */
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        ServerSocket serverSocket = serverSocketChannel.socket();
        InetSocketAddress address = new InetSocketAddress("localhost", 80);
        serverSocket.bind(address);
        while (true) {
            //  Monitoring events will be blocked until the monitoring events arrive
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if (key.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) key.channel();
                    //  The server will create a new SocketChannel for each new connection and register it on the selector. The event is read. You can debug and see the process yourself
                    SocketChannel socketChannel = serverSocketChannel1.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    //  The if block above is the op registered for the channel_ The read event will loop into this code block for reading
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    System.out.println("[address: " + socketChannel.getRemoteAddress() + ", data: " + readDataFromSocketChannel(socketChannel) + "]");
                    socketChannel.close();
                }
                keyIterator.remove();
            }
        }
    }

    public static String readDataFromSocketChannel (SocketChannel socketChannel) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        StringBuilder dataString = new StringBuilder();
        while (true) {
            if (socketChannel.read(byteBuffer) == -1)
                break;
            byteBuffer.flip();
            int limit = byteBuffer.limit();
            char[] dst = new char[limit];
            for (int i = 0; i < limit; i ++) {
                dst[i] = (char) byteBuffer.get(i);
            }
            dataString.append(dst);
            byteBuffer.clear();
        }
        return dataString.toString();
    }
}

NioClient.java

package NioDemo.SocketNioDemo;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author Mo
 * @createTime 2022/2/3 12:30
 * @description
 */
public class NioClient {
    public static void main(String[] args) throws IOException {
        for (int i = 0; i < 10; i++) {
            //  Establishing a socket connection is the arrival of an accept event
            Socket socket = new Socket("localhost", 80);
            OutputStream outputStream = socket.getOutputStream();
            String data = "hello, world" + i;
            //  OutputStream performs a write operation, which is the arrival of a Read event for the server
            outputStream.write(data.getBytes());
            outputStream.close();
        }
    }
}

Let's see the code behind the process

Simulate a concurrency. The server code is the same. The only change is that the request code of the client is different. You can write it yourself

Daemon thread

package NioDemo.SocketNioDemo;

import java.util.concurrent.CountDownLatch;

/**
 * @author Mo
 * @createTime 2022/2/3 13:43
 * @description
 */
public class NioClientDaemon {
    public static void main(String[] args) throws InterruptedException {
        Integer clientNumber = 3;
        CountDownLatch countDownLatch = new CountDownLatch(clientNumber);
        for (int i = 0; i < clientNumber; i++, countDownLatch.countDown()) {
            NioRequestThread nioRequestThread = new NioRequestThread(countDownLatch, i);
            new Thread(nioRequestThread).start();
        }
        synchronized (NioClientDaemon.class) {
            NioClientDaemon.class.wait();
        }
    }

}

Request thread

package NioDemo.SocketNioDemo;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;

/**
 * @author Mo
 * @createTime 2022/2/3 13:19
 * @description
 */
public class NioRequestThread implements Runnable{

    private CountDownLatch countDownLatch;
    private Integer clientIndex;

    public NioRequestThread() {
    }

    public NioRequestThread(CountDownLatch countDownLatch, Integer clientIndex) {
        this.countDownLatch = countDownLatch;
        this.clientIndex = clientIndex;
    }

    @Override
    public void run() {
        Socket socket = null;
        OutputStream outputStream = null;
        try {
            socket = new Socket("localhost", 80);
            outputStream = socket.getOutputStream();
            this.countDownLatch.await();
            outputStream.write(("This is " + clientIndex + "Message sent by thread").getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Topics: Java network Multithreading NIO