Java中的NIO详解Day06-Selector

快来打我* 2023-03-01 13:57 38阅读 0赞

Selector

  • 基本概念
  • Selector的创建
  • Selector注册通道
  • SelectionKey
      • interest集合
      • ready集合
      • Channel和Selector
      • 附加的对象
  • Selector选择通道
  • SelectedKeys
  • wakeup()
  • close()
  • Selector示例

基本概念

  • 选择器Selector:

    • Java NIO中能够检测一个或者多个NIO通道,并且能够获取到通道是否为诸如读写事件做好准备的组件
    • 一个单独的线程可以管理多个Channel,从而管理多个网络连接
  • 使用单个线程来处理多个Channel的优点:

    • 只需要更少的线程来处理通道

      • 事实上,可以只用一个线程处理所有的通道
      • 对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程之间都要占用一些内存资源
      • 因此,使用的线程越少越好

Selector的创建

  • 通过调用Selector.open() 方法创建一个Selector:

    Selector selector = Selector.open();

Selector注册通道

  • 为了将ChannelSelector配合使用,必须将channel注册到selector
  • 通过SelectorChannel.register() 方法来实现向Selector中注册通道:

    channel.configureBlocking(false);
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

  • Channel必须处于非阻塞模式下,才能与Selector一起使用

  • 因为FileChannel不能切换到非阻塞模式,所以不能将FileChannelSelector一起使用.而套接字通道都可以
  • register的第二个参数:

    • 是一个interest集合
    • 在通过Selector监听Channel时对什么事件感兴趣
  • Selector可以监听四种不同的类型: 通道触发了一个事件意味着该事件已经就绪

    • Connect: 连接就绪. 某个channel成功连接到另一个服务器
    • Accept: 接收就绪. 一个server socket channel准备好接收新进入的连接
    • Read: 读就绪. 一个有数据可读的通道
    • Write: 写就绪. 等待写数据的通道
  • 这四种事件使用SelectionKey的四个常量来表示:

    • SelectionKey.OP_CONNECT
    • SelectionKey.OP_ACCEPT
    • SelectionKey.OP_READ
    • SelectionKey.OP_WRITE
  • 如果Selector不止对一种事件感兴趣,可以使用位或操作符将常量连接起来:

    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE

SelectionKey

  • 当向Selector注册Channel,register() 方法会返回一个SelectionKey对象,这个对象中包含了Selector感 兴趣的属性:

    • interest集合
    • ready集合
    • Channel
    • Selector
    • 附加的对象

interest集合

  • interest集合: Selector感兴趣的事件集合. 可以通过SelectionKey读写interest集合

    int interestSet = selectionKey.interestOps();

    boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
    boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
    boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
    boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

  • 使用 “位于&” 操作interest集合与给定的SelectionKey常量,可以确定某个确定的事件是否在interest集合中

ready集合

  • ready集合是通道已经准备就绪操作的集合.在一次选择Selection之后,会首先访问这个ready集合
  • 访问ready集合:

    int readySet = selectionKey.readyOps();

  • 可以使用 “位或&” 操作ready集合与给定的SelectionKey常量,可以确定Channel中什么事件或者操作已经就绪

  • 也可以使用SelectionKey中的方法来检测Channel中什么事件或者操作已经就绪:

    selectionKey.isAcceptable();
    selectionKey.isConnectable();
    selectionKey.isReadable();
    selectionKey.isWritable();

Channel和Selector

  • 根据SelectionKey访问ChannelSelector:

    Channel channel = selectionKey.channel();
    Selector selector = selectionKey.selector();

附加的对象

  • 为了更加方便地识别某个给定的通道,可以将一个对象或者更多信息附加到SelectionKey

    • 可以附加一个与通道一起使用的Buffer或者是包含聚集数据的某个对象:

      selectionKey.attach(theObject);
      Object theObject = selectionKey.attachment();

    • 可以在用register() 方法向Selector注册Channel时附加对象:

      SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

Selector选择通道

  • 如果向Selector注册了一个或多个通道,就可以调用几个重载的select() 方法

    • select() 方法返回所感兴趣事件已经准备就绪的通道:

      • 连接
      • 接受
  • select() 方法:

    / select()阻塞到至少有一个通道在注册的事件上就绪 @return int 表示有多少个通道已经就绪 或者是 从上次调用select()方法后有多少通道变成就绪状态 */
    int select();

    / select(long timeout)阻塞到至少有一个通道在注册的事件上就绪,最长会阻塞timeout毫秒 @param timeout 最长会阻塞timeout毫秒 @return int 表示有多少个通道已经就绪 或者是 从上次调用select(long timeout)方法后有多少通道变成就绪状态 /
    int select(long timeout);

    / selectNow()不会阻塞,不管什么通道就绪都立刻返回 @return int 此方法执行非阻塞的选择操作,从前一次选择操作后,没有通道变成可选择的,直接返回0 */
    int selectNow();

SelectedKeys

  • 如果调用了select() 方法,并且返回值表明有一个或更多个通道就绪了.然后可以通过调用selectorselectedKeys() 方法,访问 “已选择键集selected key set” 中的就绪通道

    Set selectedKeys = selector.selectedKeys();

  • 当向Selector注册Channel,Channel.register() 方法会返回一个SelectionKey对象:

    • SelectionKey对象代表了注册到该Selector的通道
    • 可以通过SelectionKeyselectedKeySet() 方法访问这些对象
  • 可以通过遍历这个已选择的键集合来访问就绪的通道:

    Set selectedKeys = selector.selectedKeys();
    Iterator keyIterator = selectedKeys.iterator();
    while (keyIterator.hasNext()) {

    1. SelectionKey key = keyIterator.next();
    2. if (key.isAcceptable()) {
    3. // a connection was acceptable by a ServerSocketChannel
    4. }
    5. if (key.isConnectable()) {
    6. // a connection was established with a remote server
    7. }
    8. if (key.isReadable()) {
    9. // a channel is ready for reading
    10. }
    11. if (key.isWritable()) {
    12. // a channel is ready for writing
    13. }
    14. keyIterator.remove();

    }

  • 循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件

  • 每次迭代末尾调用keyIterator.remove():

    • Selector本身不会从已选择键集中移除SelectionKey实例
    • 必须在处理完通道时自己移除
    • 下次该通道变成就绪时 ,Selector会再次将SelectionKey实例放入已选择键集中
  • SelectionKey.channel() 方法返回的通道需要转型成为需要处理的类型: 比如ServerSocketChannel或者SocketChannel

wakeup()

  • 某个线程调用select() 方法后阻塞,即使没有通道已经就绪,也可以从select() 方法返回:

    • 只要让其余线程在第一个线程调用select() 方法的对象上调用Selector.wakeup() 方法
    • 阻塞在select() 方法上的线程会立即返回
  • 如果有其余线程调用了wakeup() 方法,但是当前没有线程阻塞在select() 方法上,下一个调用select() 方法的线程会立即返回

close()

  • 使用完毕Selector之后调用close() 方法会关闭该Selector. 并且注册到该Selector上的所有SelectionKey实例无效
  • Channel本身并不会关闭

Selector示例

  • 打开一个Selector
  • 注册一个初始化完成的通道到这个Selector
  • 持续监控这个Selector的四种事件:接受,连接,读,写.是否就绪

    Selector selector = Selector.open();
    channel.configureBlocking(false);
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
    while (true) {

    1. int readyChannels = selector.select();
    2. if (readyChannels == 0) {
    3. continue;
    4. }
    5. Set selectedKeys = selector.selectedKeys();
    6. Iterator keyIterator = selectedKeys.iterator();
    7. while (keyIterator.hasNext()) {
    8. SelectionKey key = keyIterator.next();
    9. if (key.isAcceptable()) {
    10. // 一个连接被ServerSocketChannel接受
    11. ...
    12. }
    13. if (key.isConnectable()) {
    14. // 一个连接被远程服务发布
    15. ...
    16. }
    17. if (key.isReadable()) {
    18. // 一个准备去读的channel
    19. ...
    20. }
    21. if (key.isWritable()) {
    22. // 一个准备去写的channel
    23. }
    24. keyIterator.remove();
    25. }

    }

发表评论

表情:
评论列表 (有 0 条评论,38人围观)

还没有评论,来说两句吧...

相关阅读