Selector是选择器是NIO技术中的核心组件,可以将通道注册进选择器中,其主要作用就是使用一个线程来对多个通道中的已就绪通道进行选择,然后就可以对选择的通道进行数据处理,属于一对多的关系。这种机制在NIO技术中心称为“IO多路复用”。其优势是可以节省CPU资源
说的简单点就是一个线程通过选择器可以连接多个通道,从而完成高效的I/O
在Selector中有三个核心类:
Selector:主操作类,通过静态实例化,select()方法来管理已经注册的通道
SelectionKey:注册完通道之后返回的键,通过该类来描述通道的状态
SelectableChannel:通道,通过该类获取Socket对象,将之注册到Selector中
其关系是只用SelectableChannel通道对象才能被Selector.java选择器所复用因为只有SelectableChannel通道对象才有register(Selector sel,int ops)方法
SelectableChannel:
public abstract class SelectableChannel
extends AbstractInterruptibleChannel
implements Channel
其最常用的方法如下:
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
把SelectableChannel对象注册到Selector上,第二个参数表明对该Channel的那个行为有兴趣可监听四中类型的事件:
- Connect:某个Channel成功连接到另一个服务器称为“ 连接就绪”,对应常量:SelectionKey.OP_CONNECT
- Accept:一个Server Socket Channel准备好接收新进入的连接称为“ 接收就绪 ”,对应常量:SelectionKey.OP_ACCEPT
- Read:一个有数据可读的通道可以说是“ 读就绪 ”,对应常量:SelectionKey.OP_READ
- Write:等待写数据的通道可以说是“ 写就绪 ”,对应常量:SelectionKey.OP_WRITE
如果对多个事件有兴趣的话,用 | 来连接。
需要注意的是如果要把通道注册进选择器中,就必须让该通道非阻塞
channel.configureBlocking(false);
1.不能直接注销通道,需要通过SelectionKey.cancel()来进行注销
2.一个通道只能在任意选择器上注册一次
3.SelectableChannel在对象城下是安全的
4.新创建的SelectableChannel总是阻塞的,向选择器注册通道必须显式的让其变成非阻塞
5.应该是通过它来获取Socket对象!
ServerSocketChannel open = ServerSocketChannel.open();
ServerSocket socket = open.socket();
socket.bind(new InetSocketAddress("localhost",8888));
SelectionKey
SelectionKey表示SelectableChannel在选择器中注册的标记。
注册一个通道,产生一个Key,调用通过某个Key的cancle()方法来关闭通道,关闭之后不会立即从selector中移除,而是添加到cancelledKeys中,在下一次select操作时移除,所以在调用某个key是,需要用isValid进行校验
包含两个操作集:
- interest 集合:当前channel感兴趣的操作,此类操作将会在下一次选择器select操作时被交付,可以通过selectionKey.interestOps(int)进行修改.
- ready 集合:表示此选择键上,已经就绪的操作.每次select时,选择器都会对ready集合进行更新;外部程序无法修改此集合
Selector
selector主要是作为SelectableChannel对象的多路复用器
创建:
Selector selector = Selector.open();
select方法
public abstract int select(long timeout)
具有阻塞性,其作用是选择一组键,其相应的通道以为I/O操作准备就绪。返回值是添加到就绪操作集的键的数目
selectedKeys方法
public abstract Set<SelectionKey> selectedKeys();
返回一个已注册的selectedKey集合,
Set selectedKeys = selector.selectedKeys();
Iterator 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();
}
选择器执行选择的过程,系统底层会依次询问每个通道是否已经就绪,这个过程可能会造成调用线程进入阻塞状态,那么我们有以下两种方式可以唤醒在select()方法中阻塞的线程。
wakeup方法:
通过调用Selector对象的wakeup()方法让处在阻塞状态的select()方法立刻返回
该方法使得选择器上的第一个还没有返回的选择操作立即返回。如果当前没有进行中的选择操作,那么下一次对select()方法的一次调用将立即返回。
close方法:
通过close方法关闭Selector, 该方法使得任何一个在选择操作中阻塞的线程都被唤醒,同时使得注册到该Selector的所有Channel被注销,所有的键将被取消,但是Channel本身并不会关闭。