• NIO this.selector.select()


    过程梳理:

    1. SelectorImpl.lockAndDoSelect()
    2. windowsSelectorImpl.doSelect 正式开始轮询事件
    	 2.1 subSelector.poll(); 开始底层的轮询,获取就绪文件描述符
    	 2.2 this.updateSelectedKeys(); 将就绪的key加入到selectedKeys中,进入该方法
    		2.2.1 this.subSelector.processSelectedKeys(this.updateCount);在主线程调用poll之后,会获取到已就绪的文件描述符(包含可读、可写、异常)。通过调用processSelectedKeys将就绪的文件描述符对应的SelectorKey加入到selectedKeys中。这样我们外部就可以调用到所有就绪的SelectorKey进行遍历处理。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    SelectorImpl.lockAndDoSelect()

    在这里插入图片描述

    重点:最后执行SelectorImpl.doSelect,在windows平台由WindowsSelectorImpl实现

    WindowsSelectorImpl#doSelect

    在这里插入图片描述
    在这里插入图片描述
    一个个来分析

     protected int doSelect(long var1) throws IOException {
     //首先查看Selector中是否有Channel,没有就抛出异常
            if (this.channelArray == null) {
                throw new ClosedSelectorException();
            } else {
                this.timeout = var1;
                this.processDeregisterQueue();
                 设置中断器,实际调用的是AbstractSelector.this.wakeup();方法
        // 调用的是方法AbstractInterruptibleChannel.blockedOn(Interruptible);
                if (this.interruptTriggered) {
                    this.resetWakeupSocket();
                    return 0;
                } else {
                //调整辅助线程数量
                    this.adjustThreadsCount();
                    //调整主线程与辅助线程之间的运行关系
                    this.finishLock.reset();
                    this.startLock.startThreads();
    
                    try {
                        this.begin();
    
                        try {
                        //执行poll方法
                            this.subSelector.poll();
                        } catch (IOException var7) {
                            this.finishLock.setException(var7);
                        }
    
                        if (this.threads.size() > 0) {
                            this.finishLock.waitForHelperThreads();
                        }
                    } finally {
                        this.end();
                    }
    
                    this.finishLock.checkForException();
                    this.processDeregisterQueue();
                    int var3 = this.updateSelectedKeys();
                    this.resetWakeupSocket();
                    return var3;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    adjustThreadsCount

    从注释可以看到,该方法在NIO中channel注册或者注销之后,对辅助线程的数量进行调整。其中threads.size()为当前辅助线程的数量,threadsCount为需要的辅助线程的数量。如果当前的数量小于需要的数量时,创建新的辅助线程,以达到需要的数量。如果当前的数量大于需要的数量,则杀掉多余的线程。

    在这里插入图片描述

    辅助线程:一个线程只能处理1024个Channel,多了就要提供主线程以外的辅助线程,在向Selector注册Channel的时候根据条件会增加辅助线程,到selector.select时会进行判断。
    在这里插入图片描述

    processDeregisterQueue

    处理已注销的SelectionKey
    在这里插入图片描述

    updateSelectedKeys ——重点:发现事件的到来

    在这里插入图片描述

    SubSelector

    SubSelector

    poll ——与内核交互发现感兴趣的事件

    在这里插入图片描述

    processSelectedKeys —— 处理从内核找到的感兴趣事件

    updateSelectedKeys负责处理发生就绪事件的FD,将这些FD对应的选择键加入selectedKeys集合。客户端通过遍历selectedKeys集合即可处理各种事件。看源码

    updateSelectedKeys首先会调用processSelectedKeys处理主线程上的发生就绪事件的FD列表。然后迭代threads集合分别处理每个辅助线程上发生就绪事件的FD列表。看processSelectedKeys实现:
    processSelectedKeys实现很简单,分别处理readFds,writeFds,exceptFds三个数组中的FD,核心处理过程在processFDSet中实现
    在这里插入图片描述

    processFDSet

    以处理readFds为例

    参数解释:
    var1: this.updateCount,保存更新的数量
    在这里插入图片描述
    var3: this.readFds,可读文件描述符数组,这里存放的就是Channel的文件描述符,文件描述符第一个元素是数组的长度,之后是文件描述符的值
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

       private int processFDSet(long var1, int[] var3, int var4, boolean var5) {
       			//存储已经准备好的事件个数
                int var6 = 0;
    			//1.以可读文件事件为例,var3是文件描述符数组var3[0]表示的就是数组存储的文件描述符的长度
                for(int var7 = 1; var7 <= var3[0]; ++var7) {
                	//拿到该文件描述符的值fdVal
                    int var8 = var3[var7];
                    //2. 判断当前文件描述符是否是用于唤醒的文件描述
                    if (var8 == WindowsSelectorImpl.this.wakeupSourceFd) {
                        synchronized(WindowsSelectorImpl.this.interruptLock) {
                            WindowsSelectorImpl.this.interruptTriggered = true;
                        }
                    } else {
                    	//根据fdval拿到selector中的FdMap中的SelectionKeyImpl 
                        WindowsSelectorImpl.MapEntry var9 = WindowsSelectorImpl.this.fdMap.get(var8);
                        if (var9 != null) {
                        	//拿到SelectionKeyImpl 
                            SelectionKeyImpl var10 = var9.ski;
                            if (!var5 || !(var10.channel() instanceof SocketChannelImpl) || !WindowsSelectorImpl.this.discardUrgentData(var8)) {
                                if (WindowsSelectorImpl.this.selectedKeys.contains(var10)) {
                                    if (var9.clearedCount != var1) {
                                        if (var10.channel.translateAndSetReadyOps(var4, var10) && var9.updateCount != var1) {
                                            var9.updateCount = var1;
                                            ++var6;
                                        }
                                    } else if (var10.channel.translateAndUpdateReadyOps(var4, var10) && var9.updateCount != var1) {
                                        var9.updateCount = var1;
                                        ++var6;
                                    }
    
                                    var9.clearedCount = var1;
                                } else {
                                    if (var9.clearedCount != var1) {
                                    	//进行更新SelectionKeyImpl 的就绪事件
                                        var10.channel.translateAndSetReadyOps(var4, var10);
                                        //其实就是查看你感兴趣的事件是否已经是就绪事件,也就是你感兴趣的事件已经通过了验证可以进行后续处理了。
                                        if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
                                        	//将该selectionKeys加入到Selector的set数据结构中
                                            WindowsSelectorImpl.this.selectedKeys.add(var10);
                                            //
                                            var9.updateCount = var1;
                                            ++var6;
                                        }
                                    } else {
                                        var10.channel.translateAndUpdateReadyOps(var4, var10);
                                        if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
                                            WindowsSelectorImpl.this.selectedKeys.add(var10);
                                            var9.updateCount = var1;
                                            ++var6;
                                        }
                                    }
    
                                    var9.clearedCount = var1;
                                }
                            }
                        }
                    }
                }
    			//此时var6>0,表示有Channel感兴趣的事件经过验证可以进行后续处理了,
                return var6;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    在这里插入图片描述

    translateAndSetReadyOps

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    首先看入参var3,其实就是我们创建的第一个Channel,ServerSocketChannel对应的SelectionKeyImpl,它感兴趣的事件是读事件(因为它的interestOps为16,下面的代码中可以看出)
    在这里插入图片描述

    为什么是16,因为它感兴趣的事件是OP_ACCEPT
    在这里插入图片描述

      public boolean translateReadyOps(int var1, int var2, SelectionKeyImpl var3) {
      	//拿到该SelectionKeyImpl 中的感兴趣事件和已准备事件类型
            int var4 = var3.nioInterestOps(); #16
            int var5 = var3.nioReadyOps(); #0
            
            int var6 = var2;
            //Net.POLLNVAL表示套接字文件未打开,前面提到过这里的var1代表NET.POLLIN表示具有可读事件,一般这种常量被定义为00001,00010这种形式的比特,进行与运算其实就是比较二者是否相等。这里显然不相等
            if ((var1 & Net.POLLNVAL) != 0) {
                return false;
                //这一步的含义其实就是事件是否是Net.POLLERR、Net.POLLHUP其中一个它们一个00001,一个是00010,二者进行或运算00011,只要var1是其中的一个,进行与运算就不为0 。NET.POLLIN 768 相当于1100000000 也就是512+256
            } else if ((var1 & (Net.POLLERR | Net.POLLHUP)) != 0) {
                var3.nioReadyOps(var4);
                return (var4 & ~var5) != 0;
            } else {
            	//这里表示的含义:这是一个读事件的判断并且SelectionKeyImpl 就是对该读事件感兴趣,因为我们从上面看到SelectionKeyImpl 的interestOps为16,所以var4 & 16) != 0 是成立的
                if ((var1 & Net.POLLIN) != 0 && (var4 & 16) != 0) {
                	//var6 =16
                    var6 = var2 | 16;
                }
    			//关键一步:表示SelectionKeyImpl的读事件已就绪:要通知Channel:你感兴趣的读事件来了!
                var3.nioReadyOps(var6);
                return (var6 & ~var5) != 0; //这一步值得细说,SelectionKeyImpl通过比特位实现InterestOps和ReadyOps的存储,一个比特序列可以存储多个感兴趣的事件,就是比特位之间运算可能让人看得有些懵,没办法,省消耗比省脑子更好。这里只需要知道返回true。
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    2023-2024山东大学移动互联网开发期末回忆
    Java集合自定义字段排序
    2022-08-03
    [EFI]Surface Pro 4电脑 Hackintosh 黑苹果引导文件
    复现 MMDetection
    ubuntu下载conda
    P2605 [ZJOI2010]基站选址(线段树优化dp经典题)
    Intel汇编-EFLAGS条件分支
    AcWing-C/C++语法基础【合集2】
    交换机、IP地址、ARP协议
  • 原文地址:https://blog.csdn.net/qq_44587855/article/details/122383281