Java中的NIO详解Day06-Selector
Selector
- 基本概念
- Selector的创建
- Selector注册通道
- SelectionKey
- interest集合
- ready集合
- Channel和Selector
- 附加的对象
- Selector选择通道
- SelectedKeys
- wakeup()
- close()
- Selector示例
基本概念
选择器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 channel准备好接收新进入的连接
- 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() 方法:
/ select()阻塞到至少有一个通道在注册的事件上就绪 @return int 表示有多少个通道已经就绪 或者是 从上次调用select()方法后有多少通道变成就绪状态 */
int select();/ select(long timeout)阻塞到至少有一个通道在注册的事件上就绪,最长会阻塞timeout毫秒 @param timeout 最长会阻塞timeout毫秒 @return int 表示有多少个通道已经就绪 或者是 从上次调用select(long timeout)方法后有多少通道变成就绪状态 /
int select(long timeout);/ selectNow()不会阻塞,不管什么通道就绪都立刻返回 @return int 此方法执行非阻塞的选择操作,从前一次选择操作后,没有通道变成可选择的,直接返回0 */
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()) {
// a connection was acceptable by a ServerSocketChannel
}
if (key.isConnectable()) {
// a connection was established with a remote server
}
if (key.isReadable()) {
// a channel is ready for reading
}
if (key.isWritable()) {
// a channel is ready for writing
}
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()) {
// 一个连接被ServerSocketChannel接受
...
}
if (key.isConnectable()) {
// 一个连接被远程服务发布
...
}
if (key.isReadable()) {
// 一个准备去读的channel
...
}
if (key.isWritable()) {
// 一个准备去写的channel
}
keyIterator.remove();
}
}
还没有评论,来说两句吧...