Selector

多路复用

1、单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控

2、多路复用仅针对网络 I/O,普通文件 I/O 无法利用多路复用

3、Selector 保证

(1)有可连接事件时才去连接

(2)有可读事件才去读取

(3)有可写事件才去写入

4、限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件

 

Selector

public abstract class Selector
extends Object
implements Closeable

1、SelectableChannel 对象的多路复用器

2、可以通过调用此类的 open 方法来创建 Selector,该方法将使用系统的默认 SelectorProvider 创建一个新的 Selector

3、还可以通过调用自定义 SelectorProvider 的 openSelector 方法来创建 Selector

4、 Selector 保持打开,直到通过其 close 方法关闭

5、 Selector 的可选通道注册由 SelectionKey 对象表示, Selector 保存三组 SelectionKey

(1)key set 包含表示此 Selector 当前通道注册的键,该集合由 keys 方法返回

(2)selected-key set 中的每个键的通道,在先前的选择操作中,被检测到至少准备好进行键的 interest set 中的一个操作,该集合由 selectedKeys 方法返回,selected-key set 始终是 key set 的子集

(3)cancelled-key set 是已经被取消但其通道尚未被注销的密钥的集合,此集不能直接访问,cancelled-key set 始终是 key set 的子集

(4)新创建的 Selector 中的所有三组都为空

6、通过通道的 register 方法,将一个键添加到 Selector 的 key set 中,作为注册频道的副作用,在选择操作期间,从 key set 中删除取消的键,key set 本身不能直接修改

7、无论是通过关闭其通道,还是调用其 cancel 方法,都会将其添加到其 Selector 的 cancelled-key set 中,取消键将导致其通道在下一次选择操作期间被注销,此时该键将从所有 Selector 的键集中移除

8、键通过选择操作被添加到 selected-key 中

9、通过调用集合的 remove 方法,或通过调用从集合中获得的 iterator 的 remove 方法,可以直接从 selected-key set 中移除一个 key,键不会以任何其他方式从 selected-key set 中移除,特别是,它们不会作为选择操作的副作用被移除,键不能被直接添加到 selected-key set 中

10、选择操作

(1)在每个选择操作期间,可以添加或移除 Selector 的 selected-key set 的 key,也可以从 key set 和 cancelled-key set 中移除 key

(2)Selection 由 select()、select(long)selectNow() 执行,以及包括(3)(4)(5)三个步骤

(3)cancelled-key set 中的每个密钥,从其作为其成员的每个密钥集中移除,并且其信道被注销,此步骤将 cancelled-key set 设置为空

(4)查询底层操作系统,更新每个剩余通道的准备状态,在选择操作开始时,执行由其密钥的 interest set 确定的任何操作,对于准备进行至少一个此类操作的通道,执行以下两个操作之一;如果在此步骤开始时,key set 中的所有密钥都具有空的 interest set,那么 selected-key set 和任何密钥的 ready set 都将不被更新

如果通道的密钥尚未在 selected-key set 中,则将其添加到该集合中,并且其 ready set 被修改,以便准确地标识该信道现在被报告准备好的那些操作,先前记录在 ready set 中的任何就绪信息都被丢弃
如果通道的密钥已经在 selected-key set 中,因此其 ready set 被修改,以识别通道报告准备就绪的任何新操作,以前记录在 ready set 中的任何准备信息都将被保留,即由底层系统返回的 ready set 被按位分离到键的 ready set 中

(5)如果在步骤(4)进行中,将任何键添加到 cancelled-key set,则它们将如步骤(1)中那样处理

(6)一个选择操作是否阻塞等待一个或多个通道的准备,如果是的话,等待多长时间是三种选择方法之间唯一的本质区别

11、并发

(1)Selector 本身可以安全地被多个并发线程使用,但其 key set 不是

(2)选择操作在以 Selector 本身、key set、selected-key set 的顺序同步,还在上述 10 中的(3)、(5)中的 cancelled-key set 上进行同步

(3)在选择操作正在进行时,对 Selector 的键的 interest set 进行更改,对该操作没有影响,他们将在下一个选择操作中看到

(4)键可能会被取消,渠道可能随时关闭,因此,在一个或多个 Selector 的 key set 中存在密钥,并不意味着密钥是有效的或其信道是打开的,如果有可能另一个线程将取消键或关闭通道,则应用代码应小心同步,并根据需要检查这些条件

(5)一个被阻塞在 select() 或 select(long) 方法中的线程,可能会被其他一些线程,以三种方式之一打断

(6)通过调用 Selector 的 wakeup 方法

(7)通过调用 Selector 的 close 方法

(8)通过调用阻塞线程的 interrupt 方法,在这种情况下,设置它的中断状态,并且将调用 Selector 的 wakeup 方法

(9)close 方法在 Selector 和所有三个键组上同步,其顺序与选择操作相同

(10)Selector 的密钥和 selected-key set 通常不会被多个并发线程安全使用,如果这样的线程可能会直接修改这些集合中的一个,那么应该通过对集合本身进行同步来控制访问,这些集合的 iterator 方法所返回的 iterator 是快速失效的。如果在迭代器创建后,集合被修改,除了调用迭代器自身的 remove 方法外,其他任何方式都会被抛出一个 ConcurrentModificationException

12、打开 Selector

public static Selector open() throws IOException

(1)新的 Selector 是通过调用系统范围的默认 SelectorProvider 对象的 openSelector 方法创建的

(2)返回一个新的 Selector

13、告诉这个 Selector 是否打开

public abstract boolean isOpen()

(1)如果这个 Selector 是打开的,则返回 true

14、返回创建此通道的 SelectorProvider

public abstract SelectorProvider provider()

15、返回这个 Selector 的 key set

public abstract Set<SelectionKey> keys()

(1)key set 是不能直接修改的,一个键只有在它被取消和它的通道被取消注册后,才能被删除,任何试图修改 key set 的行为,都会导致 UnsupportedOperationException 被抛出

(2)key set 不是线程安全的

16、返回这个 Selector 的 selected-key set

public abstract Set<SelectionKey> selectedKeys()

(1)键可以从 selected-key set 中删除,但不能直接添加到 selected-key set 中,任何试图向 selected-key set 添加对象的行为,都会导致 UnsupportedOperationException 被抛出

(2)selected-key set 不是线程安全的

17、选择一组键,其对应的通道已经准备好进行 I/O 操作

public abstract int selectNow() throws IOException

(1)此方法执行非阻塞选择操作,如果以前的选择操作没有通道变为可选择,则该方法立即返回 0

(2)调用此方法将清除任何先前调用 wakeup 方法的效果

(3)返回键的数量(就绪通道数量:自前一次 select 以来,到本次 select 之间的时间段上,有多少通道变成就绪状态),可能为 0,其 ready set 由选择操作更新

18、选择一组键,其对应的通道已经准备好进行 I/O 操作

public abstract int select(long timeout) throws IOException

(1)这个方法执行一个阻塞的选择操作,只有在至少一个通道被选中,这个 Selector 的 wakeup 方法被调用,当前线程被中断,或者给定的到达超时时间后(以先到者为准),它才会返回

(2)这个方法不提供实时保证,它通过调用 Object.wait(long) 方法来安排超时

(3)timeout:如果是正数,在等待通道准备好时,最多阻塞 timeout 毫秒;如果是 0,则无限期阻断;不能为负数

(4)返回键的数量(就绪通道数量:自前一次 select 以来,到本次 select 之间的时间段上,有多少通道变成就绪状态),可能为 0,其 ready set 被更新

19、选择一组键,其对应的通道已经准备好进行 I/O 操作

public abstract int select() throws IOException

(1)这个方法执行一个阻塞式的选择操作,只有在至少一个通道被选中,这个 Selector 的 wakeup 方法被调用,或者当前线程被中断(以先到者为准),它才会返回

(2)返回键的数量(就绪通道数量:自前一次 select 以来,到本次 select 之间的时间段上,有多少通道变成就绪状态),可能是 0,其 ready set 被更新

20、导致尚未返回的第一个选择操作立即返回

public abstract Selector wakeup()

(1)如果另一个线程目前在调用 select() 或 select(long) 方法时被阻塞,那么该调用将立即返回

(2)如果当前没有选择操作正在进行,那么下一次调用 select() 或 select(long) 方法之一将立即返回,除非在此期间调用 selectNow() 方法

(3)在任何情况下,该调用返回的值可能是非零

(4)随后对 select() 或 select(long) 方法的调用将像往常一样阻塞,除非在此期间再次调用此方法

(5)在两个连续的选择操作之间多次调用此方法,与只调用一次的效果相同

(6)返回此 Selector

21、关闭这个 Selector

public abstract void close() throws IOException

(1)如果一个线程目前在这个 Selector 的其中一个选择方法中被阻塞,那么它将被打断,就像调用 Selector 的 wakeup 方法一样

(2)任何仍与此 Selector 相关的未取消的键都会失效,它们的通道被取消注册,任何与此 Selector 相关的其他资源都会被释放

(3)如果这个 Selector 已经被关闭,那么调用这个方法就没有任何效果。

(4)在一个 Selector 被关闭后,除了调用此方法或 wakeup 方法外,任何进一步使用它的尝试都将导致 ClosedSelectorException 被抛出

22、事项

(1)Selector 查询通道的某个操作的一种就绪状态

(2)操作的就绪状态:一旦通道具备完成某个操作的条件,表示该通道的某个操作已经就绪,就可以被 Selector 查询,程序可以对通道进行对应的操作

(3)Channel 注册后,并且一旦通道处于某种就绪的状态,就可以被选择器查询,使用 Selector 的 select() 方法完成

(4)select 方法的作用:对感兴趣的通道操作,进行就绪状态的查询

(5)Selector 可以不断的查询 Channel 中发生的操作的就绪状态,并且挑选感兴趣的操作就绪状态,一旦通道有操作的就绪状态达成,并且是 Selector 感兴趣的操作,就会被 Selector 选中,放入 selected-key set 中

 

AbstractSelector

public abstract class AbstractSelector
extends Selector

1、Selector 的基本实现类

(1)该类封装了实施选择操作的中断所需的底层代码,一个具体的 Selector 类,必须在调用一个可能无限期阻塞的 I/O 操作之前和之后,分别调用 begin 和 end 方法,为了确保 end 方法总是被调用,这些方法应该在 try …… finally 块中使用

try {
    begin();
    // Perform blocking I/O operation here
} finally {
    end();
}

(2)这个类还定义了维护一个 Selector 的 cancelled-key set 和从其通道的 key set 中删除一个键的方法,并声明了抽象的 register 方法,该方法被一个 SelectableChannel 的 register 方法调用,以执行注册一个通道的实际工作

2、关闭此 Selector

public final void close() throws IOException

(1)如果 Selector 已经关闭,则该方法将立即返回,否则它将 Selector 标记为关闭,然后调用 implCloseSelector 方法以完成关闭操作

3、关闭此 Selector

protected abstract void implCloseSelector() throws IOException

(1)该方法由 close 方法调用,以执行关闭 Selector 的实际工作

(2)只有当 Selector 尚未关闭时,才会调用此方法,并且不会多次调用此方法

(3)该方法的实现,必须安排在选择操作中,被阻塞的任何其他线程立即返回,就好像通过调用 wakeup 方法一样

4、告知这个 Selector 是否打开

public final boolean isOpen()

(1)如果这个选择器是打开的,返回 true

5、返回创建此通道的 SelectorProvider

public final SelectorProvider provider()

6、检索此 Selector 的 cancelled-key set

protected final Set<SelectionKey> cancelledKeys()

(1)该集合只能在同步时使用

(2)返回 cancelled-key set

7、使用此 Selector 注册给定通道

protected abstract SelectionKey register(AbstractSelectableChannel ch,
                                         int ops,
                                         Object att)

(1)该方法由通道的 register 方法调用,以便执行此 Selector 注册通道的实际工作

(2)ch:要注册的频道

(3)ops:初始 interest set,必须是有效的

(4)att:返回密钥的初始附件

(5)返回一个新的密钥,代表在这个 Selector 上注册给定的通道

8、从Selector 通道的 key set 中删除给定的密钥

protected final void deregister(AbstractSelectionKey key)

(1)该方法必须由 Selector 对每个要注销的通道进行调用

(2)key:要删除的 SelectionKey

9、标志着一个可能无限期阻塞的 I/O 操作的开始

protected final void begin()

(1)应与 end 方法一起调用此方法,使用 try …… finally 块,以实现该 Selector 的中断

(2)如果在选择器上的 I/O 操作中,线程被阻塞时,调用线程的 interrupt 方法,调用此方法可以安排选择器的 wakeup 方法被调用

10、标志着一个可能无限期阻塞的 I/O 操作的结束

protected final void end()

(1)这个方法应该和 begin 方法一起调用,使用 try …… finally 块,以便为这个 Selector 实现中断

 

SelectableChannel 向 Selector 注册

1、SelectableChannel:使用给定的 Selector 注册此通道,返回一个 SelectionKey

public final SelectionKey register(Selector sel,
                                   int ops)
                            throws ClosedChannelException

(1)sc.register(sel, ops) 等价于 sc.register(sel, ops, null)

(2)sel:要注册该通道的 Selector

(3)ops:为返回的 SelectionKey 设置的 interest set

(4)返回该通道在给定 Selector 注册的 SelectionKey

2、SelectableChannel:使用给定的 Selector 注册此通道,返回一个 SelectionKey

public abstract SelectionKey register(Selector sel,
                                      int ops,
                                      Object att)
                               throws ClosedChannelException

(1)如果该通道当前已经向给定的 Selector 注册,则返回表示该注册的选择键,密钥的 interst set 将改为 ops,就像通过调用 interestOps(int) 方法一样,如果 att 参数不是 null,则键的附件将被设置为新值

(2)如果该键已被取消,则将抛出 CancelledKeyException

(3)若此通道尚未注册到给定的 Selector,则注册该通道,并返回所产生的新密钥,键的 interest set 将为 ops,其附件将为 att

(4)可以随时调用此方法,如果此方法被调用,如果在调用此方法时,其他 SelectableChannel 调用此方法或 configureBlocking 方法时,则它将首先阻塞,直到其他操作完成,然后,此方法将在 Selector 的 key set 上同步,因此,如果与涉及同一 Selector 的另一个注册或选择操作同时调用,则可能会阻塞

(5)如果此操作正在进行时,此通道被关闭,则此方法返回的密钥将被取消,因此将无效

(6)sel:要注册该通道的 Selector

(7)ops:为返回的 SelectionKey 设置的 interest set

(8)att:所得密钥的附件,可能是 null

(9)返回表示该通道与给定 Selector 的注册的 SelectionKey

3、AbstractSelectableChannel:使用给定的选择器注册此通道,返回一个 SelectionKey

public final SelectionKey register(Selector sel,
                                   int ops,
                                   Object att)
                            throws ClosedChannelException

(1)该方法首先验证该通道是否打开,并且给定的初始兴趣集合是有效的

(2)如果该通道已经向给定的选择器注册,那么在将其兴趣设置为给定值之后,返回表示该注册的 SelectionKey

(3)否则,该通道尚未注册到给定的选择器,因此在保持适当的锁定的同时,调用选择器的 register 方法,生成的密钥将在返回之前,添加到此通道的密钥集中

(4)sel:要注册该通道的选择器

(5)ops:为所得密钥设置的兴趣

(6)att:所得密钥的附件,可能是 null

(7)返回表示该通道与给定 Selector 的注册的 SelectionKey

4、事项

(1)一个通道可以被注册到多个 Selector 上,但一个通道只能在同一 Selector 注册一次

(2)如果 Selector 对通道的多操作类型感兴趣,可以用 |(按位或)连接 SelectionKey

(3)与 Selector 一起使用时,Channel 必须处于非阻塞模式下,否则将抛出异常 IllegalBlockingModeException

 

SelectionKey

public abstract class SelectionKey
extends Object

1、一个令牌代表一个 SelectableChannel 与 Selector 的注册

2、每当通道被选择器注册时,都会创建一个 SelectionKey

(1)一个键一直有效,直到它通过调用其 cancel 方法、close 其通道、close 其选择器而被取消

(2)取消一个键并不立即从它的 Selector 中移除;相反,它被添加到 Selector 的 cancelled-key set 中,以便在下一次选择操作中移除

(3)密钥的有效性可以通过调用其 isValid 方法来测试

3、SelectionKey 包含两个表示为整数值的 operation set,operation set 的每一位都表示该键的通道所支持的可选择操作的类别

(1)interest set 确定下一次调用 Selector 的选择方法之一后,哪些操作类别将被测试为准备就绪,interest set 在创建密钥时用给定的值初始化,可以稍后通过 interestOps(int) 方法进行更改

(2)ready set 标识了该键的通道,被该键的 Selector 检测到就绪的操作类别,当键被创建时,ready set 被初始化为零;它可以在以后的选择操作中被 Selector 更新,但它不能被直接更新;一个 SelectionKey 的 ready set 表明它的通道已经为某些操作类别做好了准备,这是一种提示,但不是一种保证,即这种类别的操作可以由一个线程执行而不会导致线程阻塞,在选择操作完成后,ready set 很可能是准确的,它很可能被外部事件和在相应通道上调用 I/O 操作弄得不准确

4、这个类定义了所有已知的操作集位,但具体哪些位被一个给定的通道所支持,取决于通道的类型

(1)SelectableChannel 的每个子类都定义了一个 validOps() 方法,该方法返回一个集合,用来识别那些被通道支持的操作

(2)试图设置或测试一个不被键的通道支持的操作集位,将导致一个适当的运行时异常

5、通常有必要将一些特定于应用程序的数据与 SelectionKey 相关联

(1)例如,为了实现该协议,一个代表上级协议的状态并处理准备就绪通知的对象

(2)因此,SelectionKey 支持将单个任意的对象附加到一个键上,一个对象可以通过 attach 方法被附加,然后再通过 attachment 方法被检索

6、SelectionKey 对于多并发线程的使用是安全的

(1)读写 interest set 的操作,一般来说,将与 Selector 的某些操作同步,确切地说,如何进行这种同步是与实现有关的

(2)在一个低性能的实现中,如果一个选择操作已经在进行中,读或写 interest set 可能会无限期地阻塞

(3)在一个高性能的实现中,读或写 interest set 可能会短暂地阻塞,如果有的话

(4)在任何情况下,一个选择操作将始终使用操作开始时,当前的  interest set 值

7、读操作的操作集位

(1)假设在一个选择操作开始时,一个 SelectionKey 的 interest set 包含 OP_READ

(2)如果 Selector 检测到相应的通道已经准备好进行读取 / 已经到达流的末端 / 已经被远程关闭以继续读取 / 有一个错误等待处理

(3)那么 Selector 将把 OP_READ 添加到 SelectionKey 的 ready set,并把 SelectionKey 添加到 Selector 的 selected-key set

public static final int OP_READ = 1 << 0;

8、写操作的操作集位

(1)假设在一个选择操作开始时,一个 SelectionKey 的 interest set 包含 OP_WRITE

(2)如果 Selector 检测到相应的通道已经准备好写入 / 已经被远程关闭以便进一步写入 / 有一个错误等待处理

(3)那么 Selector 将把 OP_WRITE 添加到 SelectionKey 的 ready set,并把该 SelectionKey 添加到 Selector 的 selected-key set

public static final int OP_WRITE = 1 << 2;

9、socket-connect 操作的操作集位

(1)假设在一个选择操作开始时,一个 SelectionKey 的 interest set 包含 OP_CONNECT

(2)如果 Selector 检测到相应 SocketChannel 已经准备好完成它的连接序列 / 有一个错误等待处理

(3)那么 Selector 将把 OP_CONNECT 添加到 SelectionKey 的 ready set 中,并把该 SelectionKey 添加到 Selector 的 selected-key set

public static final int OP_CONNECT = 1 << 3;

10、socket-accept 操作的操作集位

(1)假设在一个选择操作开始时,一个选择键的兴趣集包含OP_ACCEPT

(2)如果 Selector 检测到相应的 ServerSocketChannel 已经准备好接受另一个连接 / 有一个错误等待处理

(3)那么 Selector 将把 OP_ACCEPT 添加到 SelectionKey 的 ready set 中,并把该 SelectionKey 添加到 Selector 的 selected-key set

public static final int OP_ACCEPT = 1 << 4;

11、SelectionKey 是 Selector 对所注册的 SelectableChannel 感兴趣的操作事件

(1)一个 SelectionKey,首先是包含注册在 Selector 的通道操作的类型,也包含特定的通道与特定的选择器之间的注册关系

(2)NIO 编程,就是根据对应的 SelectionKey,进行不同的业务逻辑处理

(3)SelectionKey 概念,和事件的概念相似,一个 SelectionKey 类似监听器模式中的一个事件

(4)由于 Selector 不是事件触发的模式,而是主动查询的模式,所以不叫 Event,而是叫 SelectionKey

12、返回创建此键的通道

public abstract SelectableChannel channel()

(1)该方法即使在 cancel 键之后,仍将继续返回通道

13、返回创建此键的选择器

public abstract Selector selector()

(1)该方法即使在 cancel 键之后,也会继续返回选择器

14、告知这个密钥是否有效

public abstract boolean isValid()

(1)键在创建时有效,并保持原样,直到它被 cancel / 其通道关闭 / 其选择器关闭

(2)当此密钥有效,返回 true

15、要求取消该密钥的通道与其选择器的注册

public abstract void cancel()

(1)返回时,密钥将无效,并将被添加到其选择器的 cancelled-key set 中

(2)在下一次选择操作期间,键将从选择器的所有密钥集合中移除

(3)如果此键已被取消,则调用此方法不起作用,一旦取消,钥匙永远无效

(4)可以随时调用此方法,它在选择器的 cancelled-key set 上同步,因此,如果与涉及同一选择器的 cancel 或选择操作同时调用,可能会短暂阻塞

16、返回此密钥的 interest set

public abstract int interestOps()

(1)保证返回的集合只包含对这个键的通道有效的操作位

(2)这个方法可以在任何时候被调用,是否阻断,以及阻断多长时间,都取决于实现

17、返回此密钥的 ready set

public abstract int readyOps()

(1)保证返回的集合只包含对该密钥的通道有效的操作位

18、测试此密钥的通道是否可读

public final boolean isReadable()

(1)k.isReadable() 等价于 k.readyOps() & OP_READ != 0
 
(2)如果此键的通道不支持读取操作,则此方法始终返回 false

(3)当 readyOps() & OP_READ 不为 0,返回 true

19、测试此密钥的通道是否准备好进行写入

public final boolean isWritable()

(1)k.isWritable() 等价于 k.readyOps() & OP_WRITE != 0

(2)如果此键的通道不支持写操作,则此方法始终返回 false

(3)当 readyOps() & OP_WRITE 不为 0,返回 true

20、测试此密钥的通道是否完成其 Socket 连接操作

public final boolean isConnectable()

(1)k.isConnectable() 等价于 k.readyOps() & OP_CONNECT != 0

(2)如果此键的通道不支持 Socket 连接操作,则此方法始终返回 false

(3)当 readyOps() & OP_CONNECT 不为 0,返回 true

21、测试此密钥的通道是否已准备好接受新的 Socket 连接

public final boolean isAcceptable()

(1)k.isAcceptable() 等价于 k.readyOps() & OP_ACCEPT != 0

(2)如果此键的通道不支持接受 Socket 连接操作,则此方法始终返回 false

(3)当 readyOps() & OP_ACCEPT 不为 0,返回 true

22、将给定对象附加到此键

public final Object attach(Object ob)

(1)随后可以通过 attachment 方法检索附件

(2)一次只能附加一件物品

(3)调用此方法会导致任何先前的附件被丢弃,当前的附件可以通过附加 null 来丢弃

(4)ob:附件,可能是 null

23、返回当前附件

public final Object attachment()

(1)如果没有附件,返回 null

 

SelectableChannel 实现类所支持的 SelectionKey

1、SocketChannel

public final int validOps()

(1)返回确定 SocketChannel 支持操作的操作集

(2)SocketChannel 支持连接、读取、写入,因此该方法返回 ( SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE )(十进制:13)

2、ServerSocketChannel

public final int validOps()

(1)返回确定 ServerSocketChannel 支持操作的操作集

(2)ServerSocketChannel 仅支持接受新连接,因此此方法返回 SelectionKey.OP_ACCEPT(十进制:16)

3、DatagramChannel

public final int validOps()

(1)返回确定 DatagramChannel 支持操作的操作集

(2)DatagramChannel 支持读写,所以这种方法返回 ( SelectionKey.OP_READ | SelectionKey.OP_WRITE )(十进制:5)

 

AbstractSelectionKey

public abstract class AbstractSelectionKey
extends SelectionKey

1、SelectionKey 的基础实现类

2、该类跟踪键的有效性,并实现 cancel

3、告知这个密钥是否有效

public final boolean isValid()

(1)键在创建时有效,并保持原样,直到它被取消 / 其通道关闭 / 其选择器关闭

(2)如果这个键有效,返回 true

4、取消此键

public final void cancel()

(1)如果此键尚未被取消,则在该组同步时,将其添加到其 Selector 中的 cancelled-key set 中

 

删除 SelectionKey

1、当处理完一个事件后,一定要调用迭代器 remove 方法,移除对应 SelectionKey,否则会出现错误

2、原因

(1)当调用 register 后,Selector 中 key set 存放 SelectionKey 以及其对应的通道

(2)当被 select 后,SelecionKey 存放在 selected-key set,但 SelecionKey 不会自动移除,所以需要在处理完事件后,通过迭代器手动 remove 其中 SelecionKey,否则会导致已被处理过的事件再次被处理,引发错误

 

断开处理

1、当客户端与服务器之间的连接断开时,会给服务器端发送一个读事件,对异常断开和正常断开需要加以不同的方式进行处理

2、正常断开

(1)服务器端 read 方法的返回值为 -1

(2)当结束到返回值为 -1 时,需要调用 SelectionKey 的 cancel 方法取消此事件,并在取消后 remove 该事件

3、异常断开

(1)抛出 IOException 异常

(2)在 try-catch 的 catch 块中捕获异常,并调用 SelectionKey 的 cancel 方法

 

处理消息边界

1、传输数据的三种情况

(1)数据长度大于缓冲区大小:需要将缓冲区进行扩容

(2)半包

(3)粘包

2、解决

(1)固定消息长度:数据包大小一样,服务器按预定长度读取,当发送的数据较少时,需要将数据进行填充,直到长度与消息规定长度一致,浪费带宽

(2)按分隔符拆分:效率低,需要一个一个字符地去匹配分隔符

(3)TLV 格式:即 Type 类型、Length 长度、Value 数据(消息头存放后面数据的长度),如 HTTP 请求头中的 Content-Type 与 Content-Length,类型、长度已知的情况下,方便获取消息大小,分配合适 Buffer,但 Buffer 需要提前分配,如果内容过大,则影响服务器吞吐量

 

附件与扩容

1、Channel 的 register 方法的第三个参数:附件

(1)可以向其中放入一个 Object 类型的对象

(2)该对象会与注册的 Channel 以及其对应 SelectionKey 绑定

2、可以从 SelectionKey 的 attachment(),获取到对应通道的附件

3、需要在 Accept 发生后,将 SocketChannel 注册到 Selector 中时,对每个通道添加一个 ByteBuffer 附件,让每个通道发生读事件时,都使用自己的缓冲区,避免与其他通道发生冲突

4、扩容

(1)当 Channel 中的数据大于缓冲区时,需要对缓冲区进行扩容操作

(2)思路:缓冲区调用 compact 方法后,position 与 capacity 相等,说明缓冲区已写满,但仍有数据未读取,此时创建新的缓冲区,其大小扩大为两倍,同时将旧缓冲区中的数据拷贝到新的缓冲区中,调用 SelectionKey 的 attach 方法,将新的缓冲区作为新的附件放入 SelectionKey 中

 

ByteBuffer 大小分配

1、每个 Channel 都需要记录可能被切分的消息,因为 ByteBuffer 不能被多个 Channel 共同使用,因此需要为每个 Channel 维护一个独立的 ByteBuffer

2、ByteBuffer 不能太大,因此需要设计大小可变的 ByteBuffer

3、分配思路

(1)分配一个较小的 Buffer,如果容量不够,再分配两倍大小的 Buffer,将原 Buffer 内容拷贝至新 Buffer,消息连续容易处理,但数据拷贝耗费性能

(2)多个数组组成 Buffer,一个数组容量不够,把剩余内容写入新数组,与(1)区别:消息存储不连续解析,但避免拷贝引起的性能损耗

 

Write 事件

1、服务器通过 Buffer 向通道中写入数据时,可能因为通道容量小于 Buffer 中的数据大小,导致无法一次性将 Buffer 中的数据全部写入到 Channel 中

(1)一次将 Buffer 中的数据全部写入,期间会等待写入状态就绪,写入量为 0,效率低,这时便需要分多次写入,等待期间执行其他操作

(2)底层:操作系统通过发送缓冲区,控制发送数据的大小

2、实现思路

(1)执行一次写操作,向将 Buffer 中的内容写入到 SocketChannel 中,然后判断 Buffer 中是否还有数据

(2)若 Buffer 中还有数据,则在原 interest set 上,加上 SelectionKey.OP_WRITE,同时将未写完的 Buffer 作为附件,一起放入到 SelectionKey 中

(3)等待期间执行其他操作,isWritable() 检测写入是否就绪

(4)若 Buffer 中没有数据,则移除 SelecionKey 中的 Buffer 附件,避免其占用过多内存,同时还需(使用 -)移除 SelectionKey.OP_WRITE

 

NIO 一般步骤

1、创建 Selector

2、创建 ServerSocketChannel,并绑定监听端口

3、设置通道为非阻塞模式

4、把通道注册到 Selector 上,监听连接事件

5、循环调用 Selector 的 select 方法,监测通道的就绪状况

6、调用 selectKeys 方法获取 selected-key set

7、遍历 selected-key set,判断就绪事件类型,channel() 获取对应通道,实现具体的业务操作

8、根据业务,决定是否需要再次注册监听事件,重复执行 3

posted @   半条咸鱼  阅读(192)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示