• 七、Java NIO 选择器


    所有文章

    https://www.cnblogs.com/lay2017/p/12901123.html

    正文

    Java NIO选择器(selector)是一个可以监控一个或多个Channel的组件,监控Channel是否可以read或者write操作。这是一种使得单线程可以管理多个Channel的方法,因此NIO可以使用更少的线程来管理更多的网络连接。

    为什么使用selector?

    使用单个线程处理多个Channel,可以让你节省线程资源。线程资源的创建、销毁、上下文切换是会增加消耗的。因此,我们需要使用更少的线程来做优化。使用选择器可以做到节省线程,如图

     创建一个选择器

    创建一个选择器通过调用open方法

    Selector selector = Selector.open();

    注册Channel到选择器当中

    为了在selector中选择Channel,你需要先将Channel注册到selector当中。通过register方法

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

    Channel必须是非阻塞模式,否则将不生效。

    注意到register方法的第二个参数,指明了你要监听该Channel的什么事件:

    1.Connect:连接成功

    2.Accept:ServerChannelSocket收到连接

    3.Read:可以读取

    4.Write:可以写入

    事件对应的参数分别是

    1.SelectionKey.OP_CONNECT

    2.SelectionKey.OP_ACCEPT

    3.SelectionKey.OP_READ

    4.SelectionKey.OP_WRITE

    如果你想监听多个事件,那么可以这样

    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 

    SelectionKey

    当你把一个Channel注册到Selector当中,register方法会返回一个SelectionKey对象。这个SelectionKey对象包含了一些东西,如下:

    1.interest set

    2.ready set

    3.channel

    4.selector

    5.attached object(可选)

    下面一一说明

    Interest Set

    interest set是一个事件集合,包含了selector你响应监听的事件。你可以通过以下方式来判断哪些事件是你关注的

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

    Ready Set

    ready set包含了哪些操作你可以执行,这可能会是你比较常用的,如

    int readySet = selectionKey.readyOps();

    你可以和interest set一样通过readySet值的位与来得出哪些操作可以执行,也可以直接调用方法

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

    Channel和Selector

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

    Attaching Object

    你可以把一个对象关联到SelectionKey。例如你可以把Channel对应的Buffer关联,或者把聚合数据对象关联

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

    你还可以在register的时候关联

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

    通过Selector选择Channel

    一旦你把Channel都注册到了Selector当中,你就可以调用select方法。select方法如

    1.int select():阻塞等待,直到至少一个Channel响应事件

    2.int select(long timeout):和select类似,但是增加了一个最大超时时间

    3.int selectNow():不阻塞,直接返回

    int返回值返回的是当前有多少个Channel有响应事件。注意,它返回的是你上一次调用select到当前时间有多少个。例如:

    你调用了一次select,返回1,表示一个Channel响应。然后你再调用select,又返回1,表示一个新的Channel响应。如果第一个Channel响应的时候,你没有做任何事情那么当前是有两个Channel响应,但是select只返回了1。

    selectedKeys

    select方法只知道当前有新的Channel响应事件,但不知道总共有多少个。可以通过调用selectedKeys来获取总共多少个

    Set<SelectionKey> selectedKeys = selector.selectedKeys(); 

    当你注册一个Channel到Selector,register方法会返回SelectionKey。其实,你可以通过selectedKeySet方法获取,然后,你可以迭代SelectionKey,去操作准备好的Channel

    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    
    Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
    
    while(keyIterator.hasNext()) {
        
        SelectionKey key = keyIterator.next();
    
        if(key.isAcceptable()) {
            // a connection was accepted by a ServerSocketChannel.
    
        } else if (key.isConnectable()) {
            // a connection was established with a remote server.
    
        } else if (key.isReadable()) {
            // a channel is ready for reading
    
        } else if (key.isWritable()) {
            // a channel is ready for writing
        }
    
        keyIterator.remove();
    }

    注意一下remove方法,Selector本身并没有移除SelectionKey的实现。你需要代替Selector来做这件事。当你处理完Channel以后,你需要调用remove来移除。而如果这个Channel又响应事件,它会重新添加SelectedKey。

    wakeUp

    当一个线程调用了select方法以后会被阻塞,那么如果你想停止这种阻塞你可以启动另外一个线程调用Selector.wakeup()方法,那么当前阻塞的线程都将不阻塞,并且新的线程调用select方法也不会阻塞

    close

    如果你想关闭Selector,那么就调用close方法。这个方法会使得所有SelectionKey失效。但是注意,Channel并没有关闭。

    Selector的完整示例

    下面是一个完整示例

    // 创建一个Selector
    Selector selector = Selector.open();
    // Channel设置为非阻塞
    channel.configureBlocking(false);
    // channel注册到Selector,监听read事件
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
    // 死循环
    while(true) {
      // 开始select操作
      int readyChannels = selector.selectNow();
      // 如果没有Channel响应,跳过
      if(readyChannels == 0) continue;
      // 有Channel响应,获取SelectionKey
      Set<SelectionKey> selectedKeys = selector.selectedKeys();
      // 获取迭代器
      Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
      // 遍历SelectionKey
      while(keyIterator.hasNext()) {
        // 获取下一个
        SelectionKey key = keyIterator.next();
        // 判断是哪种操作
        if(key.isAcceptable()) {
            // a connection was accepted by a ServerSocketChannel.
        } else if (key.isConnectable()) {
            // a connection was established with a remote server.
        } else if (key.isReadable()) {
            // a channel is ready for reading
        } else if (key.isWritable()) {
            // a channel is ready for writing
        }
        // 移除当前SelectionKey
        keyIterator.remove();
      }
    }
  • 相关阅读:
    矩阵特征值和椭圆长短轴的关系?
    Harris角点检测原理详解
    SIFT特征提取分析
    Sift中尺度空间、高斯金字塔、差分金字塔(DOG金字塔)、图像金字塔
    图像处理与计算机视觉的经典书籍
    霍夫变换
    熔断原理与实现Golang版
    如何利用go-zero在Go中快速实现JWT认证
    如何让服务在流量暴增的情况下保持稳定输出
    企业级RPC框架zRPC
  • 原文地址:https://www.cnblogs.com/lay2017/p/12906313.html
Copyright © 2020-2023  润新知