Java NIO Selector
- Why Use a Selector?
- Creating a Selector
- Registering Channels with the Selector
- SelectionKey's
- Selecting Channels via a Selector
- wakeUp()
- close()
- Full Selector Example
A Selector is a Java NIO component which can examine one or more NIO Channel's, and determine which channels are ready for e.g. reading or writing. This way a single thread can manage multiple channels, and thus multiple network connections.
Selector 是一個NIO的組件,它可以檢查一個或多個channel,并決定哪個channel是否做好了讀、寫準備
Why Use a Selector?
The advantage of using just a single thread to handle multiple channels is that you need less threads to handle the channels. Actually, you can use just one thread to handle all of your channels. Switching between threads is expensive for an operating system, and each thread takes up some resources (memory) in the operating system too. Therefore, the less threads you use, the better.
可以用盡量少的線程,甚至一個線程處理多個channel。
線程之間切換耗費資源嚴重,而且每個線程本身就要分配系統(tǒng)資源,比如-xss指定的內(nèi)存??臻g。
所以使用的線程越少越好,selector的出現(xiàn)就是為了減少線程的使用。
Keep in mind though, that modern operating systems and CPU's become better and better at multitasking, so the overheads of multithreading becomes smaller over time. In fact, if a CPU has multiple cores, you might be wasting CPU power by not multitasking. Anyways, that design discussion belongs in a different text. It suffices to say here, that you can handle multiple channels with a single thread, using a Selector.
但是,需要記住,現(xiàn)代的操作系統(tǒng)和CPU在多任務方面表現(xiàn)的越來越好,所以多線程的開銷隨著時間的推移,變得越來越小了。實際上,如果一個CPU有多個內(nèi)核,不使用多任務可能是在浪費CPU能力。不管怎么說,關于那種設計的討論應該放在另一篇不同的文章中。在這里,只要知道使用Selector能夠處理多個通道就足夠了。
Here is an illustration of a thread using a Selector to handle 3 Channel's:

? Java NIO: A Thread uses a Selector to handle 3 Channel's
Creating a Selector
You create a Selector by calling the Selector.open() method, like this:
Selector selector = Selector.open();
Registering Channels with the Selector
In order to use a Channel with a Selector you must register the Channel with the Selector. This is done using the SelectableChannel.register() method, like this:
channel.configureBlocking(false); // 非阻塞模式
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
The Channel must be in non-blocking mode to be used with a Selector. This means that you cannot use FileChannel's with a Selector since FileChannel's cannot be switched into non-blocking mode. Socket channels will work fine though
.與Selector一起使用時,Channel必須處于非阻塞模式下。這意味著不能將FileChannel與Selector一起使用,因為FileChannel不能切換到非阻塞模式。而SocketChannel都可以。
Notice the second parameter of the register() method. This is an "interest set", meaning what events you are interested in listening for in the Channel, via the Selector. There are four different events you can listen for:
- Connect
- Accept
- Read
- Write
A channel that "fires an event" is also said to be "ready" for that event.
So, a channel that has connected successfully to another server is "connect ready". — client端channel
A server socket channel which accepts an incoming connection is "accept" ready. —— server端 serverSocketChannel
A channel that has data ready to be read is "read" ready.
A channel that is ready for you to write data to it, is "write" ready.
These four events are represented by the four SelectionKey constants:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
If you are interested in more than one event, OR the constants together, like this:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
I'll return to the interest set a bit further down in this text.
SelectionKey's
As you saw in the previous section, when you register a Channel with a Selector the register() method returns a SelectionKey objects. This SelectionKey object contains a few interesting properties:
- The interest set
- The ready set
- The Channel
- The Selector
- An attached object (optional)
I'll describe these properties below.
Interest Set
The interest set is the set of events you are interested in "selecting", as described in the section "Registering Channels with the Selector". You can read and write that interest set via the SelectionKey like this:
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
As you can see, you can AND the interest set with the given SelectionKey constant to find out if a certain event is in the interest set.
interest集合是你所選擇的感興趣的事件集合??梢酝ㄟ^SelectionKey讀寫interest集合
用“位與 & ”操作interest 集合和給定的SelectionKey常量,可以確定某個確定的事件是否在interest 集合中。
Ready Set
The ready set is the set of operations the channel is ready for. You will primarily be accessing the ready set after a selection.
ready 集合是通道已經(jīng)準備就緒的操作的集合。在一次選擇(Selection)之后,你會首先訪問這個ready set
Selection is explained in a later section. You access the ready set like this:
int readySet = selectionKey.readyOps();
You can test in the same way as with the interest set, what events / operations the channel is ready for. But, you can also use these four methods instead, which all reaturn a boolean:
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel + Selector
Accessing the channel + selector from the SelectionKey is trivial. Here is how it's done:
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
從SelectionKey中獲取channel和selector有直接的方法可以調(diào)用
Attaching Objects
You can attach an object to a SelectionKey this is a handy way of recognizing a given channel, or attaching further information to the channel. For instance, you may attach the Buffer you are using with the channel, or an object containing more aggregate data.
可以將一個對象或者更多信息附著到SelectionKey上,這樣就能方便的識別某個給定的通道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集數(shù)據(jù)的某個對象。
Here is how you attach objects:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
You can also attach an object already while registering the Channel with the Selector, in the register()method. Here is how that looks:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Selecting Channels via a Selector
Once you have register one or more channels with a Selector you can call one of the select() methods. These methods return the channels that are "ready" for the events you are interested in (connect, accept, read or write). In other words, if you are interested in channels that are ready for reading, you will receive the channels that are ready for reading from the select() methods.
一旦向Selector注冊了一或多個通道,就可以調(diào)用幾個重載的select()方法。這些方法返回對所感興趣的事件(如連接、接受、讀或?qū)懀┮呀?jīng)準備就緒的那些通道。換句話說,如果你對“讀就緒”的通道感興趣,select()方法會返回讀事件已經(jīng)就緒的那些通道。
Here are the select() methods:
- int select()
- int select(long timeout)
- int selectNow()
select() blocks until at least one channel is ready for the events you registered for. 阻塞到至少有一個通道在你注冊的事件上就緒了—注意這里會阻塞
select(long timeout) does the same as select() except it blocks for a maximum of timeout milliseconds (the parameter). 指定毫秒級超時時間
selectNow() doesn't block at all. It returns immediately with whatever channels are ready.
The int returned by the select() methods tells how many channels are ready. — 返回已ready的channel數(shù)量
That is, how many channels that became ready since last time you called select(). If you call select() and it returns 1 because one channel has become ready, and you call select() one more time, and one more channel has become ready, it will return 1 again. If you have done nothing with the first channel that was ready, you now have 2 ready channels, but only one channel had become ready between each select() call.
注意這里的ready,是從上次調(diào)用select之后開始算起。
即自上次調(diào)用select()方法后有多少通道變成就緒狀態(tài)。如果調(diào)用select()方法,因為有一個通道變成就緒狀態(tài),返回了1,若再次調(diào)用select()方法,如果另一個通道就緒了,它會再次返回1。如果對第一個就緒的channel沒有做任何操作,現(xiàn)在就有兩個就緒的通道,但在第二次select()方法調(diào)用時,也只會返回一個通道。
selectedKeys()
Once you have called one of the select() methods and its return value has indicated that one or more channels are ready, you can access the ready channels via the "selected key set", by calling the selectors selectedKeys() method.
如果調(diào)用select返回的可用channel數(shù)目大于等于1,就可以使用selector.selectedKey()方法獲取selectionKey的set集合
Here is how that looks:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
When you register a channel with a Selector the Channel.register() method returns a SelectionKeyobject. This key represents that channels registration with that selector. It is these keys you can access via the selectedKeySet() method. From the SelectionKey.
You can iterate this selected key set to access the ready channels. Here is how that looks:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> 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();
}
This loop iterates the keys in the selected key set. For each key it tests the key to determine what the channel referenced by the key is ready for.
Notice the keyIterator.remove() call at the end of each iteration. The Selector does not remove the SelectionKey instances from the selected key set itself. You have to do this, when you are done processing the channel. The next time the channel becomes "ready" the Selector will add it to the selected key set again.
要手動keyIterator.remove()方法將SelectionKey實例從set中清除,因為Selector不會自動清除。
The channel returned by the SelectionKey.channel() method should be cast to the channel you need to work with, e.g a ServerSocketChannel or SocketChannel etc.
要做強制轉(zhuǎn)換,轉(zhuǎn)換成注冊時的channel類型
wakeUp()
A thread that has called the select() method which is blocked, can be made to leave the select()method, even if no channels are yet ready. This is done by having a different thread call the Selector.wakeup() method on the Selector which the first thread has called select() on. The thread waiting inside select() will then return immediately.
如果一個線程調(diào)用select方法后變成blocked狀態(tài),即使沒有通道已經(jīng)就緒,也有辦法讓其從select()方法返回
只要讓第二個線程在第一個線程調(diào)用select()方法的那個對象上調(diào)用Selector.wakeup()方法即可。
阻塞在select()方法上的線程會立馬返回。
If a different thread calls wakeup() and no thread is currently blocked inside select(), the next thread that calls select() will "wake up" immediately.
如果有其它線程調(diào)用了wakeup()方法,但當前沒有線程阻塞在select()方法上,下個調(diào)用select()方法的線程會立即“醒來(wake up)”。
close()
When you are finished with the Selector you call its close() method. This closes the Selector and invalidates all SelectionKey instances registered with this Selector. The channels themselves are not closed.
用完Selector后調(diào)用其close()方法會關閉該Selector,且使注冊到該Selector上的所有SelectionKey實例無效。通道本身并不會關閉。
Full Selector Example
Here is a full example which opens a Selector, registers a channel with it (the channel instantiation is left out), and keeps monitoring the Selector for "readiness" of the four events (accept, connect, read, write).
public static void main(String[] args) throws IOException {
Selector selector = Selector.open(); // 構造selector
SocketChannel socketChannel = SocketChannel.open(); // 構造 socketChannel
socketChannel.validOps();
DatagramChannel udpChannel = DatagramChannel.open(); // 構造 udpChannel
socketChannel.configureBlocking(false); // 設置非阻塞
udpChannel.configureBlocking(false); // 設置非阻塞
// 試試fileChannel,根本沒有configureBlocking方法,丟。。。
// RandomAccessFile randomAccessFile = new RandomAccessFile("nio-data.txt","rw");
// FileChannel fileChannel = randomAccessFile.getChannel();
// fileChannel.con....沒有這個方法
socketChannel.register(selector, SelectionKey.OP_WRITE); // 不是ServerSocketChannel,不能用OP_ACCEPT狀態(tài),否則會報參數(shù)非法
udpChannel.register(selector, SelectionKey.OP_READ);
while (true) {
int idleChannelSize = selector.select(2000); // 這里會一直阻塞,所以要加超時時間
System.out.println("now ready channel size:" + idleChannelSize);
if (0 == idleChannelSize) continue;
Set<SelectionKey> idleChannelSet = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = idleChannelSet.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
System.out.println("Key is acceptable," + key.channel().toString());
} else if (key.isConnectable()) {
System.out.println("Key is connectable," + key.channel().toString());
} else if (key.isReadable()) {
System.out.println("Key is readable," + key.channel().toString());
} else if (key.isWritable()) {
System.out.println("Key is writeable," + key.channel().toString());
}
keyIterator.remove(); // 在里面這層循環(huán)里處理,否則remove誰呢?
}
}
}
這里的兩個channel其實都不是ready狀態(tài),所以select()方法會一直阻塞。這里后面加入了超時時間,就會打印
now ready channel size:0
now ready channel size:0
now ready channel size:0