NIO learning of Netty learning

Posted by yozyk on Sat, 13 Nov 2021 03:53:58 +0100

Netty learning NIO learning

I. three major components

The core of Java NIO system is: channel and buffer. A channel represents an open connection to an IO device (e.g., file, socket). If you need to use NIO system, you need to obtain the channel used to connect IO devices and the buffer used to hold data. Then operate the buffer to process the data. In short, the channel is responsible for transmission and the buffer is responsible for storage

1.1 Channel

1.2 Buffer

We mostly use ByteBuffer

1.3 Selector

1.3.1 using multithreading technology

  • The memory occupation is high, and each thread needs to occupy a certain amount of memory. When there are many connections, a large number of threads will be opened, resulting in a large amount of memory occupation
  • High cost of thread context switching
  • It is only suitable for scenarios with few connections. Too many connections will create many threads and cause problems

1.3.2 using thread pool technology

  • In blocking mode, a thread can only process one connection. After a thread in the thread pool acquires a task, it will acquire and execute the next task only after it finishes executing the task (after disconnecting)

  • If the socke connection has not been disconnected, its corresponding thread cannot process other socke connections

  • Short connection is to establish a connection and disconnect it immediately after sending a request and response, so that the threads in the thread pool can quickly process other connections

    1.3.3 Selector Technology

  • The function of the selector is to cooperate with a thread to manage multiple channels (filechannels are blocked, so the selector cannot be used) and obtain events on these channels. These channels work in non blocking mode. When there are no tasks in one channel, they can execute tasks in other channels. It is suitable for scenarios with many connections but less traffic
  • If the event is not ready, calling the select() method of the selector will block the thread until the ready event occurs in the channel. When these events are ready, the select method returns them to the thread for processing

Second, ByteBuffer

2.1 basic knowledge

  • Read and write absolute and relative get and put methods for a single byte.

  • Transfers a sequence of consecutive bytes from this buffer to the relative bulk get method in the array.

  • Bulk put the relative bulk put method that transfers a sequence of consecutive bytes in a byte array or other byte buffer to this buffer.

  • Absolute and relative get and put methods that read and write values of other primitive types, convert them to and from a byte sequence of a specific byte order.

  • The method of creating a view buffer, which allows the byte buffer to be regarded as a buffer containing some other original type values; and
    Methods of compacting, duplicating, and slicing byte buffers.

  • Byte buffers can be created by allocating (allocating space for the contents of the buffer) or by wrapping an existing byte array into the buffer.

2.2 basic structure

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>{
    //Member variable
    final byte[] hb;                
    final int offset;
    boolean isReadOnly; 
    
    //Construction method
    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }
    //Construction method
    ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
        this(mark, pos, lim, cap, null, 0);
    }
    }

2.3 basic methods

/**
Allocate a new byte buffer.
The location of the new buffer will be zero, its limit will be its capacity, its tag will be undefined, and each of its elements will be initialized to zero. It will have a backing array with an array offset of zero.
**/
public static ByteBuffer allocateDirect(int capacity) {}
public static ByteBuffer allocate(int capacity) {}

/**
Wrap a byte array into a buffer.
The new buffer will be supported by the given byte array; That is, modifying the buffer will cause the array to be modified, and vice versa. The capacity of the new buffer will be array.length, its position will be offset, its limit will be offset + length, and it will be marked undefined. Its backing array will be the given array, and its array offset will be zero
**/
public static ByteBuffer wrap(byte[] array,int offset, int length){}
public static ByteBuffer wrap(byte[] array) {}

2.4 basic structure of buffer

public abstract class Buffer {
    //mark <= position <= limit <= capacity
    //Member variable
 	private int mark = -1;
    //Starting value
    private int position = 0;
    //Maximum
    private int limit;
    //capacity
    private int capacity;
    
     //constructor 
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
    
}

2.5 Buffer method

/**
Set the limit for this buffer. If greater than the new limit, set it to the new limit. If the mark is defined and greater than the new limit, it is discarded.
**/
public final Buffer limit(int newLimit) {}

/**
Set the location of this buffer. If the mark is defined and larger than the new location, it is discarded.
**/
public final Buffer position(int newPosition) {}

/**
Resets the location of this buffer to the previously marked location.
**/
public final Buffer reset() {}

/**
Clear this buffer. Set the position to zero, set the limit to capacity, and discard the tag.
**/
public final Buffer clear() {}

/**
Flip this buffer. Set the limit to the current position, and then set the position to zero. If a tag is defined, it is discarded.
After a series of channels read or place operations, call this method to prepare a series of channel writes or related acquisition operations, and switch mode.
**/

public final Buffer flip() {}

/**
Is there a next element
**/
public final boolean hasRemaining() {}

/**
Tells whether the buffer is read-only.
**/
public abstract boolean isReadOnly();

/**
Rewind this buffer. The position is set to zero and the tag is discarded.
**/
public final Buffer rewind() {}

2.6 sticking package and half package

  • Sticky bag

    When sending data, the sender does not send data one by one, but integrates the data together and sends it together when the data reaches a certain amount. This will cause multiple messages to be put in a buffer and sent together

  • Half package
    The size of the receiver's buffer is limited. When the receiver's buffer is full, you need to truncate the information and continue to put data after the buffer is empty. This will lead to the phenomenon that a complete piece of data is finally truncated

2.7 simple use

package com.shu.ByteBuffer;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Author shu
 * @Date: 2021/11/12/ 16:35
 * @Description ByteBuffer Basic Usage 
 **/
public class TestByteBuffer {
    public static void main(String[] args) {
        // Get FileChannel
        try (FileChannel channel = new FileInputStream("shu.txt").getChannel()) {
            // Get buffer
            ByteBuffer buffer = ByteBuffer.allocate(10);
            int hasNext = 0;
            StringBuilder builder = new StringBuilder();
            while((hasNext = channel.read(buffer)) > 0) {
                // Switch mode limit=position, position=0
                buffer.flip();
                // When there is still data in the buffer, obtain the data
                while(buffer.hasRemaining()) {
                    builder.append((char)buffer.get());
                }
                // Switching mode position=0, limit=capacity
                buffer.clear();
            }
            System.out.println(builder.toString());
        } catch (IOException e) {
        }
    }
}

Three channels

3.1 FileChannel

FileChannel can only work in blocking mode, so it cannot be matched with Selector

3.1.1 basic knowledge

  • Channels for reading, writing, mapping, and manipulating files.
  • A file channel is a SeekableByteChannel connected to a file. It has a current location in its file, which can be queried and modified.
  • The file itself contains a variable length byte sequence, which can be read and written, and its current size can be queried.
  • When the written bytes exceed their current size, the file size will increase; When a file is truncated, the size of the file decreases.
  • The file may also have some related metadata, such as access rights, content type and last modification time; This class does not define a method for metadata access.
  • In addition to the familiar byte channel read, write, and close operations, this class also defines the following file specific operations:
  • You can read or write bytes at the absolute position in the file without affecting the current position of the channel.
  • An area of a file can be mapped directly into memory; For large files, this is usually more effective than calling the usual read or write methods.
  • Updates to files may be forced out of the underlying storage device to ensure that data is not lost in the event of a system crash.
  • Bytes can be transferred from the file to some other channel, vice versa. This method can be optimized by many operating systems for very fast direct transfer to or from the file system cache.
  • An area of the file may be locked to prevent other programs from accessing it.

3.1.2 basic structure

public abstract class FileChannel
    extends AbstractInterruptibleChannel
    implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
{
    //constructor 
    protected FileChannel() { }
}

3.1.2 basic methods

/**
Open or create a file and return a file channel to access the file.
options Parameter determines how the file is opened. The READ and WRITE options determine whether a file should be opened for reading and / or writing. If the array does not contain any options (or APPEND options), the file is opened for reading. By default, reads or writes start at the beginning of the file
**/
public static FileChannel open(Path path, Set<? extends OpenOption> options,FileAttribute<?>... attrs)throws IOException{}
public static FileChannel open(Path path, OpenOption... options)
        throws IOException{}

/**
Reads a sequence of bytes from this channel to the given buffer.
Read bytes from the current file location of the channel, and then update the file location with the actual number of bytes read. Otherwise, the behavior of this method is exactly the same as that specified in the ReadableByteChannel interface.
**/

 public abstract int read(ByteBuffer dst) throws IOException;

 public abstract long read(ByteBuffer[] dsts, int offset, int length)
        throws IOException;

 public final long read(ByteBuffer[] dsts) throws IOException {
        return read(dsts, 0, dsts.length);
    }


/**
Writes a sequence of bytes from the given buffer to this channel.
Bytes are written from the current file location of the channel unless the channel is in append mode, in which case the location first advances to the end of the file. If necessary, the file grows to accommodate the bytes written, and then updates the file location with the actual number of bytes written. Otherwise, the behavior of this method is exactly the same as specified by the WritableByteChannel interface.
**/

public abstract int write(ByteBuffer src) throws IOException;

public abstract long write(ByteBuffer[] srcs, int offset, int length)
        throws IOException;

public final long write(ByteBuffer[] srcs) throws IOException {
        return write(srcs, 0, srcs.length);
    }

/**
Returns the current location
**/

public abstract long position() throws IOException;

/**
Zero copy: this method may be more effective than a simple loop that reads from and writes to the target channel. Many operating systems can transfer bytes directly from the file system cache to the target channel without actually copying them.
**/
public abstract long transferTo(long position, long count,
                                    WritableByteChannel target)
        throws IOException;

/**
Force write: forces any updates to this channel file to be written to the storage device containing it.
**/
 public abstract void force(boolean metaData) throws IOException;

3.1.4 basic application

public class TestChannel {
    public static void main(String[] args){
        try (FileInputStream fis = new FileInputStream("shu.txt");
             FileOutputStream fos = new FileOutputStream("student.txt");
             FileChannel inputChannel = fis.getChannel();
             FileChannel outputChannel = fos.getChannel()) {
            long size = inputChannel.size();
            long capacity = inputChannel.size();
            // Multiple transmission
            while (capacity > 0) {
                // The return value of transferTo is the number of bytes transferred
                capacity -= inputChannel.transferTo(size-capacity, capacity, outputChannel);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.2 ServerSocketChannel

3.2.1 blocking

  • In blocking mode, the related methods will cause the thread to pause
    • ServerSocketChannel.accept suspends the thread when no connection is established
    • SocketChannel.read will pause the thread when there is no data readable in the channel
    • The performance of blocking is actually that the thread is suspended. During the suspension, the cpu will not be occupied, but the thread is equivalent to idle
  • In a single thread, blocking methods interact with each other and can hardly work normally, requiring multithreading support
  • However, under multithreading, there are new problems, which are reflected in the following aspects
    • A 32-bit jvm has 320k threads and a 64 bit jvm has 1024k threads. If there are too many connections, it will inevitably lead to OOM and too many threads. On the contrary, the performance will be reduced due to frequent context switching
    • Thread pool technology can be used to reduce the number of threads and thread context switching, but the symptoms are not the root cause. If many connections are established but inactive for a long time, all threads in the thread pool will be blocked. Therefore, it is not suitable for long connections, but only for short connections

3.2.2 non blocking

  • You can set the obtained connection to non blocking through the configureBlocking(**false * *) method of ServerSocketChannel. If there is no connection, accept will return null
  • You can set the data read from the channel to non blocking through the configureBlocking(**false * *) method of SocketChannel. If there is no data readable in the channel at this time, read will return - 1

3.2.3 basic structure

public abstract class ServerSocketChannel extends AbstractSelectableChannel
 implements NetworkChannel{
    //constructor 
     protected ServerSocketChannel(SelectorProvider provider) {
        super(provider);
    }
    }

3.2.4 basic methods

/**
Open a server socket channel.
The new channel is created by calling the openServerSocketChannel method of the system wide default SelectorProvider object.
The socket of the new channel is initially unbound; Before accepting a connection, it must be bound to a specific address through one of the bind methods of its socket
**/
 public static ServerSocketChannel open() throws IOException {
        return SelectorProvider.provider().openServerSocketChannel();
    }


/**
Bind a port
**/

 public final ServerSocketChannel bind(SocketAddress local)
        throws IOException
    {
        return bind(local, 0);
    }


/**
Accept connections to sockets for this channel.
If this channel is in non blocking mode, this method will immediately return null if there are no pending connections. Otherwise, it will block indefinitely until a new connection is available or an I/O error occurs.
The socket channel (if any) returned by this method will be in blocking mode, regardless of the blocking mode of this channel.
This method performs exactly the same security checks as the accept method of the ServerSocket class. That is, if the security manager has been installed, for each new connection, this method will verify whether the checkAccept method of the security manager allows the address and port number of the remote endpoint connected.
**/
 public abstract SocketChannel accept() throws IOException;

3.2.5 basic structure of abstractselectablechannel

/**
This class defines how to handle channel registration, deregistration, and shutdown mechanisms. It maintains the current blocking mode of the channel and its current selection key set. It performs all the synchronization required to implement the SelectableChannel specification. The implementation of the abstract protected method defined in this class does not need to be synchronized with other threads that may participate in the same operation.
**/
public abstract class AbstractSelectableChannel extends SelectableChannel{
    
     // The provider that created this channel
    private final SelectorProvider provider;

    // Keys that have been created by registering this channel with selectors.
    // They are saved because if this channel is closed the keys must be
    // deregistered.  Protected by keyLock.
    //
    private SelectionKey[] keys = null;
    private int keyCount = 0;

    // Lock for key set and count
    private final Object keyLock = new Object();

    // Lock for registration and configureBlocking operations
    private final Object regLock = new Object();
	
    // Blocking mode, protected by regLock
    boolean blocking = true;
    
    
    protected AbstractSelectableChannel(SelectorProvider provider) {
        this.provider = provider;
    }
    }

3.2.6 AbstractSelectableChannel basic method

/**
Adjust the blocking mode of this channel.
If the given blocking mode is different from the current blocking mode, this method calls the implConfigureBlocking method while holding the appropriate lock to change the mode
**/
 public final SelectableChannel configureBlocking(boolean block)
        throws IOException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if (blocking == block)
                return this;
            if (block && haveValidKeys())
                throw new IllegalBlockingModeException();
            implConfigureBlocking(block);
            blocking = block;
        }
        return this;
    }
    
/**
Register the channel with the given selector and return a selection key.
This method first verifies whether the channel is open and whether the given initial interest set is valid.
If the channel has been registered with the given selector, the selection key representing the registration will be returned after setting its interest set to the given value.
Otherwise, the channel has not been registered with the given selector, so the register method of the selector is called when the appropriate lock is held. The result key is added before returning
**/

 public final SelectionKey register(Selector sel, int ops,
                                       Object att)
        throws ClosedChannelException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if ((ops & ~validOps()) != 0)
                throw new IllegalArgumentException();
            if (blocking)
                throw new IllegalBlockingModeException();
            SelectionKey k = findKey(sel);
            if (k != null) {
                k.interestOps(ops);
                k.attach(att);
            }
            if (k == null) {
                // New registration
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
            }
            return k;
        }
    }

3.2.7 basic realization

public class Server {
    public static void main(String[] args) {
        // Create buffer
        ByteBuffer buffer = ByteBuffer.allocate(16);
        // Get server channel
        try(ServerSocketChannel server = ServerSocketChannel.open()) {
            // Bind port for server channel
            server.bind(new InetSocketAddress(8080));
            // Collection of user stored connections
            ArrayList<SocketChannel> channels = new ArrayList<>();
            // Cyclic receive connection
            while (true) {
                // Set to non blocking mode, return null when there is no connection, and the thread will not be blocked
                server.configureBlocking(false);
                SocketChannel socketChannel = server.accept();
                // The connection is put into the collection only when the channel is not empty
                if (socketChannel != null) {
                    System.out.println("after connecting...");
                    channels.add(socketChannel);
                }
                // Loop through the connections in the collection
                for(SocketChannel channel : channels) {
                    // Processing data in channels
                    // Set to non blocking mode. If there is no data in the channel, it will return 0 and will not block the thread
                    channel.configureBlocking(false);
                    int read = channel.read(buffer);
                    if(read > 0) {
                        buffer.flip();
                        ByteBufferUtil.debugRead(buffer);
                        buffer.clear();
                        System.out.println("after reading");
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


public class Client {
    public static void main(String[] args) {
        try (SocketChannel socketChannel = SocketChannel.open()) {
            // Establish connection
            socketChannel.connect(new InetSocketAddress("localhost", 8080));
            System.out.println("waiting...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Four selectors

Selector is generally called selector, which can also be translated into multiplexer. It is one of the core components of Java NIO. Its main function is to check whether the state of one or more NiO channels is readable and writable. In this way, a single thread can manage multiple channels and, of course, multiple network connections.

4.1 Selector

4.1.1 basic knowledge

  • Set the channel to non blocking mode, register it in the selector, and set the events of interest

  • channel must work in non blocking mode

  • FileChannel does not have a non blocking mode, so it cannot be used with a selector

  • connect - triggered when the client connection is successful

  • accept - triggered when the server successfully accepts the connection

  • Read - triggered when the data can be read in, because the receiving capacity is weak and the data cannot be read in temporarily

  • write - triggered when the data can be written out. The data cannot be written out temporarily because of weak sending ability

  • Multiplexer for the SelectableChannel object. You can create a selector by calling the open method of this class, which will use the system default selector provider to create a new selector.

  • You can also create a selector by calling the openSelector method of a custom selector provider. The selector remains open until it is closed by its close method.

4.1.2 basic structure

public abstract class Selector implements Closeable {
     protected Selector() { }
}

4.1.3 basic methods

/**
Open a selector. The new selector is created by calling the openSelector method of the system wide default SelectorProvider object.
**/
public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}

/**
Causes the first selection operation that has not been returned to return immediately.
If another thread is currently blocked in a call to the select() or select(long) method, the call will return immediately.
**/
 public abstract Selector wakeup();

/**
Close this selector.
If a thread is currently blocked in one of the selector's selection methods, it will be interrupted, just like calling the selector's wakeup method.
**/
public abstract void close() throws IOException;

/**
Select a set of keys whose corresponding channel is ready for I/O operation.
This method performs a blocking selection operation. It returns only after selecting at least one channel, calling the wakeup method of this selector, or the current thread is interrupted, whichever comes first. And get the number of ready channels. If no channel is ready, the thread will be blocked
**/
public abstract int select() throws IOException;

/**
Returns the selected keyset of this selector, gets the ready event and gets the corresponding channel
**/
public abstract Set<SelectionKey> selectedKeys();

4.2 SelectionKey

4.2.1 basic knowledge

  • Indicates that the SelectableChannel is registered with the Selector token.
  • Each time you register a channel using the selector, a selection key is created.
  • A key remains valid until it is called to cancel it, either by closing its channel, or by closing its selector.
  • Canceling a key does not immediately remove it from its selector; It is added to the selector's cancel keyset for removal during the next selection operation.
  • You can test the validity of the key by calling its isValid method.

4.2.2 basic structure

public abstract class SelectionKey {
    protected SelectionKey() { }
}

4.2.3 basic methods

 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;

/**
Tell me if this key is valid.
A key is valid at creation and remains until it is cancelled, its channel is closed, or its selector is closed.
**/
 public abstract boolean isValid();


/**
Requests that the channel for this key and its selector be unregistered. When returned, the key will be invalid and will be added to the cancel key set of its selector. During the next selection operation, the key is removed from the keyset of all selectors.
**/
 public abstract void cancel();


/**
Test whether this channel is ready to read.
**/
 public final boolean isReadable() {
        return (readyOps() & OP_READ) != 0;
}

/**
Test whether the channel is ready to write
**/
 public final boolean isWritable() {
        return (readyOps() & OP_WRITE) != 0;
    }

/**
Test whether the channel has completed or failed to complete its socket connection operation.
**/
public final boolean isConnectable() {
        return (readyOps() & OP_CONNECT) != 0;
    }

/**
Test whether this channel is ready to accept new socket connections.
**/

 public final boolean isAcceptable() {
        return (readyOps() & OP_ACCEPT) != 0;
    }

4.3 SelectorProvider

4.3.1 basic knowledge

  • Service provider classes for selectors and optional channels.
  • A selector provider is a concrete subclass of this class that has a zero argument constructor and implements the abstract method specified below.
  • A given call to the Java virtual machine maintains a system wide default provider instance that is returned by the provider method. The first call to this method locates the default provider specified below.
  • The system wide default provider is used by the static open methods of DatagramChannel, Pipe, Selector, ServerSocketChannel, and SocketChannel classes.
  • It is also used by the System.inheritedChannel() method.
  • Programs can use providers other than the default provider by instantiating the provider and then directly calling the open methods defined in this class.
  • All methods in this class are safe for use by multiple concurrent threads.

4.3.2 basic structure

public abstract class SelectorProvider {

    private static final Object lock = new Object();
    private static SelectorProvider provider = null;
    
    protected SelectorProvider() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null)
            sm.checkPermission(new RuntimePermission("selectorProvider"));
    }
    }

4.3.3 basic methods

/**
Returns a system wide default selector provider for this call to the Java virtual machine.
The first call to this method locates the default provider object
**/
public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

  public abstract DatagramChannel openDatagramChannel(ProtocolFamily family)
        throws IOException;


 public abstract ServerSocketChannel openServerSocketChannel()
        throws IOException;


 public abstract SocketChannel openSocketChannel()
        throws IOException;


/**
Returns the channel inherited from the entity that created this Java virtual machine.
On many operating systems, a process, such as a Java virtual machine, can be started in a way that allows the process to inherit channels from the entity that created the process. How you do this depends on the system, as do the possible entities to which the channel may be connected. For example, on UNIX systems, when a request reaches the associated network port, the Internet service daemon (inetd) is used to start the program to service the request. In this example, the starting process inherits a channel representing a network socket.
If the inherited Channel represents a network socket, the Channel type returned by this method is determined as follows:
If the inherited channel represents a stream oriented connection socket, SocketChannel is returned. The socket channel is at least initially in blocking mode, bound to the socket address, and connected to the peer.
If the inherited channel represents a stream oriented listening socket, ServerSocketChannel is returned. The server socket channel is at least initially in blocking mode and bound to the socket address.
DatagramChannel if the inherited channel is a datagram oriented socket. The datagram channel is at least initially in blocking mode and bound to the socket address.
In addition to the network oriented channels described, this method may return other types of channels in the future.
The first call to this method creates the returned channel. Subsequent calls to this method return the same channel.
**/
   public Channel inheritedChannel() throws IOException {
        return null;
   }

4.4 basic usage

4.4.1 Accpet events

public class SelectServer {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(16);
        // Get server channel
        try(ServerSocketChannel server = ServerSocketChannel.open()) {
            server.bind(new InetSocketAddress(8080));
            // Create selector
            Selector selector = Selector.open();
            
            // The channel must be set to non blocking mode
            server.configureBlocking(false);
            // Register the channel in the selector and set the events of interest
            server.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                // If no event is ready, the thread will be blocked, otherwise it will not be blocked. This avoids CPU idling
                // The return value is the number of events ready
                int ready = selector.select();
                System.out.println("selector ready counts : " + ready);
                
                // Get all events
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                
                // Traversing events using iterators
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    
                    // Determine the type of key
                    if(key.isAcceptable()) {
                        // Obtain the channel corresponding to the key
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        System.out.println("before accepting...");
                        
        				// Get the connection and process it, and it must be processed, otherwise it needs to be cancelled
                        SocketChannel socketChannel = channel.accept();
                        System.out.println("after accepting...");
                        
                        // Remove after processing
                        iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.4.2 Read event

public class SelectServer {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(16);
        // Get server channel
        try(ServerSocketChannel server = ServerSocketChannel.open()) {
            server.bind(new InetSocketAddress(8080));
            // Create selector
            Selector selector = Selector.open();
            // The channel must be set to non blocking mode
            server.configureBlocking(false);
            // Register the channel in the selector and set up the practice of interest
            server.register(selector, SelectionKey.OP_ACCEPT);
            // Set events of interest for serverKey
            while (true) {
                // If no event is ready, the thread will be blocked, otherwise it will not be blocked. This avoids CPU idling
                // The return value is the number of events ready
                int ready = selector.select();
                System.out.println("selector ready counts : " + ready);
                // Get all events
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // Traversing events using iterators
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    // Determine the type of key
                    if(key.isAcceptable()) {
                        // Obtain the channel corresponding to the key
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        System.out.println("before accepting...");
                        // Get connection
                        SocketChannel socketChannel = channel.accept();
                        System.out.println("after accepting...");
                        // Set to non blocking mode and register the connected channel to the selection
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        // Remove after processing
                        iterator.remove();
                    } else if (key.isReadable()) {
                        SocketChannel channel = (SocketChannel) key.channel();
                        System.out.println("before reading...");
                        channel.read(buffer);
                        System.out.println("after reading...");
                        buffer.flip();
                        ByteBufferUtil.debugRead(buffer);
                        buffer.clear();
                        // Remove after processing
                        iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.4.3 Write event

public class WriteServer {
    public static void main(String[] args) {
        try(ServerSocketChannel server = ServerSocketChannel.open()) {
            server.bind(new InetSocketAddress(8080));
            server.configureBlocking(false);
            Selector selector = Selector.open();
            server.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    // Remove event after processing
                    iterator.remove();
                    if (key.isAcceptable()) {
                        // Get the channel of the client
                        SocketChannel socket = server.accept();
                        // Write data
                        StringBuilder builder = new StringBuilder();
                        for(int i = 0; i < 500000000; i++) {
                            builder.append("a");
                        }
                        ByteBuffer buffer = StandardCharsets.UTF_8.encode(builder.toString());
                        // First, write to buffer - > channel. If it is not finished, add a writable event
                        int write = socket.write(buffer);
                        System.out.println(write);
                        // The channel may not be able to put all the data in the buffer
                        if (buffer.hasRemaining()) {
                            // Register in the Selector, pay attention to writable events, and add the buffer to the attachment of the key
                            socket.configureBlocking(false);
                            socket.register(selector, SelectionKey.OP_WRITE, buffer);
                        }
                    } else if (key.isWritable()) {
                        SocketChannel socket = (SocketChannel) key.channel();
                        // Get buffer
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        // Perform write operation
                        int write = socket.write(buffer);
                        System.out.println(write);
                        // If you have completed the write operation, you need to remove the attachment in the key and are no longer interested in the write event
                        if (!buffer.hasRemaining()) {
                            key.attach(null);
                            key.interestOps(0);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.5 multithreading optimization

  • Idea: each thread performs its own duties. Like the medical treatment process of the hospital, each unit performs its own duties
public class ThreadsServer {
    public static void main(String[] args) {
        try (ServerSocketChannel server = ServerSocketChannel.open()) {
            // The current thread is the Boss thread
            Thread.currentThread().setName("Boss");
            server.bind(new InetSocketAddress(8080));
            // The Selector responsible for polling the Accept event
            Selector boss = Selector.open();
            server.configureBlocking(false);
            server.register(boss, SelectionKey.OP_ACCEPT);
            // Create a fixed number of workers
            Worker[] workers = new Worker[4];
            // Atomic integer for load balancing
            AtomicInteger robin = new AtomicInteger(0);
            for(int i = 0; i < workers.length; i++) {
                workers[i] = new Worker("worker-"+i);
            }
            while (true) {
                boss.select();
                Set<SelectionKey> selectionKeys = boss.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    // BossSelector is responsible for the Accept event
                    if (key.isAcceptable()) {
                        // Establish connection
                        SocketChannel socket = server.accept();
                        System.out.println("connected...");
                        socket.configureBlocking(false);
                        // The socket is registered in the Selector of the Worker
                        System.out.println("before read...");
                        // Load balancing, polling allocation, Worker
                        workers[robin.getAndIncrement()% workers.length].register(socket);
                        System.out.println("after read...");
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class Worker implements Runnable {
        private Thread thread;
        private volatile Selector selector;
        private String name;
        private volatile boolean started = false;
        /**
         * Synchronization queue is used for communication between Boss thread and Worker thread
         */
        private ConcurrentLinkedQueue<Runnable> queue;

        public Worker(String name) {
            this.name = name;
        }

        public void register(final SocketChannel socket) throws IOException {
            // Start only once
            if (!started) {
                thread = new Thread(this, name);
                selector = Selector.open();
                queue = new ConcurrentLinkedQueue<>();
                thread.start();
                started = true;
            }
            
            // Add the registration event of SocketChannel to the synchronization queue
            // Execute the registration event in the Worker thread
            queue.add(new Runnable() {
                @Override
                public void run() {
                    try {
                        socket.register(selector, SelectionKey.OP_READ);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            // Wake up blocked Selector
            // select is similar to park in LockSupport, and the principle of wakeup is similar to unpark in LockSupport
            selector.wakeup();
        }

        @Override
        public void run() {
            while (true) {
                try {
                    selector.select();
                    // Get the task through the synchronization queue and run it
                    Runnable task = queue.poll();
                    if (task != null) {
                        // Obtain the task and perform the registration operation
                        task.run();
                    }
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while(iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        // Worker is only responsible for Read events
                        if (key.isReadable()) {
                            // Simplify processing and omit details
                            SocketChannel socket = (SocketChannel) key.channel();
                            ByteBuffer buffer = ByteBuffer.allocate(16);
                            socket.read(buffer);
                            buffer.flip();
                            ByteBufferUtil.debugAll(buffer);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

V. NIO and BIO

5.1 Stream and Channel

  • stream will not automatically buffer data, and channel will use the send buffer and receive buffer (lower layer) provided by the system
  • stream only supports blocking API, channel supports both blocking and non blocking API, and network channel can cooperate with selector to realize multiplexing
  • Both are full duplex, that is, reading and writing can be carried out at the same time
    • Although the Stream is unidirectional, it is also full duplex

5.2 IO model

  • synchronization

    : the thread gets the result by itself (a thread)

    • For example, after a thread calls a method, it needs to wait for the method to return the result
  • asynchronous

    : the thread does not get the result itself, but other threads return the result (at least two threads)

    • For example, after thread A calls A method, it continues to run downward, and the running result is returned by thread B

After calling channel.read or stream.read once, the user mode will be switched to the operating system kernel mode to complete the real data reading, and the reading is divided into two stages, namely:

  • Waiting for data phase

  • Copy data phase

According to UNIX network programming - Volume I, IO models mainly include the following

5.2.1 blocking IO

  • When a user thread performs a read operation, it needs to wait for the operating system to perform the actual read operation. During this period, the user thread is blocked and cannot perform other operations

5.2.2 non blocking IO

  • User thread

    Keep calling the read method in a loop

    , if there is no data readable in kernel space, return immediately

    • Non blocking only in the waiting phase
  • After the user thread finds that there is data in the kernel space, it waits for the kernel space to copy the data, and returns the result after the copy is completed

5.2.3 multiplexing

Multiplexing through Selector in Java

  • When there is no event, calling the select method will be blocked
  • Once one or more events occur, the corresponding events will be processed to realize multiplexing

Difference between multiplexing and blocking IO

  • In the blocking IO mode, if the thread is blocked due to the accept event, after the read event occurs, it still needs to wait for the accept event to complete before processing the read event
  • In multiplexing mode, the execution of an event will not be affected if another event is blocked after the occurrence of an event

5.2.4 asynchronous IO

  • Thread 1 understands the return after calling the method. It will not be blocked and does not need to get the result immediately
  • When the running result of the method comes out, thread 2 returns the result to thread 1

Reference blog: https://nyimac.gitee.io

Topics: Java Netty Back-end