NIO three components

Posted by jabbaonthedais on Tue, 09 Nov 2021 04:58:10 +0100

1. Channel

1.1 what is Channel

  • Channel is similar to Stream. It is a two-way channel for reading and writing data.
  • You can read data from the channel into the buffer or write data from the buffer into the channel. (channel only deals with buffer)
  • The previous stream is either input or output, and channel is lower than stream.

1.2 common channels

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

1.3 common methods of channel

  • Create channel
ServerSocketChannel channel = ServerSocketChannel.open();
  • Bind port for channel
channel.bind(new InetSocketAddress(8080));
  • Set channel to non blocking access
 channel.configureBlocking(false);
  • channel processing of udp
public class UdpServer {
    public static void main(String[] args) {
        try (DatagramChannel channel = DatagramChannel.open()) {
            channel.socket().bind(new InetSocketAddress(9999));
            System.out.println("waiting...");
            ByteBuffer buffer = ByteBuffer.allocate(32);
            channel.receive(buffer);
            buffer.flip();
            debug(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class UdpClient {
    public static void main(String[] args) {
        try (DatagramChannel channel = DatagramChannel.open()) {
            ByteBuffer buffer = StandardCharsets.UTF_8.encode("hello");
            InetSocketAddress address = new InetSocketAddress("localhost", 9999);
            channel.send(buffer, address);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • UDP is connectionless. The client sends data regardless of whether the server is enabled or not
  • The receive method of the server will store the received data into the byte buffer. If the data message exceeds the buffer size, the excess data will be discarded by default.

2. Buffer

2.1 what is Buffer

  • Buffer is used to buffer read and write data. The most commonly used buffer is ByteBuffer
  • Channel only deals with Buffer.
  • Buffer is divided into heap memory and direct memory
    • Heap memory is affected by garbage collection, and frequent object relocation may occur
    • Direct memory is not affected by GC. It has its own recycling mechanism, but this memory allocation is time-consuming

2.2 common methods of ByteBuffer

  • Allocate space

Bytebuffer buf=ByteBuffer.allocate(16);
  • Write data to buffer
// Call the read method of channel
int readBytes = channel.read(buf);

// Call buffer's own put method
buf.put((byte)127);
  • Read data from buffer
// Call the write method of channel
int writeBytes = channel.write(buf);

// Call buffer's own get method
byte b = buf.get();
  • Other methods
    • flip: switch to read mode (mark position will be cleared)
    • rewind: reset position to 0 (mark position will be cleared)
    • Mark: mark the position at this time
    • reset: move position to mark

3. Selector

3.1 what is a Selector

  • A single thread can monitor multiple Channel read-write events by cooperating with the Selector, which is called multiplexing.
  • Multiplexing is only for network IO, and ordinary file IO cannot use multiplexing
  • Selector can guarantee
    • Connect only when there are connectable events
    • Read only when there are readable events
    • Write only when there are writable events
      • Because of the network transmission capacity, the Channel may not always be writable, which may lead to write blocking. Once the Channel can write, the writable event of the Selector will be triggered.

3.2 basic use of selector

  • establish
Selector selector = Selector.open();
  • Bind Channel events
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Binding event);
  • channel must work in non blocking mode, while FileChannel does not have non blocking mode, so it cannot be used with Selector
  • The events that can be bound are
    • connect: triggered when the client connection is successful
    • accept: triggered when the server successfully receives the connection
    • Read: triggered when the data can be read in. Because the receiving capacity is weak, the data can not be read in temporarily
    • write: triggered when the data can be written. Because the sending ability is weak, the data can not be written temporarily
  • Listen for all Channel registered events
// Blocking listening
int count = selector.select();

// With timeout listening
int count = selector.select(long timeout);

// Non blocking listening
int count = selector.selectNow();
  • When is blocking listening awakened
    • Blocking will wake up when any event in the monitored event set occurs
    • Call selector.wakeup()
    • Call selector.close()
    • The thread of the selector is interrupt ed
  • Handling accept events
@Slf4j
public class ChannelDemo6 {
    public static void main(String[] args) {
        try (ServerSocketChannel channel = ServerSocketChannel.open()) {
            channel.bind(new InetSocketAddress(8080));
            System.out.println(channel);
            Selector selector = Selector.open();
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                int count = selector.select();
                log.debug("select count: {}", count);

                // Get all events
                Set<SelectionKey> keys = selector.selectedKeys();

                // Traverse all events and handle them one by one
                Iterator<SelectionKey> iter = keys.iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    // Judge event type
                    if (key.isAcceptable()) {
                        ServerSocketChannel c = (ServerSocketChannel) key.channel();
                        // Must handle
                        SocketChannel sc = c.accept();
                        log.debug("{}", sc);
                    }
                    // After processing, the event must be removed
                    iter.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • After an event occurs, you can either process it or cancel it. You can't do nothing. Otherwise, the event will still be triggered the next time you select it, because the NIO underlying uses horizontal triggering.
  • The accept event is handled by calling the accept method of channel.
  • Handling read events
@Slf4j
public class ChannelDemo6 {
    public static void main(String[] args) {
        try (ServerSocketChannel channel = ServerSocketChannel.open()) {
            channel.bind(new InetSocketAddress(8080));
            System.out.println(channel);
            Selector selector = Selector.open();
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                int count = selector.select();
                log.debug("select count: {}", count);

                // Get all events
                Set<SelectionKey> keys = selector.selectedKeys();

                // Traverse all events and handle them one by one
                Iterator<SelectionKey> iter = keys.iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    // Judge event type
                    if (key.isAcceptable()) {
                        ServerSocketChannel c = (ServerSocketChannel) key.channel();
                        // Must handle
                        SocketChannel sc = c.accept();
                        sc.configureBlocking(false);
                        sc.register(selector, SelectionKey.OP_READ);
                        log.debug("Connection established: {}", sc);
                    } else if (key.isReadable()) {
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(128);
                        int read = sc.read(buffer);
                        // The returned value is - 1, which means that the connection is closed and needs special treatment
                        if(read == -1) {
                            key.cancel();
                            sc.close();
                        } else {
                            buffer.flip();
                            debug(buffer);
                        }
                    }
                    // After processing, the event must be removed
                    iter.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • When the client is disconnected from the server, the client will send a special read event to the server. The processing method of this read event is to cancel the channel registered on the selector.
  • Handling write events
public class WriteServer {

    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while(true) {
            selector.select();

            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                iter.remove();
                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey sckey = sc.register(selector, SelectionKey.OP_READ);
                    // 1. Send content to client
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 3000000; i++) {
                        sb.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
                    int write = sc.write(buffer);
                    // 3. write indicates how many bytes are actually written
                    System.out.println("Actual write byte:" + write);
                    // 4. If there are unread bytes left, you need to pay attention to the write event
                    if (buffer.hasRemaining()) {
                        // read 1  write 4
                        // Pay more attention to write events on the basis of the original events
                        sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);
                        // Add buffer to sckey as an attachment
                        sckey.attach(buffer);
                    }
                } else if (key.isWritable()) {
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    SocketChannel sc = (SocketChannel) key.channel();
                    int write = sc.write(buffer);
                    System.out.println("Actual write byte:" + write);
                    if (!buffer.hasRemaining()) { // Finished
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                        key.attach(null);
                    }
                }
            }
        }
    }
}
  • When the socket buffer is writable, the write event will be triggered frequently. Therefore, you should pay attention to the writable event only when the socket buffer cannot be written, and cancel the attention to the writable event after the data is written.

 

Topics: Netty network Network Protocol udp