• NIO:Selector 详解


    示例程序TCPEchoServerSelector中展示了Selector的基本用法。在此,我们将对其进行更加详细的介绍。

    Selector: 创建和关闭

    static Selector open()

    boolean isOpen()

    void close()

    调用Selectoropen()工厂方法可以创建一个选择器实例。选择器的状态是"打开""关闭"的。创建时选择器的状态是打开的,并保持该状态,直到调用close()方法通知系统其务已经完成。可以调用isOpen()方法来检查选择器是否已经关闭。

    5.6.1在信道中注册

    我们已经知道,每个选择器都有一组与之关联的信道,选择器对这些信道上"感兴趣的"I/O操作进行监听。SelectorChannel之间的关联由一个SelectionKey实例表示。(注意一个信道可以注册多个Selector实例,因此可以有多个关联的SelectionKey实例)SelectionKey维护了一个信道上感兴趣的操作类型信息,并将这些信息存放在一个int型的位(bitmap中,该int型数据的每一位都有相应的含义。

     SelectionKey类中的常量定义了信道上可能感兴趣的操作类型,每个这种常量都是只有一位设置为1的位掩码(bitmask)(见第3.1.3节) 

    SelectionKey: 兴趣操作集

    static int OP_ACCEPT

    static int OP_CONNECT

    static int OP_READ

    static int OP_WRITE

    int interestOps()

    SelectionKey interestOps(int ops)

    通过对OP_ ACCEPTOP_CONNECTOP_READ以及OP_WRITE中适当的常量进行按位OR,我们可以构造一个位向量来指定一组操作。例如,一个包含读和写的操作集由表达式(OP_READ | OP_WRITE)来指定。不带参数的interestOps()方法将返回一个int位图,该位图中设置为1的每一位都指示了信道上需要监听的一种操作。另一个方法以一个位图为参数,指示了应该监听信道上的哪些操作。重点提示:任何对key(信道)所关联的兴趣操作集的改变,都只在下次调用了select()方法后才会生效。

    SocketChannel, Server SocketChannel:注册Selector

    SelectionKey register(Selector sel, int ops)

    SelectionKey register(Selector sel, int ops, Object

    attachment)

    int validOps()

    boolean isRegistered()

    SelectionKey keyFor(Selector sel)

    调用信道的register()方法可以将一个选择器注册到该信道。在注册过程中,通过存储在int型数据中的位图来指定该信道上的初始兴趣操作集(见上文的"SelectionKey:兴趣操作")。register()方法将返回一个代表了信道和给定选择器之间的关联的SelectionKey实例。validOps()方法用于返回一个指示了该信道上的有效I/O操作集的位图。对于ServerSocketChannel来说,accept是惟一的有效操作,而对于SocketChannel来说,有效操作包括读、写和连接。对于DatagramChannel,只有读写操作是有效的。一个信道可能只与一个选择器注册一次,因此后续对register()方法的调用只是简单地更新该key所关联的兴趣操作集。使用isRegistered()方法可以检查信道是否已经注册了选择器。keyFor()方法与第一次调用register()方法返回的是同一个SelectionKey实例,除非该信道没有注册给定的选择器。

    以下代码注册了一个信道,支持读和写操作:

    SelectionKey key = clientChannel.register(selector,

    SelectionKey.OP_READ | SelectionKey.OP_WRITE);

    5.1展示了一个选择器,其键集中包含了7个代表注册信道的键:两个在端口40004001上的服务器信道,以及从服务器信道创建的5个客户端信道:

    SelectionKey: 获取和取消

    Selector selector()

    SelectableChannel channel()

    void cancel()

    键关联的Selector实例和Channel实例可以分别使用该键的selector()channel()方法获得。cancel()方法用于(永久性地)注销该键,并将其放入选择器的注销集(canceled set中(图5.1)。在下一次调用select()方法时,这些键将从该选择器的所有键集中移除,其关联的信道也将不再被监听(除非它又重新注册)。

     

    (点击查看大图)图5.1Selector与其关联的键集

    Selected Key Set:   选择键集; Cancelled Key Set:注销键集; Key Set:键集;Interest Sets

    兴趣操作集

     5.6.2选取和识别准备就绪的信道

     在信道上注册了选择器,并由关联的键指定了感兴趣的I/O操作集后,我们就只需要坐下来等待I/O了。这要使用选择器来完成。

    Selector: 等待信道准备就绪

    int select()

    int select(long timeout)

    int selectNow()

    Selector wakeup()

    select()方法用于从已经注册的信道中返回在感兴趣的I/O操作集上准备就绪的信道总数。(例如,兴趣操作集中包含OP_READ的信道有数据可读,或包含OP_ACCEPT的信道有连接请求待接受。)以上三个select方法的惟一区别在于它们的阻塞行为。无参数的select方法会阻塞等待,直到至少有一个注册信道中有感兴趣的操作准备就绪,或有别的线程调用了该选择器的wakeup()方法(这种情况下select方法将返回0)。以超时时长作为参数的select方法也会阻塞等待,直到至少有一个信道准备就绪,或等待时间超过了指定的毫秒数(正数),或者有另一个线程调用其wakeup()方法。selectNow()方法是一个非阻塞版本:它总是立即返回,如果没有信道准备就绪,则返回0wakeup()方法可以使当前阻塞(也就是说在另一个线程中阻塞)的任何一种select方法立即返回;如果当前没有select方法阻塞,下一次调用这三种方法的任何一个都将立即返回。

     选择之后,我们需要知道哪些信道准备好了特定的I/O操作。每个选择器都维护了一个已选键集(selected-key set),与这些键关联的信道都有即将发生的特定I/O操作。通过调selectedKeys()方法可以访问已选键集,该方法返回一组SelectionKey。我们可以在这组键上进行迭代,分别处理等待在每个键关联的信道上的I/O操作。

    Iterator<SelectionKey> keyIter =

    selector.selectedKeys().iterator();

    while (keyIter.hasNext()) {

    SelectionKey key = keyIter.next();

    // ...Handle I/O for key's channel...

    keyIter.remove();

    }

     5.1中的选择器的已选键集中有两个键:K2K5

    Selector: 获取键集

    Set<SelectionKey> keys()

    Set<SelectionKey> selectedKeys() 

    以上方法返回选择器的不同键集。keys()方法返回当前已注册的所有键。返回的键集是不可修改的:任何对其进行直接修改的尝试(如,调用其remove()方法)都将抛出UnsupportedOperationException异常。selectedKeys()方法用于返回上次调用select()方法时,"选中"的已准备好进行I/O操作的键。重要提示:selectedKeys()方法返回的键集是可修改的,实际上在两次调用select()方法之间,都必须"手工"将其清空。换句话说,select方法只会在已有的所选键集上添加键,它们不会创建新的键集。

    所选键集指示了哪些信道当前可以进行I/O操作。对于选中的每个信道,我们需要知道它们各自准备好的特定I/O操作。除了兴趣操作集外,每个键还维护了一个即将进行的I/O操作集,称为就绪操作集(ready set)。 

    SelectionKey: 查找就绪的I/O操作

    int readyOps()

    boolean isAcceptable()

    boolean isConnectable()

    boolean isReadable()

    boolean isValid()

    boolean isWritable()

    对于给定的键,可以使用readyOps()方法或其他指示方法来确定兴趣集中的哪些I/O作可以执行。readyOps()方法以位图的形式返回所有准备就绪的操作集。其他方法用于分别检查各种操作是否可用。 

    例如,查看键关联的信道上是否有正在等待的读操作,可以使用以下代码:

    (key.readyOps() & SelectionKey.OP_READ) != 0key.isReadable()

    选择器的已选键集中的键,以及每个键中准备就绪的操作,都是由    select()方法来确定的。随着时间的推进,这些信息可能会过时。其他线程可能会处理准备就绪的I/O操作。同时,键也不是永远存在的。当其关联的信道或选择器关闭时,键也将失效。通过调用其cancel()方法可以显示地将键设置为无效。调用其isValid()方法可以检测一个键的有效性。无效的键将添加到选择器的注销键集中,并在下次调用任一种形式的                select()方法或    close()方法时从键集中移除。(当然,从键集中移除键意味着与它关联的信道也不再受监听。)

    5.6.3信道附件

    当一个信道准备好进行I/O操作时,通常还需要额外的信息来处理请求。例如,在前面的回显协议中,当客户端信道准备好写操作时,就需要有数据可写。当然,我们所需要的可写数据是由之前同一信道上的读操作收集的,但是在其可写之前,这些数据存放在什么地方呢?另一个例子是第3章中的成帧过程。如果一个消息一次传来了多个字节,我们需要保存已接收的部分消息,直到完整个消息接收完成。这两种情况都需要维护每个信道的状态信息。然而,我们非常幸运!SelectionKey通过使用附件使保存每个信道的状态变得容易。

    SelectionKey: 查找准备就绪的I/O操作

    Object attach(Object ob)

    Object attachment()

    每个键可以有一个附件,数据类型只能是Object类。附件可以在信道第一次调用register()方法时与之关联,或者后来再使用          attach()方法直接添加到键上。通过         SelectionKey   attachment()方法可以访问键的附件。 

    5.6.4 Selector小结

    总的来说,使用Selector的步骤如下:

    I.创建一个Selector实例。

    II.将其注册到各种信道,指定每个信道上感兴趣的I/O操作。

    III.重复执行:

    1.调用一种select方法。

    2.获取选取的键列表。

    3.对于已选键集中的每个键,

    a.获取信道,并从键中获取附件(如果合适的话)

    b.确定准备就绪的操作并执行。如果是accept操作,将接受的信道设置为非阻塞模式,

    并将其与选择器注册。

    c.如果需要,修改键的兴趣操作集

    d.从已选键集中移除键

    如果选择器告诉了你什么时候I/O操作准备就绪,你还需要非阻塞I/O吗?答案是肯定的。信道在已选键集中的键并不能确保非阻塞I/O,因为调用了select()方法后,键集信息可能会过时。另外,阻塞式写操作会阻塞等待直到写完所有的字节,而就绪集中的OP_WRITE仅表示至少有一个字节可写。实际上,只有非阻塞模式的信道才能与选择器进行注册:如果信道在阻塞模式,SelectableChannel类的register()方法将抛出IllegalBlockingModeException异常。

     

    相关下载:

    Java_TCPIP_Socket编程(doc)

    http://download.csdn.net/detail/undoner/4940239

     

    文献来源:

    LSOFT.CN(琅软中国)  

  • 相关阅读:
    HDU2059(龟兔赛跑)
    pat 1012 The Best Rank
    pat 1010 Radix
    pat 1007 Maximum Subsequence Sum
    pat 1005 Sign In and Sign Out
    pat 1005 Spell It Right
    pat 1004 Counting Leaves
    1003 Emergency
    第7章 输入/输出系统
    第六章 总线
  • 原文地址:https://www.cnblogs.com/wuyida/p/6301054.html
Copyright © 2020-2023  润新知