最后更新时间:2014-06-23
一个Selector是一个Java NIO的组件,他可以检测一个或者多个NIO的通道,并且决定哪个通道准备好某些事件,例如读或者写。这种方式一个单独的线程就可以管理多个通道,以及多个网络连接。
为什么使用Selector
使用只是单线程去管理多个通道的优点就是你需要更少的线程去处理这些通道。实际上,你可以只是使用一个线程去处理所有的通道。线程切换对于操作系统是昂贵的,并且每一个线程在操作系统中也会消耗一些资源(内存)。因此,使用越少的线程越好。
但是请记住,现在的操作系统和CPU在多任务处理中变得越来越好,以至于多线程的负荷随着时间的推移变得很小了。实际上,如果一个CPU是多核,你可能会浪费CPU的能力在不是多任务的情况下。不管怎样,那个设计的主题属于不同的主题。它足够在这里说了,使用Selector,你可以用单线程处理多个channels。
这里有一个在单线程的情况下去处理三个通道的图解:
创建一个Selector
通过调用Selector.open()方法可以创建一个Selector,像下面这样:
Selector selector = Selector.open();
用Selector注册通道
为了跟随Selector使用一个Channel,你必须用Selector注册一个通道。这个可以使用SelectableChannel.register()方法去实现,像下面这样:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
这个Channel必须是在非阻塞模式下使用Selector。这就意味着你不能在FileChannel中使用Selector,因为FileChannel不能切换到非阻塞模式。但是SocketChannel会工作的很好。
注意register()方法的第二个参数。这是一个“兴趣集合”,意味着你对Channel中的什么事件监听感兴趣,通过这个Selector。这里有你可以监听的四种不同的事件类型:
- Connect(连接)
- Accept(接收)
- Read(读)
- Write(写)
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey's
- 这些兴趣集合
- 这些准备的集合
- 通道
- Selector
- 一个附加的对象(可选的)
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;
正如你看到的,你可以通过给予的SelectionKey变量进行逻辑与操作去发现是否一个特定的事件在你的感兴趣的集合中。
int readySet = selectionKey.readyOps();
你可以用这个感兴趣的集合用相同的方式测试,这个通道准备好了哪些事件哪些操作。但是你也可以使用这四中方法代替,他们将会返回一个Boolean值:
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
通道+选择器
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
附加的对象
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
当用Selector注册到Channel中的时候,你也可以使用register方法附加一个先前的对象,这里有一个例子:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
通过一个Selector选择通道
- int select()
- int select(long timeout)
- int selectNow()
Set<SelectionKey> selectedKeys = selector.selectedKeys();
当你用Selector注册一个通道的时候,这个Channel.register()方法返回一个SelectionKey对象。这一项显示了通道注册在那个selector上。你可以通过selectedKeySet()方法访问这些项。来自于SelectionKey。
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();
}
在选中的key上面进行循环迭代。对于每一个key,它去测试这个key去决定被这个key准备好的这个通道引用的什么事件。
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
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();
}
}
翻译地址:http://tutorials.jenkov.com/java-nio/selectors.html