NIO系列Selecor相关

  前面已经整理了一下buffer和channel,现在要说的selector在我看来和前面的2者在一起可以称得上是NIO的三巨头了。channel为通道,buffer为通道内传递的东西,selector则是管理通道的选择器。
  或许现在才说有点晚,但是还是要区别一下NIO和IO是什么关系。首先IO是面向流的,使用流来进行数据传递(字节),有输入流和输出流之分,他是阻塞型IO。而NIO(non-blocking IO) 是面向缓冲的,使用通道来传递数据(bytebuffer),没有输入输出通道的区分,一个通道同时支持读写,nio可以是非阻塞的(通过设置channel)。
  正是因为一个channel既可以读,也可以写,那么在什么时候读取,什么时候写入呢?selector就像是一个指挥,来调度每个channel该在什么时候干什么事情。

Selector

构建

  selector唯一的构造函数是protect型方法,所以一般使用selector需要使用静态工厂的open方法来获得实例:

1
public static Selector open() throws IOException

添加管理channel

  Selector类没有添加通道的方法,添加方法register是在SelectableChannel类中申明的,这也说明了并不是所有的channel都可以添加到selector中(比如常用的FileChannel),不过所有的网络通道都是可以添加的。

1
2
3
4
5
6
7
8
9
10
11
public final SelectionKey register(Selector selector,int options)
throws ClosedChannelException

public final SelectionKey register(Selector selector,int options,Object attachment)
throws ClosedChannelException

int options:
SelectionKey.OP_ACCEPT
SelectionKey.OP_CONNECT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

  第一个参数是要向哪一个selector注册。第二个参数是channel要注册的操作,当该channel有注册的操作时,selector会选择到这个channel。第三个参数是附件,是可选的,用来将一个任何对象附加到这个注册的键上。

选择channel

  有三个方法可以选择就绪的channel,他们的区别在于等待。首先第一个方法是非阻塞的,如果当前没有就绪的channel,他会立刻返回:

1
public abstract int selectNow() throws IOException

  而下面两个方法是阻塞的,第一个方法在返回前会一直等待,第二个则是设定了一个等待的时间,超过了等待时间 则会返回0:

1
2
public abstract int select() throws IOException
public abstract int select(long timeout) throws IOException

  当有channel就绪的时候,可以使用selectedkeys来获得这些就绪的通道的key:

1
public abstract Set<SelectionKey> selectedKeys()

  需要注意一点,在用迭代器处理这个集合时,拿到这个key后,需要将这个key从迭代器中移除,即需要调用iterator.remove()方法。这样会告诉迭代器这个key已经得到处理,否则会多次处理一个key,将产生一些意想不到的问题。。

关闭selector

  在关闭服务器,或者不再需要selector的时候应该把他关闭。这个操作不仅会释放与之相关的资源,更重要的是,取消了向selector注册的所有key,并中断被这个selector的某个select方法所阻塞的线程

SelectionKey

  这个对象类似于一个指针,同时还可以保存一个附件(上面selector第二中注册方法中提到)。将一个channel注册到selector时,会返回selectionkey对象。不过一般不需要保留这个引用,selectedKeys()方法可以一样返回这selectionkey对象。

判断

  当从Set获得一个具体的SelectionKey时,通常要判断这些建能进行哪些操作:

1
2
3
4
public final boolean isAcceptable()
public final boolean isConnectable()
public final boolean isReadable()
public final boolean isWritable()

获得channel

  一旦了解了这个key对应的channel可以进行怎样的操作,就可以用key.channel来获得这个通道:

1
public abstract SelectableChannel channel()

  同时,如果这个key上有保存过附件,可以通过key.attachment来获得附件:

1
public final Object attachment()

撤销注册

  最后,如果结束使用的连接,就要撤销这SelectionKey对象的注册,这样Selector就不会浪费资源再去查这个key是否准备就绪。虽然我不知道这个操作是不是真的在所有情况下都必要,不过至少没啥坏处,就当养成一个好的习惯。。不过只有在未关闭channel的时候这个操作才有必要,如果关闭的通道,那么会自动的在所有选择器中取消这个channel的注册。当然,直接关闭selector也有一样的效果,会让这个selector中所有的key都失效,已经在上面提到过了。

1
public abstract void cancel()