有了通道和缓冲区,我们就能进行数据的交互了。JDK的NIO是操作系统底层的非阻塞IO模型在java中的实现。所以还差一个选择器,这个选择器用来选择读写时不阻塞的Socket。为了完成就绪选择,要将不同的通道注册到一个Selector对象,每个通道分配一个SelectionKey。然后程序就可以询问这个Selector对象,哪些通道已经准备就绪就可以无阻塞地完成希望完成的操作。
Selector类
创建选择器
Selector提供了静态方法open来创建新的选择器:
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
向选择器增加通道
创建了选择器之后,我们就能向选择器增加通道。注意选择器没有增加通道的方法,我们只能调用通道的register方法来加入选择器。register()方法在SelectableChannel类中声明,所以它的子类例如前面讲到的ServerSocketChannel都能加入选择器,可以理解为这些channel是可以选择的,有些channel例如FileChannel就不可选择。因此,通过将选择器传递给通道的一个注册方法,就可以向选择器注册这个通道,我们来看SelectableChannel的register方法:
public final SelectionKey register(Selector sel, int ops)
throws ClosedChannelException
{
return register(sel, ops, null);
}
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
第一个参数是通道要向哪个选择器注册。第二个参数是SelectionKey类中的一个命名常量,标注通道所注册的操作。SelectionKey定义了4个命名位常量,用于选择操作类型:
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
这些都是位标志整型常量(1、2、4等)。因此,如果一个通道需要在同一个选择器中关注多个操作(例如读和写一个socket),只要在注册时利用位“或”操作符 | 组合这些常量就行了。
选择就绪的通道
不同的通道注册到选择器后,就可以随时查询选择器,找出哪些通道已经准备好可以进行处理。通道可能已经准备好完成某些操作,但对于另一些操作还没有准备好,例如可能一个通道已经准备就绪可以读取但是不能写入。有三个方法可以选择就绪的通道,它们都返回就绪的通道的数量,他们的区别在于寻找就绪通道等待的时间。
public abstract int selectNow() throws IOException;
selectNow方法是非阻塞的,如果自从上次selection操作以来没有新的通道就绪它就会立即返回0。
public abstract int select(long timeout) throws IOException;
这个select方法会阻塞最多timeout长的时间来等待有至少一个通道就绪。
public abstract int select() throws IOException;
这个select方法也是阻塞的,它会等到至少一个通道就绪。
public abstract Set<SelectionKey> selectedKeys();
获取就绪的通道
public abstract Set<SelectionKey> selectedKeys();
关闭选择器
public abstract void close() throws IOException;
SelectionKey类
SelectionKey的含义
SelectionKey对象相当于通道的指针,一般会存储这个通道上连接的状态。当一个通道注册到一个选择器时,register方法会返回SelectionKey对象,不过通常我们不需要保留这个引用。selectedKeys方法可以在set中再次返回相同的对象。一个通道可以注册到多个选择器。
测试SelectionKey能进行的操作
SelectionKey提供了以下几个方法来测试对应的通道能进行的操作:
public final boolean isReadable() {
return (readyOps() & OP_READ) != 0;
}
public final boolean isWritable() {
return (readyOps() & OP_WRITE) != 0;
}
public final boolean isConnectable() {
return (readyOps() & OP_CONNECT) != 0;
}
public final boolean isAcceptable() {
return (readyOps() & OP_ACCEPT) != 0;
}
通过SelectionKey获取与之对应的通道
SelectionKey的channel方法能返回该SelectionKey对应的通道。
/**
* Returns the channel for which this key was created. This method will
* continue to return the channel even after the key is cancelled.
*
* @return This key's channel
*/
public abstract SelectableChannel channel();