关于JAVA NIO的就绪选择

对于网络编程来说,NIO API的很重要的一部分是就绪选择,即能够选择读写时不阻塞的Socket。这主要针对网络服务器,但对于打开多个窗口并运行多个并发连接的客户端(例如,WebSpider程序或浏览器)来说,也可以利用这个特性。

为了完成就绪选择,要将不同的通道注册到一个 Selector对象。每个通道分配有一个 SelectionKey。然后程序可以询问这个Selector对象,哪些通道已经准备就绪可以无阻塞的完成你希望完成的操作,可以请求Selector对象返回相应的键集合。

Selector类

Selector唯一的构造函数是保护类型方法。一般情况下,要调用静态工厂方法Selector.open()来创建新的通道选择器。

public static Selector open() throws IOException

下一步向选择器增加通道。Selector类没有增加通道的方法。register() 方法在 SelectableChannel 类中声明。并不是所有的通道都是可选择的(特别是FileChannel就不是可选择),不过所有网络通道都是可选择的。因此,通过将选择器传递给通道的一个注册方法,就可以向选择器注册这个通道,

public final SelctionKey register(Selector sel,int pos)
        throws ClosedChannelException

public final SelctionKey register(Selector sel,int pos,Object att)
        throws ClosedChannelException

一般来说,register()方法第一个参数是通道要向哪个选择器注册,第二个参数是SelectionKey类中的一个命名常量,标识通道所注册的操作。SelectionKey定义了4个命名位常量,用于选择操作的类型,

  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

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

channel.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE)

第三个参数是可选的,这是键的附件(attachment)。这个对象通常用于存储连接的状态。例如,如果要实现一个Web服务器,可能要附加一个FileInputStream或FileChannel,这个流或通道连接到服务器提供给客户端的本地文件。

不同的通道注册到选择器后就可以随时查询选择器,找出哪些通道已经准备好可以进行处理。通道可能已经准备好完成某些操作,但对于另一些操作还没有准备好。例如,可能一个通道已经准备就绪可以读取,但还不能写入。

有三个方法可以选择就绪的通道:

public abstrct int`selectNow() throws IOException

public abstrct int`select() throws IOException

public abstrct int`select(long timeout) throws IOException

它们的区别在于寻找就绪通道的等待时间。第一个selectNow()方法会完成“非阻塞”的选择。如果当前没有准备好要处理的通道,它会立即返回。

后两个方法是 阻塞 的。select(),在返回前会等待,直到至少一个注册的通道准备好可以进行处理;selectNow(),在返回0前只能等待不超过timeout毫秒。如果没有就绪通道,责程序就不做任何操作,这些方法会很有用。

当知道有通道已经准备好处理时,可以使用 selectedKeys() 方法获取就绪的通道,

public abstract Set<selectedKey> selectedKeys()

迭代处理返回的集合时,要依次处理各个SelectionKey。还可以从迭代器中删除键,告诉选择器Selector这个键已经处理过了。否则选择器在以后循环时还会一直通知你有这个键。

最后,当准备关闭服务器或不再需要选择器时,应当将它关闭:

public abstract void close() throws IOException

这个步骤会释放与 选择器 关联的所有资源。更重要的是,它取消了之前向 选择器 注册的所有键,并中断被这个选择器的某个选择方法所阻塞的线程。

SelectionKey类

SelectionKey对象相当于通道的指针,每个向Selector注册的通道都返回一个SelectionKey对象。塔门还可以保存一个对象附件,一般会存储这个通道上的连接的状态。

讲一个通道注册到一个选择器时,register()方法会返回SelectionKey对象。不过,通常不需要你保留这个引用。selectedKeys()方法可以在Set中再次返回相同的对象。此外,一个通道可以注册到多个选择器Selector。

当从所选择的键集合中获取一个SelectionKey时,通常首先要测试这些键能进行哪些操作。有以下4种可能,

  • public final boolean isAcceptable()
  • public final boolean isConnectable()
  • public final boolean isReadable()
  • public final boolean isWritable()

这个测试并不总是必须的。有些情况下,选择器只测试一种可能性,也只返回完成这种操作的键。但如果选择器确实要测试多种就绪状态,就要在操作前先测试通道对于哪个操作进入就绪状态。也有可能通道准备好可以完成多个操作。

一旦了解了与键关联的通道准备好完成何种操作,就可以用channel()方法来获取这个通道,

public abstract SelectableChannel channel()

如果在保存状态信息的SelectionKey存储了一个对象,就可以用attachment()方法获取该对象:

public final Object attachment()

最后, 如果结束使用连接,就要撤销其 SelectionKey 对象的注册,这样选择器就不会浪费资源再去查询它是否准备就绪。我不知道这是否在所有情况下都非常重要,但起码没有坏处。可以调用这个键的cancel()方法来撤销注册:

public abstract void cancel()

不过,只有在未关闭通道时这个步骤才有必要。如果通道关闭,会自动在所有选择器中撤销对应这个通道的所有键的注册(因为一个通道可以注册到多个selector中)。类似地,关闭选择器会使这个选择器中的所有键都失效。

欢迎提出疑问和指出不足!!!

其它更详细参见我的另一篇:Java NIO中的Selector和IO复用

你可能感兴趣的:(java,socket,api,nio,网络编程)