前面已经整理了一下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 | public final SelectionKey register(Selector selector,int options) |
第一个参数是要向哪一个selector注册。第二个参数是channel要注册的操作,当该channel有注册的操作时,selector会选择到这个channel。第三个参数是附件,是可选的,用来将一个任何对象附加到这个注册的键上。
选择channel
有三个方法可以选择就绪的channel,他们的区别在于等待。首先第一个方法是非阻塞的,如果当前没有就绪的channel,他会立刻返回:
1 | public abstract int selectNow() throws IOException |
而下面两个方法是阻塞的,第一个方法在返回前会一直等待,第二个则是设定了一个等待的时间,超过了等待时间 则会返回0:
1 | public abstract int select() 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
1 | public final boolean isAcceptable() |
获得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() |