[Netty] introduction and use of three components in NIO

1. NIO basic introduction

Java NIO is synchronous and non blocking. NIO related classes are placed in Java NIO package and sub packages, and many classes of rewriting of the native IO.

NIO is buffer oriented or block oriented programming: data is read into a buffer that it will process later and can be moved in the buffer when needed, which increases the flexibility in the processing process.

It has three core components:

  • Channel: Channel
  • Buffer: buffer
  • Selector: selector

Its core component structure is shown in the figure below:

As can be seen from the above figure:

  1. One Channel corresponds to one Buffer
  2. One Selector corresponds to one Thread, but corresponds to multiple channels
  3. The Selector switches between channels according to different events
  4. Data is read / written through Buffer, which is different from BIO. BIO deals directly with channels
  5. The Buffer in NIO is bidirectional (both read and write), but it needs to be switched by the flip() method; BIO is not a two-way flow, either a separate input flow or an output flow
  6. Channel is also bidirectional

The Client does not interact directly with the Channel, but through the intermediate medium Buffer.

2. Buffer

Buffer is essentially a memory block that can read and write data. It can be understood as a container object.

Except for the boolean type, all the basic data types in Java have the corresponding Buffer type.

Take IntBuffer as an example:

Buffer usage example:

public class BufferDemo {

    public static void main(String[] args) {
        // 1. Create Buffer
        IntBuffer intBuffer = IntBuffer.allocate(5);
        for (int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put(i);
        }
        // 2.Buffer conversion. Write -- > read
        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

An IntBuffer with a size of 5 is created here. Then, put five integers into it (Buffer write). Because Buffer can be written and read. Therefore, before reading, switch from Buffer to IntBuffer flip()

3. Channel

NIO channels are similar to streams, but have the following differences:

  1. Channels can read and write at the same time, while streams can only read or write
  2. The channel can read and write data asynchronously
  3. The channel can read data from the buffer or write data to the buffer

Channel is an interface:

public interface Channel extends Closeable {
	// ...
}

Common Channel classes are:

  • Filechanle: used for reading and writing data of files
  • Datagram channel: used for reading and writing UDP data
  • ServerSocketChannel: used for reading and writing TCP data
  • SocketChannel: used for reading and writing TCP data

Example 1: local file write data

Use ByteBuffer and FileChannel to write "hello, JAVA" to a disk file

public static void main(String[] args) throws Exception {
    String str = "hello, JAVA";
    FileOutputStream out = new FileOutputStream("E:\\temp.txt");
    FileChannel fileChannel = out.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    byteBuffer.put(str.getBytes());
    byteBuffer.flip();
    fileChannel.write(byteBuffer);
    out.close();
}

After running the above code, a temp will be generated in the E disk Txt file, and it has content.

Example 2: reading data from local files

Use ByteBuffer and FileChannel to set temp Txt file

public static void main(String[] args) throws Exception {
    File file = new File("E:\\temp.txt");
    FileInputStream in = new FileInputStream(file);
    FileChannel fileChannel = in.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
    fileChannel.read(byteBuffer);
    // Convert bytes to strings
    String result = new String(byteBuffer.array());
    System.out.println(result);
    in.close();
}

Example 3: reading and writing data from local files

Copy a file through FileChannel and a Buffer

public class FileChannelRwDemo {

    public static void main(String[] args) throws Exception{
        FileInputStream in = new FileInputStream("E:\\temp.txt");
        FileChannel inFileChannel = in.getChannel();
        FileOutputStream out = new FileOutputStream("temp.txt");
        FileChannel outFileChannel = out.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        while (true) {
        	// This line of code is the focus
            byteBuffer.clear();
            
            int read = inFileChannel.read(byteBuffer);
            if (-1 == read) {
                break;
            }
            byteBuffer.flip();
            outFileChannel.write(byteBuffer);
        }
        in.close();
        out.close();
    }
}

4. Selector

The Selector can detect whether events occur on multiple registered channels. If an event occurs, get the event, and then handle each event accordingly.

Only when there are real read and write events in the connection can read and write. This greatly reduces the overhead of the system, and there is no need to create a thread for each connection and maintain multiple threads.

Selector workflow:

  1. When the client connects, it will get the SocketChannel through ServerSocketChannel
  2. Register SocketChannel with Selector (SelectableChannel#register())
  3. After registration, a SelectionKey will be returned, which will be associated with the Selector
  4. The Selector listens through the select() method. This method returns the number of channels with events
  5. SelectionKey can be obtained further
  6. SelectionKey reversely obtains the SocketChannel through the channel() method, and then it can be operated

Example: data communication between server and client through NIO

Server:

public class NioServer {

    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // Binding port
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // Set non blocking
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        // Register ServerSocketChannel with Selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            if (selector.select(1000) == 0) {
                System.out.println("The server waited for 1 second and there was no connection");
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // If there is a client connection
                if (selectionKey.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("The client connection is successful and a socketChannel :" + socketChannel.hashCode());
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                    // Gets the Buffer associated with the Channel
                    ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println("The server received:" + new String(byteBuffer.array()));
                }
                iterator.remove();
            }
        }
    }
}

client:

public class NioClient {

    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        // (connecting to the server)
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("Because the connection takes time, the client does not need to be blocked and can do other work...");
            }
        }
        // Connection successful
        String str = "Hello Java";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }
}

Run the server first and then the client. The server prints the following information:

Keywords: Java Netty Back-end

Added by LexHammer on Sun, 30 Jan 2022 17:04:42 +0200