Selector是Java NIO中检测一个或多个Channel的,同时确定哪些channel是否已经可以进行读或者写的组件,这样一个线程就可以管理多个channel,从而管理多个网络连接,
Selector的优点:可以使用更少线程来管理channel(线程会占用内存资源,线程的切换对系统的开销也很大,一般线程越少越好,但随着现在机器性能提高,多核计算使用单线程反而浪费资源,),理论上可以由一个线程管理所有channel.
使用Selector.open()创建一个Selector
Channel和Selector要一起使用就必须将Channel注册到一个Selector上,和selector一起使用,channel必须时未阻塞的,那么由于FileChannel不能切换为非阻塞模式,FileChannel不能和selector一起使用,而Socket channel可以;注意register的第二个参数,这是一个interest set,channel通过它监听channel感兴趣的事件,有以下4件事情可以监听:1.Connect,2.accept 3.read 4.write
//调整channel的阻塞状态,false为非阻塞
channel.configureBlocking(false);
//向给定的选择器注册此通道,返回一个选择键
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
一个channel注册一个事件也可以说是该事件已经准备好了,所以一个channel连接到一个服务器就是connect ready连接就绪,一个server socket channel接受新进入的连接就是accept ready接受就绪.一个channel有数据准备读取,就是read ready 读取就绪,一个channel可以写数据就是write reay写入就绪;
这四个时间由四个selectionKey常量表示:
1. SelectionKey.OP_CONNECT, 2. SelectionKey.OP_ACCEPT,3. SelectionKey.OP_READ,4. SelectionKey.OP_WRITE
如果不知对一种时间感兴趣,可以使用"位或"操作符将常量连接起来
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
register注册方法会返回一个SelectionKey,表示SelectableChannel在Selector中注册的标记,在调用键的cancel方法,关闭其channel或者关闭SelectionKey来取消这个键之前,会一直有效.取消某个键不会立即在其选择器汇总移除它,会将该键添加到选择气的已取消键集,,以便在下一次进行选择操作时移除它,可通过调用isValid方法来测试其有效性,这个键对象包含一些属性:
1. interest set 2. ready set 3. Channel 4. Selector 5.An attached object 附加对象(可选)
Interest Set
是Channel向Selector注册时选择额感兴趣的时间的合集,可以通过SelectionKey来读写interest:
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;
和上面的官网文档demo一样可以通过逻辑与来判断某个事件是否在Interest Set中存在
Ready Set
ready set 是channel已经准备好的操作的集合,在一次selection后你要首先访问这个ready set,Selection使用方法:
int readySet = selectionKey.readyOps();
可以使用和上面interest Set一样的方法检测什么样的事件已经准备好了,但你也能用以下的方法来检测
selectionKey.isAcceptable() ; selectionKey.isConnectable() ; selectionKey.isReadable() ; selectionKey.isWritable() ;
Channel + Selector
使用SelectionKey来访问channel+selector是简单地:
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
Attaching Objects
你可以附加一个对象或更多信息给一个SelectionKey,这是一个方便识别给定channel的方法,比如,你可以在使用channel时附加buffer或一个包含更多数据集合的对象,或者可以在channel注册到selector方法时也可以附加一个对象
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment;
SelectionKey key = channel.register(selector,SelectionKey.OP_READ,theObject);
Selecting Channels via a Selector 通过Selector选择channels
一旦一个或多个channels注册到selector,就可以调用select()方法,这些方法可返回对你所感兴趣的事件已经准备好的哪些通道,换句话说,如果你对某个事件感兴趣,这个方法可以返回这个事件已经就绪的channels;
select()方法:
int select(): 阻塞直到至少一个channel在你注册的事件上就绪了;
int select(long timeout) ;在select()基础上有个最长阻塞时间;
int selectNow(): 不会阻塞,无论什么通道只要就绪立即返回(网络摘录:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)
这三个方法返回的int表示准备就绪的channel数量,注意:这个返回值是上次select()方法后有多少channel准备就绪,即使上一次select()出的channel并没有进行任何操作
selectedKeys()
一旦调用一次select()方法,同时返回值表示至少一个channel就绪,你可以通过调用selector的selectedKeys()方法访问selected key set(已选择键集)中的这个就绪的channel
Set<SelectionKey> selectedKeys = selector.selectedKeys
当使用channel.register()方法将channel注册个一个selector返回的selectionKey对象,它表示了那个channel注册the selector上,你可以通过SelectionKey的selectedKeySet()方法访问者这些键,可以遍历这个selected key set来访问这些就绪的channel
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();
}
这个循环对selected key set进行遍历,并对每一个key检测它对应的channel的就绪事件.
keyIterator.remove()这个方法在每次迭代结尾调用,这个Selector不会自己移除这个SelectionKey实例,必须要在完成通道加工后自己删除,下一次channel就绪时这个selector会再次将他放入selected key set;
SelectionKey.channel()方法返回的channel 需要转换成需要处理的类型;
wakeUp()
一个线程调用select()方法后阻塞了 ,即使没有channel就绪,也可以从select()方法离开,这是通过其他线程在第一个线程调用select()方法的selector对象上调用Selector.wakeup()方法实现的.阻塞在select()方法上的线程会立即返回.
如果一个其他的线程调用wakeup()方法但当前没有线程在select()方法上阻塞,下一个调用select()方法的线程会立刻被wakeup唤醒
close()
使用完selector后调用close()方法关闭它,关闭selector会使注册到这个selector的所有selectionkey实例失效,channel本身不会关闭