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进行遍历处理。




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


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

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



updateSelectedKeys负责处理发生就绪事件的FD,将这些FD对应的选择键加入selectedKeys集合。客户端通过遍历selectedKeys集合即可处理各种事件。看源码
updateSelectedKeys首先会调用processSelectedKeys处理主线程上的发生就绪事件的FD列表。然后迭代threads集合分别处理每个辅助线程上发生就绪事件的FD列表。看processSelectedKeys实现:
processSelectedKeys实现很简单,分别处理readFds,writeFds,exceptFds三个数组中的FD,核心处理过程在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;
}
}




首先看入参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。
}
}