深入理解NIO底层源码——进阶

前言

看这篇文章之前,建议先看我的前一遍博客
深入理解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的实现类,下面是继承图

image-20220217223414778

下面是该类的构造方法,主要就是绑定底层生成的服务端套接字

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

​ 先放继承图

在这里插入图片描述

SelectorSelectableChannel的多路复用器,它由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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值