有了通道和缓冲区,我们就能进行数据的交互了。JDK的NIO是操作系统底层的非阻塞IO模型在java中的实现。所以还差一个选择器,这个选择器用来选择读写时不阻塞的Socket。为了完成就绪选择,要将不同的通道注册到一个Selector对象,每个通道分配一个SelectionKey。然后程序就可以询问这个Selector对象,哪些通道已经准备就绪就可以无阻塞地完成希望完成的操作。

Selector类

创建选择器

Selector提供了静态方法open来创建新的选择器:

  1. public static Selector open() throws IOException {
  2. return SelectorProvider.provider().openSelector();
  3. }

向选择器增加通道

创建了选择器之后,我们就能向选择器增加通道。注意选择器没有增加通道的方法,我们只能调用通道的register方法来加入选择器。register()方法在SelectableChannel类中声明,所以它的子类例如前面讲到的ServerSocketChannel都能加入选择器,可以理解为这些channel是可以选择的,有些channel例如FileChannel就不可选择。因此,通过将选择器传递给通道的一个注册方法,就可以向选择器注册这个通道,我们来看SelectableChannel的register方法:

  1. public final SelectionKey register(Selector sel, int ops)
  2. throws ClosedChannelException
  3. {
  4. return register(sel, ops, null);
  5. }
  6. public abstract SelectionKey register(Selector sel, int ops, Object att)
  7. throws ClosedChannelException;

第一个参数是通道要向哪个选择器注册。第二个参数是SelectionKey类中的一个命名常量,标注通道所注册的操作。SelectionKey定义了4个命名位常量,用于选择操作类型:

  1. public static final int OP_READ = 1 << 0;
  2. public static final int OP_WRITE = 1 << 2;
  3. public static final int OP_CONNECT = 1 << 3;
  4. public static final int OP_ACCEPT = 1 << 4;

这些都是位标志整型常量(1、2、4等)。因此,如果一个通道需要在同一个选择器中关注多个操作(例如读和写一个socket),只要在注册时利用位“或”操作符 | 组合这些常量就行了。

选择就绪的通道

不同的通道注册到选择器后,就可以随时查询选择器,找出哪些通道已经准备好可以进行处理。通道可能已经准备好完成某些操作,但对于另一些操作还没有准备好,例如可能一个通道已经准备就绪可以读取但是不能写入。有三个方法可以选择就绪的通道,它们都返回就绪的通道的数量,他们的区别在于寻找就绪通道等待的时间。

  1. public abstract int selectNow() throws IOException;

selectNow方法是非阻塞的,如果自从上次selection操作以来没有新的通道就绪它就会立即返回0。

  1. public abstract int select(long timeout) throws IOException;

这个select方法会阻塞最多timeout长的时间来等待有至少一个通道就绪。

  1. public abstract int select() throws IOException;

这个select方法也是阻塞的,它会等到至少一个通道就绪。

  1. public abstract Set<SelectionKey> selectedKeys();

获取就绪的通道

  1. public abstract Set<SelectionKey> selectedKeys();

这个方法返回就绪的通道的集合。

关闭选择器

  1. public abstract void close() throws IOException;

SelectionKey类

SelectionKey的含义

SelectionKey对象相当于通道的指针,一般会存储这个通道上连接的状态。当一个通道注册到一个选择器时,register方法会返回SelectionKey对象,不过通常我们不需要保留这个引用。selectedKeys方法可以在set中再次返回相同的对象。一个通道可以注册到多个选择器。

测试SelectionKey能进行的操作

SelectionKey提供了以下几个方法来测试对应的通道能进行的操作:

  1. public final boolean isReadable() {
  2. return (readyOps() & OP_READ) != 0;
  3. }
  4. public final boolean isWritable() {
  5. return (readyOps() & OP_WRITE) != 0;
  6. }
  7. public final boolean isConnectable() {
  8. return (readyOps() & OP_CONNECT) != 0;
  9. }
  10. public final boolean isAcceptable() {
  11. return (readyOps() & OP_ACCEPT) != 0;
  12. }

通过SelectionKey获取与之对应的通道

SelectionKey的channel方法能返回该SelectionKey对应的通道。

  1. /**
  2. * Returns the channel for which this key was created. This method will
  3. * continue to return the channel even after the key is cancelled.
  4. *
  5. * @return This key's channel
  6. */
  7. public abstract SelectableChannel channel();