This is the 14th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
A previous blog post on Netty programming (I) – BIO and Juejin (juejin. Cn) mentioned that one of NIO’s three major components is a component called selector, which can select multiple channels. In the last blog, Netty programming (5) — NIO module blocking and non-blocking mode – mining (juejin. Cn) mentioned that channel, buffer non-blocking mode will have CPU idle problems, so in this blog to introduce selector, It solves problems in non-blocking mode, making programs more efficient.
The Selector an overview
A single thread can monitor read and write events of multiple channels with Selector, which is called multiplexing. For multiplexing, the following points need to be paid attention to:
- Multiplexing applies only to network IO. Common file IO cannot use multiplexing
- If you don’t use Selector’s non-blocking mode, the thread is doing nothing most of the time, and Selector can guarantee that
- Connect only when there are connectable events
- Read only when there are readable events
- Write only when there are writable events
- A Channel may not always be writable due to network transmission capacity. Once a Channel is writable, a writable event of the Selector will be triggered
Selector use method
1. Create Selector
To create a Selector object, call Selector’s static factory method Selector.open() :
Selector selector = Selector.open();
Copy the code
2. Set up ServerSocketChannel, set the channel to non-blocking mode, register it in the selector, and set the events of interest
A channel must work in non-blocking mode, whereas a FileChannel does not have a non-blocking mode, so it cannot be used with a selector
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
Copy the code
3. Register ServerSocketChannel with the selector and set the events of interest
By registering a channel with a Selector, you can tell selecor to listen for events on the channel and handle them when they occur, otherwise they will block, greatly improving CPU utilization. The Selector class doesn’t have a method that adds channels. The SelectableChannel class has a register(), and by passing a Selector to the register method, you can register that channel with the Selector.
SelectionKey ssckey = ssc.register(selector,0);
Copy the code
The first argument to the register method is the selector to register with, and the second argument is a constant from the SelectionKey class, which represents the operations registered by the channel. There are four operations:
- Selectionkey. OP_ACCEPT: Triggered when the server successfully accepts the connection
- Selectionkey. OP_CONNECT: triggered when the client connection is successful
- Selectionkey. OP_READ: triggered when data can be read in, but data cannot be read temporarily due to weak receiving capability
- Selectionkey. OP_WRITE: triggered when data can be written out
However, you can set it to 0 in the Register method (such as the code above) and then select it via the interestOps method.
ssckey.interestOps(SelectionKey.OP_ACCEPT);
Copy the code
So you can see that the register method returns a SelectionKey object, and the selector uses the SelectionKey object to tell you what channel the event was on and what event happened, but usually you don’t need to keep the SelectionKey object that the register returns, The selectedKeys() method can return the same object again in a Set, as shown below.
4. Selector listens for events
A Selector has a select method that listens for events. If no channel is ready (that is, no channel-triggered event is registered with that Selector), the program blocks at that method. When an event occurs, execution continues, and the select method returns the number of channels that are ready.
int channelnum = selector.select();
Copy the code
5. Get the ready event and the corresponding channel, and then process it
When an event occurs, instead of blocking on selector. Select (), we need to handle the event. We can use the following statement to get the entire set of events:
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
Copy the code
We’re using an iterator here, because selector. SelectedKeys () returns a set, and that set is deleted later, so we iterate over it, not loop over it. As you can see, each iteration gets a SelectionKey object, which is used to get the type of the event and the channel on which the event occurred.
Let’s start with client-connection server events to show how to handle events using the following code template:
while(true)
{
int channelnum = selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();// What channel is the event
iter.remove();
// Separate event types
if (key.isAcceptable()) {
ServerSocketChannel channel = ((ServerSocketChannel) key.channel());// Select channel from key
SocketChannel sc = channel.accept();// Establish a connection
sc.configureBlocking(false);
SelectionKey sckey = sc.register(selector, 0.null);
sckey.interestOps(SelectionKey.OP_READ);
}
else if(key.isReadable()){
}
else if(key.isWritable()){
}
else if(key.isConnectable()){
}
}
}
Copy the code
Setkey = ite.next (); setKey = setKey (); setKey = ite.next ();
key.isAcceptable();
key.isReadable();
key.isWritable();
key.isConnectable();
Copy the code
To get the channel where the event occurred, use the following statement. Note that most of the time it needs to be cast:
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
Copy the code
Once the new SocketChannel connects to the server, it needs to be registered with the selector, and the read and write events are registered, so that a connection event is handled. Also, notice that each iteration performs a delete operation ite.remove (), which will be explained in conjunction with read events in the next blog post.
Event cancellation
Iterator
iter = selector. SelectedKeys ().iterator(). But if you don’t handle the event that happened, the selector will assume that the event always exists, and it will loop through, as in the following code:
while(true)
{
int channelnum = selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();// What channel is the event
iter.remove();
if (key.isAcceptable()) {
else if(key.isReadable()){
}
else if(key.isWritable()){
}
else if(key.isConnectable()){
}
}
}
Copy the code
So in the code above, it’s going to iterate over the event when it’s detected, but it’s not going to process the event, so after one iteration it’s going to go back to select(), and it’s not going to block, it’s going to keep looping, because if you didn’t process it, the selector would think that the event still exists. There are two ways to handle the event or cancel the event using the cancel() method:
if (key.isAcceptable()) {
key.cancel();
}
Copy the code
So after an event occurs, either process it or cancel it. You can’t do nothing, otherwise the event will fire again the next time.
The complete code
The complete server code for handling connection events is shown below:
public class SelectorServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
// A selector can manage multiple channels. You need to establish a link between a selector and a channel, and register a channel with the selector
try (ServerSocketChannel ssc = ServerSocketChannel.open()) {
ssc.configureBlocking(false);
SelectionKey ssckey = ssc.register(selector,0.null);// If an event occurs in the future, you can use this key to know which event and which channel it is
// Set ssckey to accept events only
ssckey.interestOps(SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(8080));
while(true)
{
int channelnum = selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
// Separate event types
if (key.isAcceptable()) {
ServerSocketChannel channel = ((ServerSocketChannel) key.channel());// Select channel from key
SocketChannel sc = channel.accept();// Establish a connection
sc.configureBlocking(false);
SelectionKey sckey = sc.register(selector, 0.null);
sckey.interestOps(SelectionKey.OP_READ);
}
else if(key.isReadable()){
}
else if(key.isWritable()){
}
else if(key.isConnectable()){
}
}
}
}
}
}
Copy the code
This blog first looks at the role of selector and how to handle connection events using selector. The next blog will continue to look at read and write events for selector.