前言
看这篇文章之前,建议先看我的前一遍博客
深入理解NIO底层源码——基础.
NIO快速开始
下面是一个nio服务端的简单demo
public class NIOTest {
private static Selector selector;
public static void main(String[] args) {
selector();
}
public static void accept(SelectionKey selectionKey) throws IOException {
System.out.println("--->有可以连接的了");
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
SocketChannel accept = channel.accept();
accept.configureBlocking(false);
accept.register(selector,SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024));
}
public static void readable(SelectionKey selectionKey) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buff = (ByteBuffer) selectionKey.attachment();
int readableByte = socketChannel.read(buff);
while (readableByte > 0){
while (buff.hasRemaining()){
System.out.println(String.valueOf(buff.get()));
}
System.out.println("---->");
buff.clear();
readableByte = socketChannel.read(buff);
}
}
public static void selector(){
ServerSocketChannel scc = null;
try {
//获取一个多路复用器
selector = Selector.open();
//获取一个服务端Channel
scc = ServerSocketChannel.open();
scc.bind(new InetSocketAddress(9001));
scc.configureBlocking(false);
//把服务端Channel注册进多路复用器里面,并设置要监听的事件
//该方法会创建一个SelectionKey作为Channel注册进Selector里的标志和作为Channel和Selector之间的桥梁
scc.register(selector, SelectionKey.OP_ACCEPT);
while (true){
//调用select方法阻塞,直到有事件就绪
selector.select();
//获取就绪Channel对应的selectionKey的Set集合的迭代器
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
//通过if判断该selectionKey就绪的是什么事件,然后交给对应的方法执行
if (selectionKey.isAcceptable()){
accept(selectionKey);
}else if (selectionKey.isReadable()){
readable(selectionKey);
}
//把selectionkey从就绪事件集合中去掉,表示已经处理了该事件
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (scc != null) {
try {
scc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
从Channel讲起
我们先看看Channel的继承图
Channel
Channel(通道)表示与例如硬件、文件、网络套接字、程序等实体的开放连接,能够对这些实体执行一个或多个IO操作,比如读和写。如果那个实体是网络套接字,那么Channel的相应的实现类就会封装了socket。它有下面两个方法
public interface Channel extends Closeable {
public boolean isOpen();
public void close() throws IOException;
}
可异步中断能力
InterruptibleChannel
实现了InterruptibleChannel的通道说明它是可以被异步关闭和打断的通道。
异步关闭管道:如果一个A线程在执行了一个InterruptibleChannel中的IO方法并阻塞,另一个线程执行该通道的close方法时,那么A线程就会收到一个AsynchronousCloseException异常。
异步中断:如果一个线程A同上被阻塞了,如果另一个线程调用了A的interupt方法,这个会导致该通道被关闭,并且A会接收到ClosedByInterruptException异常,而且A的interupt状态会被打上。
public interface InterruptibleChannel extends Channel {
public void close() throws IOException;
}
AbstractInterruptibleChannel
AbstractInterruptibleChannel实现了InterruptibleChannel接口,所以它就实现了具体的可异步中断能力。
这个类主要写了一个begin()和end()方法来实现这种能力,这两种方法需要这样使用,如下:如果要执行一个阻塞IO操作,就需要在其前后包裹begin()和end()方法,通过这种形式来达到异步中断的目的。
boolean completed = false;
try {
begin();
completed = ...; // 执行阻塞IO操作
return ...; // 返回结果
} finally {
end(completed); // end()方法放在finally里,确保会被执行
}
下面我们来分析一下begin()和end()源码
begin()
protected final void begin() {
if (interruptor == null) {
//首先当发现interruptor(打断器)为空时,就新建一个打断器
interruptor = new Interruptible() {
//下面实现Interruptible接口里面的唯一方法,就是实现异步中断的逻辑
public void interrupt(Thread target) {
//获取该通道的closeLock,确保只有一个线程让通道close
synchronized (closeLock) {
if (!open)
return;
open = false;
//把interrupted标记为被中断的线程。
interrupted = target;
try {
AbstractInterruptibleChannel.this.implCloseChannel();
} catch (IOException x) { }
}
}};
}
//注册打断者
blockedOn(interruptor);
Thread me = Thread.currentThread();
if (me.isInterrupted())
//如果发现当前线程被标记了打断,那么就调用interruptor里的打断方法
interruptor.interrupt(me);
}
我们追踪blockOn()方法,发现它实则是调用了Thread里的blockedOn方法,所以该方法的作用是把interruptor绑定到具体的线程上面。
public class Thread implements Runnable {
......
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
......
}
end()
我们回到AbstractInterruptibleChannel再来看看end()方法
protected final void end(boolean completed)
throws AsynchronousCloseException
{
//取消绑定当前线程的打断器(interruptor)
blockedOn(null);
Thread interrupted = this.interrupted;
//如果当前interrupted是当前线程(在begin那里被标记了),说明该线程是在阻塞的时候被打断
//抛出ClosedByInterruptException异常
if (interrupted != null && interrupted == Thread.currentThread()) {
interrupted = null;
throw new ClosedByInterruptException();
}
//如果通道被关闭了且IO方法没有执行完,则抛出AsynchronousCloseException异常
if (!completed && !open)
throw new AsynchronousCloseException();
}
其中begin()方法中的AbstractInterruptibleChannel.this.implCloseChannel(),是关闭通道的实际方法,不同的通道实现类都要去实现这个方法,具体可以追踪到SocketChannelImpl中下面的方法
protected void implCloseSelectableChannel() throws IOException {
synchronized (stateLock) {
isInputOpen = false;
isOutputOpen = false;
//把fd复制一份旧的,方便其它在此通道上的操作使用旧的fd
if (state != ST_KILLED)
nd.preClose(fd);
//唤醒在IO操作上阻塞的线程
if (readerThread != 0)
NativeThread.signal(readerThread);
if (writerThread != 0)
NativeThread.signal(writerThread);
//如果该通道没有注册在selector上,就要通过kill方法来关闭fd
//如果注册了在selector上,selector会在下一轮的select中,把这些fd关闭掉
if (!isRegistered())
kill();
}
}
多路复用能力
SelectableChannel
如果一个通道是SelectableChannel(继承了它),那么这个通道就具有多路复用的能力。SelectableChannel主要定义了一下几种方法。
//把多路复用通道注册进Selector,让其可以被多路复用
//返回的SelectionKey作为Selector和该通道的桥梁,通过SelectionKey可以找到通道和它注册进的Selector
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
//设置通道的阻塞模式,如果要使通道能够被多路复用,需要把阻塞模式设为false,也就是非阻塞模式
public abstract SelectableChannel configureBlocking(boolean block)
throws IOException;
//得到通道所注册的Selector之间关联的SelectionKey
public abstract SelectionKey keyFor(Selector sel);
AbstractSelectableChannel
AbstractSelectableChannel继承了SelectableChannel,而它实现了register等方法,所以实现了通道能被多路复用的能力。
下面是register()方法
public final SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
//如果找到了SelectionKey,那么说明该通道已经在该Selector中注册过
//所以直接更新SelectionKey的属性
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
//如果找不到SelectionKey,执行注册
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
其中的findKey()操作只不过是遍历数组,找到对应的SelectionKey而已
private SelectionKey findKey(Selector sel) {
synchronized (keyLock) {
if (keys == null)
return null;
for (int i = 0; i < keys.length; i++)
if ((keys[i] != null) && (keys[i].selector() == sel))
return keys[i];
return null;
}
}
真正的注册是调用了Selector方法,((AbstractSelector)sel).register(this, ops, att)、
在SelectorImpl中实现了这个方法
protected final SelectionKey register(AbstractSelectableChannel ch,
int ops,
Object attachment)
{
if (!(ch instanceof SelChImpl))
throw new IllegalSelectorException();
//新创建了一个SelectionKey,并且在构造方法中传入通道和this(当前Selctor)
//这表明这个Selectionkey将这两者绑定起来,建立了联系
SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
k.attach(attachment);
//implRegister在下面讲Selector的时候会讲
synchronized (publicKeys) {
implRegister(k);
}
//设置要监听的事件
k.interestOps(ops);
return k;
}
AbstractSelectableChannel还定义了removeKey()方法来取消注册,因为SelectionKey同时绑定了通道和Selector,所以单是通道这边把它移除掉是不行的,还要通知Selector那边让它也移除。
void removeKey(SelectionKey k) { // package-private
synchronized (keyLock) {
for (int i = 0; i < keys.length; i++)
if (keys[i] == k) {
keys[i] = null;
keyCount--;
}
//将SelectionKey标记为失效,使其在对应的Selector中的下一轮select中给移除掉。
((AbstractSelectionKey)k).invalidate();
}
}
通道支持网络通信能力
NetworkChannel
实现了NetworkChannel的通道是一个网络套接字的通道。这个接口里面定义了一些对网络套接字的操作。
//绑定本地地址
NetworkChannel bind(SocketAddress local) throws IOException;
//设置套接字,例如接收或发送缓冲区大小等
<T> NetworkChannel setOption(SocketOption<T> name, T value) throws IOException;
ServerSocketChannel
我们通过open方法来创建ServerSocketChannel
ServerSocketChannel.open()
然后到下面的方法,我们会发现会先获取一个provider
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
下面是SelectorProvider里的方法
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
//如果没有自定义provider的话,就会创建一个默认的
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
最终我们获取了WindowsSelectorProvider,而不同操作系统的provider实现类是不同的
public static SelectorProvider create() {
return new sun.nio.ch.WindowsSelectorProvider();
}
我们下面先来一张类继承图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EHkXKjkH-1645597346938)(深入理解NIO底层源码——进阶.assets/image-20220217223046765.png)]
而openServerSocketChannel被定义了在WindowsSelectorProvider的父类SelectorProviderImpl中
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
我们发现它创建了一个ServerSocketChannel的实现类,下面是继承图

下面是该类的构造方法,主要就是绑定底层生成的服务端套接字
ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
super(sp);
//获取一个底层ServerSocket(服务端套接字)的fd对象(文件描述符对象)
this.fd = Net.serverSocket(true);
//获取文件描述符,一个整形数
this.fdVal = IOUtil.fdVal(fd);
//设置通道状态
this.state = ST_INUSE;
}
SelectionKey
它是一个令牌表示一个SelectableChannel注册到Selector中。下面介绍它主要方法和属性
//SelectionKey会一直有效,除非调用了这个方法;关闭通道或Selector会触发调用这个方法
//cancel不会把SelectionKey直接从Selector中移除(要在下一次select中)
//该SelectionKey会被视为invalid
public abstract void cancel();
//通过下面的方法确认该SelectionKey是否有效
public abstract boolean isValid();
下面两个字段在SelectionKeyImpl中
//储存感兴趣的操作类别,Selector会在下一次select中测试这个操作是否准备好(比如可读或可写)
//这个值在一开始的register中就被输入
private volatile int interestOps;
//储存Selector在select中检测到已准备的操作类别,一开始为0,不被直接地更新
private int readyOps;
我们可以通过在SelectionKey中定义的下列方法,查看通道的情况
//查看通道是否已经准备可读
public final boolean isReadable() {
return (readyOps() & OP_READ) != 0;
}
//查看通道是否准备可写
public final boolean isWritable() {
return (readyOps() & OP_WRITE) != 0;
}
//查看通道是否完成功或失败套接字连接(注意:失败了也算是)
public final boolean isConnectable() {
return (readyOps() & OP_CONNECT) != 0;
}
//查看通道是否准备去接收新的套接字连接
public final boolean isAcceptable() {
return (readyOps() & OP_ACCEPT) != 0;
}
Selector
先放继承图
Selector是SelectableChannel的多路复用器,它由Selector.open()方法创建,如下,同样在一般情况下会使用默认的provider
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
追踪到WindowsSelectorProvider
public class WindowsSelectorProvider extends SelectorProviderImpl {
public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
}
再进入到WindowsSelectorProvider的构造器
WindowsSelectorImpl(SelectorProvider sp) throws IOException {
super(sp);
//一个可以操作c语言结构体的数组,里面会存放与操作系统poll操作相关的pollfd结构体
pollWrapper = new PollArrayWrapper(INIT_CAP);
//创建一个管道(用于唤醒的管道,下面会讲),在windows下,这个管道由两个通道(Channel)组成
//一个负责写,一个负责读
wakeupPipe = Pipe.open();
//获取管道中负责读的通道的fd
wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();
//禁用Nagel算法,从而让唤醒更迅速
SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();
(sink.sc).socket().setTcpNoDelay(true);
//这里同样获取了管道中负责写的那个通道的fd
wakeupSinkFd = ((SelChImpl)sink).getFDVal();
//这里往pollWrapper数组下标为0的地方放入wakeupSourceFd
pollWrapper.addWakeupSocket(wakeupSourceFd, 0);
}
我们先来看看PollArrayWrapper是什么
PollArrayWrapper
它是一个全局的本地poll数组,用来保存fd和事件掩码。在poll的时候会使用到这个数组。下面是它的构造方法
PollArrayWrapper(int newSize) {
//allocationSize指明了分配多少个SIZE_POLLFD的空间,而SIZE_POLLFD就是pollfd所占用的空间大小
int allocationSize = newSize * SIZE_POLLFD;
//从内存中分配空间
pollArray = new AllocatedNativeObject(allocationSize, true);
pollArrayAddress = pollArray.address();
this.size = newSize;
}
下面再来看看pollWrapper.addWakeupSocket(wakeupSourceFd, 0)
void addWakeupSocket(int fdVal, int index) {
putDescriptor(index, fdVal);
putEventOps(index, Net.POLLIN);
}
void putDescriptor(int i, int fd) {
//通过这个方法在pollArray数组的指定位置放入fd
//通过SIZE_POLLFD * i + FD_OFFSET来算出在pollArray这块内存中的偏移位置
pollArray.putInt(SIZE_POLLFD * i + FD_OFFSET, fd);
}
总结来说,pollArrayWrapper就是一块内存空间,用来存放pollfd结构体数组。
wakeupPipe的作用
首先先要讲一下什么时Pipe。Pipe(管道),它是由一对通道来实现,其中一个通道负责写数据,我们把它叫做sink channel;而另一个通道用来读数据,我们把他叫做source channel。如果往sink channel里面写数据,source channel就会读到数据,就像一条管道一样。下面是Pipe的主要构造,省略大量无关代码。
public abstract class Pipe {
......
/**
* A channel representing the readable end of a {@link Pipe}.
*
* @since 1.4
*/
public static abstract class SourceChannel
extends AbstractSelectableChannel
implements ReadableByteChannel, ScatteringByteChannel
{
......
}
/**
* A channel representing the writable end of a {@link Pipe}.
*
* @since 1.4
*/
public static abstract class SinkChannel
extends AbstractSelectableChannel
implements WritableByteChannel, GatheringByteChannel
{
......
}
......
}
wakeupPipe正如其名,它是用来当Selector处于阻塞状态的时候,用来唤醒Selector。例如当Selector在执行select时,如果监视的文件描述符都没有处于就绪,那么Selector就会被阻塞。那么我们如果唤醒Selector呢,很简单,我们只需要让其中一个文件描述符处于就绪状态,那么Selector就会被重新唤醒,从select中返回。由于在WindowsSelectorProvider的构造方法中(在上文),已经往pollArrayWrapper数组的第一位放入了wakeupSourceFd(Pipe)中用来读的通道的文件描述符,我们只要往wakeupSinkFd中写一些东西,那么wakeupSourceFd就会处于准备可读的就绪状态,从而使Selector被唤醒。
存放SelectionKey的SelectorImpl
Selector能存放SelectionKey,在SelectorImpl中定义了几个字段
//存放为某一种操作准备好了的SelectionKey
protected Set<SelectionKey> selectedKeys;
//存放所有SelectionKey(注册进来的)
protected HashSet<SelectionKey> keys;
//由于上面的两个字段是protected保护的,所以外部要访问需要由一个public修饰的视图
//该类中定义了两个这样的view
private Set<SelectionKey> publicKeys;
private Set<SelectionKey> publicSelectedKeys;
我们再来看看该类的构造方法,看它如何给上述字段赋值
protected SelectorImpl(SelectorProvider sp) {
super(sp);
keys = new HashSet<SelectionKey>();
selectedKeys = new HashSet<SelectionKey>();
if (Util.atBugLevel("1.4")) {
publicKeys = keys;
publicSelectedKeys = selectedKeys;
} else {
//赋值了一个不可修改的Set
publicKeys = Collections.unmodifiableSet(keys);
//赋值了不能增长的Set
publicSelectedKeys = Util.ungrowableSet(selectedKeys);
}
}
AbstractSelector:赋予Selector中断的能力
我们在上文讲述了AbstractInterruptibleChannel实现了begin()和end()方法让通道有了异步中断的能力。同样AbstractSelector也实现了begin()和end()方法,使Selector具备了中断能力,它需要被这样使用,如下
try {
begin();
// 在这里执行阻塞IO操作
...
} finally {
end();
}
下面看看它的begin()方法,里面大致与AbstractInterruptibleChannel形式相同
begin()
protected final void begin() {
if (interruptor == null) {
interruptor = new Interruptible() {
public void interrupt(Thread ignore) {
//这里调用了wakeup()方法使Selector方法唤醒
AbstractSelector.this.wakeup();
}};
}
AbstractInterruptibleChannel.blockedOn(interruptor);
Thread me = Thread.currentThread();
if (me.isInterrupted())
interruptor.interrupt(me);
}
下面是wakeup()方法,它在WindowsSelectorImpl被实现
wakeup()
public Selector wakeup() {
synchronized (interruptLock) {
if (!interruptTriggered) {
//我们可以看到主要是调用setWakeupSocket()使Selector唤醒
setWakeupSocket();
interruptTriggered = true;
}
}
return this;
}
//setWakeupSocket0()方法就是往wakeupSinkFd里面写点数据,这样wakeupSourceFd就会收到数据,从而使wakeupSourceFd处于已经准备读操作的状态,从而唤醒了Selector(上文详细讲了wakeupPipe的作用)
private void setWakeupSocket() {
setWakeupSocket0(wakeupSinkFd);
}
我们再来看看end()方法
end()
//这个方法很简单,就是把之前在begin()方法中注册在当前线程上的interruptor给取消掉
protected final void end() {
AbstractInterruptibleChannel.blockedOn(null);
}
Select()方法:多路复用的进入点
在SelectorImpl中具体实现了Select()方法
public int select(long timeout)
throws IOException
{
if (timeout < 0)
throw new IllegalArgumentException("Negative timeout");
return lockAndDoSelect((timeout == 0) ? -1 : timeout);
}
然后一直往下追踪(中途省略无关重要方法),真正执行select操作的是由WindowsSelectorImpl实现的**doSelect()**方法
doSelect
protected int doSelect(long timeout) throws IOException {
if (channelArray == null)
throw new ClosedSelectorException();
this.timeout = timeout;
//处理取消注册的队列(方法的详细解释在下文)
processDeregisterQueue();
//如果这个线程之前被打断(被唤醒过),那么就要重新设置wakeSocket,因为唤醒线程时是往wakeupSinkFd里面写东西,而wakeupSourceFd就会收到数据,从而处于准备操作的状态使线程唤醒,这个方法就是把wakeSourceFd里的东西清掉,达到重置的状态(下文会有该方法的详解)
if (interruptTriggered) {
resetWakeupSocket();
return 0;
}
//算出poll操作所需要的帮助线程的数量。如果有必要,线程会被在这里创建并且开始等待阻塞在startLock(下文有详解)
adjustThreadsCount();
finishLock.reset(); // reset finishLock
// Wakeup helper threads, waiting on startLock, so they start polling.
// Redundant threads will exit here after wakeup.
// 这个方法会唤醒阻塞在startLock上的帮助线程
startLock.startThreads();
// 在主线程做polling。主线程负责第一个MAX_SELECTABLE_FDS(1024)个在pollArray里的元素
try {
// 因为poll方法是阻塞的,所以在其前后包裹着begin()和end()方法
begin();
try {
//下面会讲poll方法
subSelector.poll();
} catch (IOException e) {
finishLock.setException(e); // Save this exception
}
// Main thread is out of poll(). Wakeup others and wait for them
// 如果有帮助线程,则等待其它线程完成
if (threads.size() > 0)
finishLock.waitForHelperThreads();
} finally {
end();
}
// 这个方法打印上文保存下来的异常
finishLock.checkForException();
// 因为在poll过程中是阻塞的,所以中途可能有SelectionKey取消注册了,所以再次处理一下
processDeregisterQueue();
// 处理准备就绪的SelectionKey
int updated = updateSelectedKeys();
// Done with poll(). Set wakeupSocket to nonsignaled for the next run.
// poll()完成了。把wakeupSocket设为无信号状态,为下一次poll做好准备
resetWakeupSocket();
return updated;
}
首先该方法调用了processDeregisterQueue()处理一些需要取消注册的SelectionKey,我们下面来看看这个方法
processDeregisterQueue()
//在canceledKey获取所有要取消的Selection,再通过具体的implDereg()来取消
void processDeregisterQueue() throws IOException {
// Precondition: Synchronized on this, keys, and selectedKeys
Set<SelectionKey> cks = cancelledKeys();
synchronized (cks) {
if (!cks.isEmpty()) {
Iterator<SelectionKey> i = cks.iterator();
while (i.hasNext()) {
SelectionKeyImpl ski = (SelectionKeyImpl)i.next();
try {
implDereg(ski);
} catch (SocketException se) {
throw new IOException("Error deregistering key", se);
} finally {
i.remove();
}
}
}
}
}
implDereg()
WindowsSelectorImpl实现了这个方法
protected void implDereg(SelectionKeyImpl ski) throws IOException{
int i = ski.getIndex();
assert (i >= 0);
synchronized (closeLock) {
if (i != totalChannels - 1) {
// 这里做了一件事,就是把channelArray的最后一个元素复制到要取消注册的SelectionKey对应的索引上,以此取消注册
SelectionKeyImpl endChannel = channelArray[totalChannels-1];
channelArray[i] = endChannel;
endChannel.setIndex(i);
// 这里也做了一件事,就是把pollWrapper数组最后一位上的文件描述符和和事件复制到i下
//这里之所以要同时操作channelArray和pollWrapper,是因为这两个数组是相互对应的
// pollWrapper储存着底层操作系统poll操作时需要监视的文件描述符和事件,而channelArray则储存着高层的SelectionKey
pollWrapper.replaceEntry(pollWrapper, totalChannels - 1,pollWrapper, i);
}
ski.setIndex(-1);
}
channelArray[totalChannels - 1] = null;
totalChannels--;
//如果现在总共监视的通道就是1024,那么线程总数就要减一;为什么会有多个线程呢,因为java的NIO中,执行一次select操作,一个线程只能维护1024个通道,如果多了的话就需要启动帮助线程来维护接下来的线程,以此类推。
if ( totalChannels != 1 && totalChannels % MAX_SELECTABLE_FDS == 1) {
totalChannels--;
threadsCount--; // The last thread has become redundant.
}
//移除fdMap中的key(fd)和SelectionKey;fdMap就是用来将fd映射到对应的SelectionKeyImpl
fdMap.remove(ski);
keys.remove(ski);
selectedKeys.remove(ski);
//执行父类AbstractSelector的方法,以移除父类keys数组中的SelectionKey
deregister(ski);
SelectableChannel selch = ski.channel();
//如果通道还没有关闭的话,就要去关闭通道
if (!selch.isOpen() && !selch.isRegistered())
((SelChImpl)selch).kill();
}
resetWakeupSocket()
// 让windows平台下的wakeup sokcet 变成无信号状态
private void resetWakeupSocket() {
synchronized (interruptLock) {
if (interruptTriggered == false)
return;
resetWakeupSocket0(wakeupSourceFd);
interruptTriggered = false;
}
}
adjustThreadsCount()
//伴随一些通道的注册和取消注册,帮助线程的数量可能会变化。需要调整这个数量。
private void adjustThreadsCount() {
if (threadsCount > threads.size()) {
// 因为需要的线程数大于当前的线程数,所以需要开启更多线程
for (int i = threads.size(); i < threadsCount; i++) {
//下面会分析SelectThread
SelectThread newThread = new SelectThread(i);
threads.add(newThread);
newThread.setDaemon(true);
//帮助线程启动后,会阻塞等待在一个叫startLock的东西上
newThread.start();
}
} else if (threadsCount < threads.size()) {
// 不需要那么多线程了,把一些线程从threadList中移除
for (int i = threads.size() - 1 ; i >= threadsCount; i--)
threads.remove(i).makeZombie();
}
}
SelectThread
SelectThread继承了Thread,所以我们分析一下run方法
// Represents a helper thread used for select.
// 代表一个用于select的帮助线程
private final class SelectThread extends Thread {
...
public void run() {
while (true) { // poll loop
// wait for the start of poll. If this thread has become
// redundant, then exit.
// 线程会阻塞在这里等待poll的开始,如果线程变得没用了,就会在这里退出
if (startLock.waitForStart(this)) {
subSelector.freeFDSetBuffer();
return;
}
// call poll()
try {
subSelector.poll(index);
} catch (IOException e) {
// Save this exception and let other threads finish.
finishLock.setException(e);
}
// notify main thread, that this thread has finished, and
// wakeup others, if this thread is the first to finish.
finishLock.threadFinished();
}
}
...
}
updateSelectedKeys()
// 更新对应通道的操作(ops),并且把准备好的keys放进ready队列里面
private int updateSelectedKeys() {
updateCount++;
int numKeysUpdated = 0;
// 更新主线程的SelectionKey
// processSelectedKeys()下面有讲
numKeysUpdated += subSelector.processSelectedKeys(updateCount);
// 更新帮助线程的SelectionKey
for (SelectThread t: threads) {
numKeysUpdated += t.subSelector.processSelectedKeys(updateCount);
}
return numKeysUpdated;
}
poll()
// 这个方法是给主线程的poll方法,帮助线程需要调poll(index)——需要传入一个参数
private int poll() throws IOException{ // poll for the main thread
// 这里调用一个本地方法,传入了readFds、writeFds等,如果有一个通道可读,那么它的fd就会被写进readFds数组里面
return poll0(pollWrapper.pollArrayAddress,
Math.min(totalChannels, MAX_SELECTABLE_FDS),
readFds, writeFds, exceptFds, timeout, fdsBuffer);
}
processSelectedKeys()
private int processSelectedKeys(long updateCount) {
int numKeysUpdated = 0;
//下面主要执行了processFDSet()方法并返回处理成功的个数
//下面会讲processFDSet()
numKeysUpdated += processFDSet(updateCount, readFds,
Net.POLLIN,
false);
numKeysUpdated += processFDSet(updateCount, writeFds,
Net.POLLCONN |
Net.POLLOUT,
false);
numKeysUpdated += processFDSet(updateCount, exceptFds,
Net.POLLIN |
Net.POLLCONN |
Net.POLLOUT,
true);
return numKeysUpdated;
}
processFDSet()
//这个方法就是根据传进来的fds数组和rOps(准备好的操作)来更新对应的SelectionKey
private int processFDSet(long updateCount, int[] fds, int rOps,
boolean isExceptFds)
{
int numKeysUpdated = 0;
for (int i = 1; i <= fds[0]; i++) {
int desc = fds[i];
if (desc == wakeupSourceFd) {
synchronized (interruptLock) {
interruptTriggered = true;
}
continue;
}
MapEntry me = fdMap.get(desc);
// If me is null, the key was deregistered in the previous
// processDeregisterQueue.
if (me == null)
continue;
SelectionKeyImpl sk = me.ski;
// The descriptor may be in the exceptfds set because there is
// OOB data queued to the socket. If there is OOB data then it
// is discarded and the key is not added to the selected set.
if (isExceptFds &&
(sk.channel() instanceof SocketChannelImpl) &&
discardUrgentData(desc))
{
continue;
}
if (selectedKeys.contains(sk)) { // Key in selected set
if (me.clearedCount != updateCount) {
// 这个translateAndSetReadyOps()就是根据传进来的rOps,将它翻译并为SelectionKey更新为正确的状态
// 比如传进来的Net.POLLIN就会被翻译为SelectionKey.OP_READ
if (sk.channel.translateAndSetReadyOps(rOps, sk) &&
(me.updateCount != updateCount)) {
me.updateCount = updateCount;
numKeysUpdated++;
}
} else { // The readyOps have been set; now add
if (sk.channel.translateAndUpdateReadyOps(rOps, sk) &&
(me.updateCount != updateCount)) {
me.updateCount = updateCount;
numKeysUpdated++;
}
}
me.clearedCount = updateCount;
} else { // Key is not in selected set yet
if (me.clearedCount != updateCount) {
sk.channel.translateAndSetReadyOps(rOps, sk);
if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0) {
// 如果Set里面还没有SelectionKey,那么放一个进去
selectedKeys.add(sk);
me.updateCount = updateCount;
numKeysUpdated++;
}
} else { // The readyOps have been set; now add
sk.channel.translateAndUpdateReadyOps(rOps, sk);
if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0) {
selectedKeys.add(sk);
me.updateCount = updateCount;
numKeysUpdated++;
}
}
me.clearedCount = updateCount;
}
}
return numKeysUpdated;
}