基本概念
选择器Selector:
Java NIO中能够检测一个或者多个NIO通道,并且能够获取到通道是否为诸如读写事件做好准备的组件
一个单独的线程可以管理多个Channel,从而管理多个网络连接
使用单个线程来处理多个Channel的优点:
只需要更少的线程来处理通道
事实上,可以只用一个线程处理所有的通道
对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程之间都要占用一些内存资源
因此,使用的线程越少越好
Selector的创建
通过调用Selector.open() 方法创建一个Selector:
Selector selector = Selector. open ( ) ;
Selector注册通道
为了将Channel 和Selector 配合使用,必须将channel 注册到selector 上
通过SelectorChannel.register() 方法来实现向Selector 中注册通道:
channel. configureBlocking ( false ) ;
SelectionKey key = channel. register ( selector, SelectionKey. OP_READ) ;
Channel 必须处于非阻塞模式下,才能与Selector 一起使用
因为FileChannel 不能切换到非阻塞模式,所以不能将FileChannel 与Selector 一起使用.而套接字通道都可以
register的第二个参数:
是一个interest 集合
在通过Selector 监听Channel 时对什么事件感兴趣
Selector可以监听四种不同的类型: 通道触发了一个事件意味着该事件已经就绪
Connect: 连接就绪. 某个channel 成功连接到另一个服务器
Accept: 接收就绪. 一个server socket channe l准备好接收新进入的连接
Read: 读就绪. 一个有数据可读的通道
Write: 写就绪. 等待写数据的通道
这四种事件使用SelectionKey 的四个常量来表示:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
如果Selector 不止对一种事件感兴趣,可以使用位或 操作符将常量连接起来:
int interestSet = SelectionKey. OP_READ | SelectionKey. OP_WRITE
SelectionKey
当向Selector 注册Channel 时 ,register() 方法会返回一个SelectionKey 对象,这个对象中包含了Selector 感 兴趣的属性:
interest集合
ready集合
Channel
Selector
附加的对象
interest集合
interest集合: Selector感兴趣的事件集合. 可以通过SelectionKey读写interest集合
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;
使用 “位于&” 操作interest 集合与给定的SelectionKey 常量,可以确定某个确定的事件是否在interest 集合中
ready集合
ready 集合是通道已经准备就绪操作的集合.在一次选择Selection 之后,会首先访问这个ready 集合
访问ready 集合:
int readySet = selectionKey. readyOps ( ) ;
可以使用 “位或&” 操作ready 集合与给定的SelectionKey 常量,可以确定Channel 中什么事件或者操作已经就绪
也可以使用SelectionKey 中的方法来检测Channel 中什么事件或者操作已经就绪:
selectionKey. isAcceptable ( ) ;
selectionKey. isConnectable ( ) ;
selectionKey. isReadable ( ) ;
selectionKey. isWritable ( ) ;
Channel和Selector
根据SelectionKey 访问Channel 和Selector:
Channel channel = selectionKey. channel ( ) ;
Selector selector = selectionKey. selector ( ) ;
附加的对象
为了更加方便地识别某个给定的通道,可以将一个对象或者更多信息附加到SelectionKey 上
可以附加一个与通道一起使用的Buffer 或者是包含聚集数据的某个对象:
selectionKey. attach ( theObject) ;
Object theObject = selectionKey. attachment ( ) ;
可以在用register() 方法向Selector 注册Channel 时附加对象:
SelectionKey key = channel. register ( selector, SelectionKey. OP_READ, theObject) ;
Selector选择通道
如果向Selector 注册了一个或多个通道,就可以调用几个重载的select() 方法
select() 方法返回所感兴趣事件已经准备就绪的通道:
select() 方法:
int select ( ) ;
int select ( long timeout) ;
int selectNow ( ) ;
SelectedKeys
如果调用了select() 方法,并且返回值表明有一个或更多个通道就绪了.然后可以通过调用selector 的selectedKeys() 方法,访问 “已选择键集selected key set” 中的就绪通道
Set selectedKeys = selector. selectedKeys ( ) ;
当向Selector 注册Channel 时 ,Channel.register() 方法会返回一个SelectionKey 对象:
SelectionKey 对象代表了注册到该Selector 的通道
可以通过SelectionKey 的selectedKeySet() 方法访问这些对象
可以通过遍历这个已选择的键集合来访问就绪的通道:
Set selectedKeys = selector. selectedKeys ( ) ;
Iterator keyIterator = selectedKeys. iterator ( ) ;
while ( keyIterator. hasNext ( ) ) {
SelectionKey key = keyIterator. next ( ) ;
if ( key. isAcceptable ( ) ) {
}
if ( key. isConnectable ( ) ) {
}
if ( key. isReadable ( ) ) {
}
if ( key. isWritable ( ) ) {
}
keyIterator. remove ( ) ;
}
循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件
每次迭代末尾调用keyIterator.remove():
Selector 本身不会从已选择键集中移除SelectionKey 实例
必须在处理完通道时自己移除
下次该通道变成就绪时 ,Selector 会再次将SelectionKey 实例放入已选择键集中
SelectionKey.channel() 方法返回的通道需要转型成为需要处理的类型: 比如ServerSocketChannel 或者SocketChannel 等
wakeup()
某个线程调用select() 方法后阻塞,即使没有通道已经就绪,也可以从select() 方法返回:
只要让其余线程在第一个线程调用select() 方法的对象上调用Selector.wakeup() 方法
阻塞在select() 方法上的线程会立即返回
如果有其余线程调用了wakeup() 方法,但是当前没有线程阻塞在select() 方法上,下一个调用select() 方法的线程会立即返回
close()
使用完毕Selector 之后调用close() 方法会关闭该Selector. 并且注册到该Selector 上的所有SelectionKey 实例无效
Channel 本身并不会关闭
Selector示例
打开一个Selector
注册一个初始化完成的通道到这个Selector 上
持续监控这个Selector 的四种事件:接受,连接,读,写.是否就绪
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 selectedKeys = selector. selectedKeys ( ) ;
Iterator keyIterator = selectedKeys. iterator ( ) ;
while ( keyIterator. hasNext ( ) ) {
SelectionKey key = keyIterator. next ( ) ;
if ( key. isAcceptable ( ) ) {
. . .
}
if ( key. isConnectable ( ) ) {
. . .
}
if ( key. isReadable ( ) ) {
. . .
}
if ( key. isWritable ( ) ) {
}
keyIterator. remove ( ) ;
}
}