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