Introduction to a Selector

A Selector is usually called a Selector, but you can also translate it as a multiplexer. It is one of the Java NIO core components that checks whether the state of one or more NIO channels is readable and writable. In this way, a single thread can manage multiple channels, that is, multiple network links can be managed.

The advantage of using Selector is that fewer threads can be used to process the channel, avoiding the overhead of thread context switching compared to using multiple threads.

An introduction to using two selectors

1. Creation of Selector

Create a Selector object by calling Selector. Open () as follows:

Selector selector = Selector.open();
Copy the code

There’s a caveat here

2. Register a Channel with Selector

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
Copy the code

A Channel must be non-blocking. So a FileChannel is not suitable for Selector, because a FileChannel cannot switch to non-blocking mode, or more precisely because a FileChannel does not inherit SelectableChannel. Socket channel can be used normally.

The SelectableChannel abstract class has a configureBlocking () method used to put the channel in blocking or non-blocking mode.

abstract SelectableChannel configureBlocking(boolean block)  
Copy the code

Note:

SelectableChannel configureBlocking of abstract class () method is implemented by AbstractSelectableChannel abstract classes, SocketChannel, ServerSocketChannel, DatagramChannel is directly inherited AbstractSelectableChannel abstract classes. You can have a look at NIO source code, a variety of abstract classes and abstract classes on the top of the abstract class. I’m not going to look at the NIO source code for the moment, because there’s a lot of work to be done, so if you need to look at it, you can look at it yourself.

The second argument to the register() method. This is an “interest set,” meaning what event is of interest when listening to a Channel with Selector. You can listen for four different types of events:

  • Connect
  • Accept
  • Read
  • Write

The channel raised an event meaning that the event is ready. For example, a Channel that successfully connects to another server is “connection-ready.” A Server Socket Channel that is ready to receive incoming connections is called “receive ready”. A channel with data to read can be said to be “read ready”. A channel waiting for data to be written can be said to be “write ready”.

The four kinds of events are represented by the four constants of SelectionKey:

SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
Copy the code

If you are interested in more than one kind of event, use the or operator, as follows:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
Copy the code

3. SelectionKey is introduced

A SelectionKey represents the registration relationship between a particular channel object and a particular selector object.

key.attachment(); // Return the attachment of SelectionKey, which can be specified when registering a channel. key.channel(); // Return the channel corresponding to the SelectionKey. key.selector(); // Returns the Selector corresponding to the SelectionKey. key.interestOps(); // Return the bit mask key.readyops () that represents the IO operations that need to be monitored by Selector; // Return a bit mask representing the IO operations that can be performed on the corresponding channel.Copy the code

key.interestOps():

We can tell if a Selector is interested in some event of a Channel by using the following method

int interestSet = selectionKey.interestOps(); 
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
Copy the code

key.readyOps()

The Ready collection is a collection of operations for which the channel is ready. The following methods are defined in JAVA to check that these operations are ready.

Int readySet = selectionKey.readyops (); Method of checking whether these operations are acceptable key.isacceptable (); Boolean isWritable() : // whether it isWritable, true Boolean isConnectable() : // whether it isConnectable, true Boolean isAcceptable() : // Whether it can be received, if trueCopy the code

Accessing channels and selectors from the SelectionKey is easy. As follows:

Channel channel = key.channel();
Selector selector = key.selector();
key.attachment();
Copy the code

An object or more information can be attached to the SelectionKey to make it easy to identify a given channel. For example, you can attach a Buffer that is used with a channel, or an object that contains aggregated data. The usage method is as follows:

key.attach(theObject);
Object attachedObj = key.attachment();
Copy the code

You can also attach objects when you register a Channel with a Selector using the register() method. Such as:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Copy the code

4. Selecting Channels via a Selector

The selector maintains a collection of registered channels, and the registration relationship is encapsulated in the SelectionKey.

Selector maintains three sets of types of selectionkeys:

  • Registered Key Set (Registered key set)

    The set of keys generated by all channels associated with a selector is called the set of registered keys. Not all registered keys are still valid. This collection is returned via the keys() method and may be empty. The set of registered keys is not directly modifiable; Trying to do so would trigger a Java. Lang. UnsupportedOperationException.

  • Selected key set

    The set of keys generated by all channels associated with a selector is called the set of registered keys. Not all registered keys are still valid. This collection is returned via the keys() method and may be empty. The set of registered keys is not directly modifiable; Trying to do so would trigger a Java. Lang. UnsupportedOperationException.

  • Cancelled key set

    A subset of the set of registered keys that contains the key for which the cancel() method was called (the key has been invalidated), but which has not yet been deregistered. This collection is a private member of the selector object and therefore cannot be accessed directly.

    Note: When a key is cancelled (as determined by the isValid() method), it is placed in the collection of cancelled keys of the relevant selector. The registration will not be cancelled immediately, but the key will be deactivated immediately. When the select() method is called again (or when an ongoing select() call ends), the cancelled keys in the collection of cancelled keys are cleaned up and the corresponding logout is completed. The channel is deregistered, and the new SelectionKey is returned. When a channel is closed, all associated keys are automatically cancelled (remember, a channel can be registered with more than one selector). When the selector is closed, all channels registered with the selector are deregistered, and the associated key is immediately invalidated (cancelled). Once the key is invalidated, the select-related method that calls it throws a CancelledKeyException.

Select ()

In the Selector object we just initialized, all three sets are empty. The Selector select () method lets you select ready channels that contain the events you’re interested in. For example, if you are interested in read-ready channels, the select () method will return those channels for which the read event is ready. Here are a few overloaded select() methods for Selector:

  • Int SELECT () : blocks until at least one channel is ready on the event you registered.
  • Int SELECT (long timeout) : same as select(), but the maximum blocking time is timeout milliseconds.
  • Int selectNow() : non-blocking, returns as soon as a channel is ready.

The int returned by the select() method indicates how many channels are ready, and how many channels have become ready since the last call to the select() method. Channels that were previously ready on the select () call are not credited in this call, nor are channels that were ready on the previous SELECT () call but are no longer in the ready state. For example, the first call to select() returns 1 if one of the channels becomes ready, and the next call to select() returns 1 if another channel is ready. If nothing is done to the first ready channel, there are now two ready channels, but only one is ready between each select() method call.

Once the select() method is called and the return value is not zero, the collection of selectedKeys can be accessed by calling Selector’s selectedKeys() method. Set selectedKeys= selectedKeys(); And then you can put that Selector and Channel that’s associated with that particular SelectionKey. As follows:

Set selectedKeys = selector.selectedKeys(); Iterator keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else  if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); }Copy the code

5. Stop choosing methods

The selector performs the selection process, and the underlying system asks each channel in turn if it is ready. This process may cause the calling thread to block, so there are three ways to wake up a blocked thread in the select () method.

  • Wakeup () : This method causes the first unreturned selection on the Selector to be returned immediately by calling the Selector’s wakeup() method. If no selection operation is currently in progress, the next call to the select() method will return immediately.
  • Close () method: The Selector is closed by the close () method, which wakes up any thread that blocks during the selection (similar to wakeup ()), and causes any channels registered with the Selector to be unregistered. All keys are cancelled, but the Channel itself is not closed.

Three template code

A server-side template code:

With template code, most of the time when we write programs, we add business code to template code

ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress("localhost", 8080)); ssc.configureBlocking(false); Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); while(true) { int readyNum = selector.select(); if (readyNum == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectedKeys.iterator(); while(it.hasNext()) { SelectionKey key = it.next(); If (key.iswritable ()) {// Acceptable connection} else if(key.isreadable ()) {else if(key.iswritable ()) {// Acceptable connection} it.remove(); }}Copy the code

Simple interaction instance between client and server

Server:

package selector; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class WebServer { public static void main(String[] args) { try { ServerSocketChannel ssc = ServerSocketChannel.open(); SSC. Socket (). The bind (new InetSocketAddress (8000) "127.0.0.1,"); ssc.configureBlocking(false); Selector selector = Selector.open(); // Register a channel, and specify that the event of interest is Accept ssc.register(selector, selectionkey.op_accept); ByteBuffer readBuff = ByteBuffer.allocate(1024); ByteBuffer writeBuff = ByteBuffer.allocate(128); writeBuff.put("received".getBytes()); writeBuff.flip(); while (true) { int nReady = selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); If (key.isacceptable ()) {// create a new connection and register the connection with the selector, and declare that this channel is only interested in reads. SocketChannel socketChannel = ssc.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); readBuff.clear(); socketChannel.read(readBuff); readBuff.flip(); System.out.println("received : " + new String(readBuff.array())); key.interestOps(SelectionKey.OP_WRITE); } else if (key.isWritable()) { writeBuff.rewind(); SocketChannel socketChannel = (SocketChannel) key.channel(); socketChannel.write(writeBuff); key.interestOps(SelectionKey.OP_READ); } } } } catch (IOException e) { e.printStackTrace(); }}}Copy the code

Client:

package selector; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class WebClient { public static void main(String[] args) throws IOException { try { SocketChannel socketChannel =  SocketChannel.open(); SocketChannel. Connect (new InetSocketAddress (8000) "127.0.0.1,"); ByteBuffer writeBuffer = ByteBuffer.allocate(32); ByteBuffer readBuffer = ByteBuffer.allocate(32); writeBuffer.put("hello".getBytes()); writeBuffer.flip(); while (true) { writeBuffer.rewind(); socketChannel.write(writeBuffer); readBuffer.clear(); socketChannel.read(readBuffer); } } catch (IOException e) { } } }Copy the code

Running results:

Run the server first, then the client, the server will continuously receive messages from the client.

= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =