NIO源码解析

我们以demo的代码为顺序看看NIO是怎样运作的

public class NIOServer
{
    public static void main(String[] args) throws IOException
    {
        /**
         * 实例化选择器
         */
        Selector selector = Selector.open();
        /**
         * 开启服务端套接字管道
         */
        ServerSocketChannel server = ServerSocketChannel.open();
        /**
         * 绑定端口
         */
        server.socket().bind(new InetSocketAddress(8080));
        /**
         * 记得设置非阻塞模式
         */
        server.configureBlocking(false);

        /**
         * 将ServerSocketChannel注册进selector中,并将其设定为连接建立时事件
         * 只能设置为OP_ACCEPT,这是channel内部规定了的.同样SocketChannel不能设置为连接建立事件,只能设置为其他三样
         */
        //⭐⭐⭐⭐其中这个SelectionKey.OP_ACCEPT称为感兴趣的集合,作用是在该channel的网络IO完成后   事件分离器根据该channel的感兴趣集合 从而调用对应的事件处理器
        //⭐注意一下这奇怪的调用,将Selector放进了ServerSocketChannel中,而不是将ServerSocketChannel放进了Selector中
        server.register(selector, SelectionKey.OP_ACCEPT);

        for (; ; )
        {
            // 判断是否有事件准备好。如果没有任何通道准备好,这个方法会阻塞,直到至少有一个通道准备好。

            /**⭐⭐⭐⭐
             *一定要先调用此方法.这个方法的底层实现会调用内核的epoll_ctl(将要注册的sockets加入到一个称作eventpoll的等待队列中,
             * 没看错,register只是将要注册的事件放进了一个HashSet成员变量中,并没有真正注册进内核)
             * 和epoll_wait(内核中等待感兴趣事件的生成,导致线程阻塞),只有调用了这个方法才能够得到IO已完成的channel
             */
            int readyChannels = selector.select();
            if (readyChannels == 0)
            {
                continue;
            }
            /**
             * 要注意一定要先调用select()方法
             */
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> i = selectionKeys.iterator();
			//⭐下面的if判断就相当于事件分离器
            while (i.hasNext())
            {
                SelectionKey currentEvents = i.next();
                //通过迭代器将集合内的这个元素删除,以免重复迭代
                i.remove();
                //有客户端建立连接
                //⭐真正操作时是需要用线程池开启线程操作的
                if (currentEvents.isAcceptable())
                {
                    SocketChannel clientSocket = server.accept();
                    clientSocket.configureBlocking(false);
                    //建立了连接并不代表有数据可读
                    /**⭐⭐⭐⭐
                     * 请注意,调用socketChannel.connect()即可select到ServerSocketChannel,这就是已连接,但此时客户端还没有对SocketChannel进行写操作
                     * ,所以此时的SocketChannel还没有数据可读的,所以要将SocketChannel注册进Selector中等待客户端的IO完成
                     */
                    clientSocket.register(selector, SelectionKey.OP_READ);
                }
                //真正处理有数据的SocketChannel的代码块
                else if (currentEvents.isReadable())
                {
                    SocketChannel clientSocket = (SocketChannel) currentEvents.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    int nums = clientSocket.read(buffer);
                    
                    if (nums > 0)
                    {
                        System.out.println(new String(buffer.array()));
                        ByteBuffer returnBuffer = ByteBuffer.wrap("true".getBytes());
                        clientSocket.write(returnBuffer);
                    } else
                    {
                        clientSocket.close();
                    }
                }
            }
        }
    }
}

一、Selector.open()

// Selector.java
public static Selector open() throws IOException {
    // 这里的静态方法provider()会使用DefaultSelectorProvider.create():方法根据系统选择一个SelectorProvider,注意不是单例的
    // windows平台的话是WindowsSelectorProvider,
    // Linux平台是一个EPollSelectorProvider,这里主要分析Linux平台下的
    // 之后openSelector方法(一会看下面)会返回一个EPollSelectorImpl作为Selector的实现,我们一般提及的Selector就是它了
    return SelectorProvider.provider().openSelector();
}
 public AbstractSelector openSelector() throws IOException {
     //返回一个Selector实现
        return new EPollSelectorImpl(this);
}

这就是EPollSelectorImpl的类结构,接下来要注意各个构造方法的调用.

class EPollSelectorImpl
    extends SelectorImpl
{

    // File descriptors used for interrupt
    protected int fd0;
    protected int fd1;

    // The poll object
    EPollArrayWrapper pollWrapper;

    // Maps from file descriptors to keys
    private Map<Integer,SelectionKeyImpl> fdToKey;

    // True if this Selector has been closed
    private volatile boolean closed = false;

    // Lock for interrupt triggering and clearing
    private final Object interruptLock = new Object();
    private boolean interruptTriggered = false;

    /**
     * Package private constructor called by factory method in
     * the abstract superclass Selector.
     */
    EPollSelectorImpl(SelectorProvider sp) throws IOException {
        super(sp);
        long pipeFds = IOUtil.makePipe(false);
        //一些文件描述符的初始化
        fd0 = (int) (pipeFds >>> 32);
        fd1 = (int) pipeFds;
        //创建EPollArrayWrapper实例,该实例封装了EPOLL的JNI调用
        //创建实例过程中会顺带调用epoll_create创建一个eventpoll对象
        pollWrapper = new EPollArrayWrapper();
        //初始化用作中断的文件描述符
        pollWrapper.initInterrupt(fd0, fd1);
        fdToKey = new HashMap<>();
    }
}

//---------------------------------------------------------------------------------------
public abstract class SelectorImpl
    extends AbstractSelector
{

    //⭐存放已经IO完成的SelectionKey
    protected Set<SelectionKey> selectedKeys;

    //⭐存放已注册进Selector的SelectionKey
    protected HashSet<SelectionKey> keys;

    //⭐上面两个key是NIO框架内部使用的,下面这两个就是用户可以调用selector.selectedKeys();
    //获取的key只是下面的两个成员返回给用户后不允许用户代码对其做某些修改
    private Set<SelectionKey> publicKeys;             // Immutable
    private Set<SelectionKey> publicSelectedKeys;     // Removal allowed, but not addition

    protected SelectorImpl(SelectorProvider sp) {
        super(sp);
        //初始化存放SelectionKey的两个set
        keys = new HashSet<SelectionKey>();
        selectedKeys = new HashSet<SelectionKey>();
        if (Util.atBugLevel("1.4")) {
            publicKeys = keys;
            publicSelectedKeys = selectedKeys;
        } else {
            //注意publicKeys和keys内部指向同一个对象
            publicKeys = Collections.unmodifiableSet(keys);
            //注意publicSelectedKeys和selectedKeys指向同一个对象
            publicSelectedKeys = Util.ungrowableSet(selectedKeys);
        }
    }
}

//---------------------------------------------------------------------------------------

public abstract class AbstractSelector
    extends Selector
{ 		//将创建Selector实例的SelectorProvider放进Selector成员中
		protected AbstractSelector(SelectorProvider provider) {
        this.provider = provider;
    }
}

这就是open()方法,其实并没有干什么实事,主要还是各个成员变量的初始化

二、server.register()

//SelectableChannel.java,是ServerSocketChannel的父类
public final SelectionKey register(Selector sel, int ops)
        throws ClosedChannelException
    {
    	//会调用实现类的register()方法
        return register(sel, ops, null);
    }
public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;

//---------------------------------------------------------------------------------------

//AbstractSelectableChannel.java,是ServerSocketChannel的父类,SelectableChannel的子类,它实现了register()方法
public abstract class AbstractSelectableChannel
    extends SelectableChannel
{
	// Keys that have been created by registering this channel with selectors.
    // They are saved because if this channel is closed the keys must be
    // deregistered.  Protected by keyLock.
    //意思就是当用户要关闭channel时,AbstractSelectableChannel.close()方法也要将该channel与Selector之间的关联SelectionKey给取消掉.等下就将channel.close()
//注意processDeregisterQueue()方法,在SelectorImpl中
    private SelectionKey[] keys = null;

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 k = findKey(sel);
           →→→{
        		synchronized (keyLock) 
                {
            		if (keys == null)
                		return null;
            		for (int i = 0; i < keys.length; i++)
                        //⭐如果发现之前channel已经关联过该Selector了,那么就使用已经关联过的SelectionKey
                        //为什么要使用已关联过的SelectionKey呢?要明确的是,当一个在Selector的channel完成网络IO时, 一个Reactor模型 的事件分离器只能将该事件分配给其中一个事件处理器,因为对于一个Selector来说,一个IO事件总不能是又可读又可写、又是可连接的吧(这是由epoll_ctl的event参数决定的,只能监听一种事件)
                        /*
                        *就譬如下面这种写法,
                        *clientSocket.register(selector, SelectionKey.OP_READ);
                    	*clientSocket.register(selector, SelectionKey.OP_WRITE);
                        *在这个方法中,SelectionKey.OP_WRITE就会覆盖掉SelectionKey.OP_READ这个                         *感兴趣集合
                        */
                        //如果实在想一个IO事件调用多个事件处理器的话,那就只能创建多个Reactor模型,也就是多个Selector了
                		if ((keys[i] != null) && (keys[i].selector() == sel))
                    		return keys[i];
            		return null;
        		}
    		 }
            if (k != null) {
                k.interestOps(ops);
                k.attach(att);
            }
            
            if (k == null) {
             //来到这里则说明之前没有创建过该 channel与Selector 的关联,则创建一个SelectionKey
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                 //看这里,变成了 selector.register() ,这里才是真正将channel注册进Selector的地方
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    //往AbstractSelectableChannel.keys[]中添加一个SelectionKey,记录该关联
                    addKey(k);
                }
            }
            return k;
        }
    }
}

上面的代码都在 Channel的相关类中,其流程也就是维护该channelSelector的关系(所以才会出现这个奇怪的调用)。

接下来来到实现了AbstractSelectorregister()SelectorImpl

//SelectorImpl.java
  protected final SelectionKey register(AbstractSelectableChannel ch,
                                          int ops,
                                          Object attachment)
    {
        if (!(ch instanceof SelChImpl))
            throw new IllegalSelectorException();
      //创建一个SelectionKeyImpl实例,并将对应的channel与selector关系放入其中
        SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
      //⭐添加需要传递给事件处理器的对象,其用法就是在注册期间将对象放入该IO事件,然后当该事件IO完成交由事件处理器处理时,再将该对象取出
        k.attach(attachment);
        synchronized (publicKeys) {
            //子类实现的方法
            implRegister(k);
        }
      //为SelectionKey设置感兴趣事件
        k.interestOps(ops);
        return k;
    }
//---------------------------------------------------------------------------------------
//EpollSelectorImpl.java
 protected void implRegister(SelectionKeyImpl ski) {
        if (closed)
            throw new ClosedSelectorException();
        SelChImpl ch = ski.channel;
        int fd = Integer.valueOf(ch.getFDVal());
     //⭐向Map中存放文件描述符与SelectionKey的映射,只需要知道每个channel都被看成一个文件,而每个channel都会有一个独特的文件描述符fd(可以的话讲讲channel初始化)
        fdToKey.put(fd, ski);
     //⭐将文件描述符加入EPollArrayWrapper实例,注意:add()方法只是将fd记录到了EPollArrayWrapper.eventsLow[]中,甚至连感兴趣的事件event都没有设置(默认为IO_READ),并没有将该fd注册进操作系统epoll
        pollWrapper.add(fd);
     	//往AbstractSelectableChannel.keys[]中添加一个SelectionKey,记录该关联
        keys.add(ski);
    }

三、EPollArrayWrapper

接下来我们先初窥一下EPollArrayWrapper实例。

在此之前要先知道什么是fd,下面是引用https://blog.csdn.net/cywosp/article/details/38965239的一段话

在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。

我们只需要知道Linux的Socket和epoll都是一个文件,并且都要通过文件描述符fd来访问就可以了。

class EPollArrayWrapper {

    // EPOLL_EVENTS(感兴趣事件),是epoll_ctl方法的第四个参数,只有initInterrupt()会使用到这个成员,在EPollSelectorImpl初始化时会调用
    private static final int EPOLLIN      = 1;
    // Special value to indicate that an update should be ignored
    //当某channel被删除时,就会将eventsLow[channel.fd]置KILLED
    private static final byte  KILLED = (byte)-1;
	//而其它的EPOLL_EVENTS就是这四个事件了
    /*
    public static final int OP_READ = 1;
    public static final int OP_WRITE = 4;
    public static final int OP_CONNECT = 8;
    public static final int OP_ACCEPT = 16;
    */
    //注意一个细节:在调用SelectionKeyImpl.intrestOps()方法修改感兴趣事件时,如果该SelectionKey管理的是ServersocketChannel,那么方法内调用的channel.translateAndSetInterestOps()就会将ops从16改为1,之后调用epoll_ctl时用的参数就是1(并无大碍,因为ServerSocketChannel只接收ACCEPT事件,ops不用代表READ事件)
//---------------------------------------------------------------------------------------   
    // Used by release and updateRegistrations to track whether a file
    // descriptor is registered with epoll.
    //BitSet其实就是BitMap,该成员用于记录fd在epoll中的存在性,该位图的更新和epoll内fd的更新是同步的
    private final BitSet registered = new BitSet(); 
//---------------------------------------------------------------------------------------    
    //⭐存放 某channel的感兴趣事件,如将一个ServSocketChannel注册进Selector时,eventsLow[channel.fd]就会存放OP_ACCEPT(8)这个事件
    private final byte[] eventsLow = new byte[MAX_UPDATE_ARRAY_SIZE];
//---------------------------------------------------------------------------------------	    
    // number of file descriptors with registration changes pending
    //记录更新过事件的fd数目,被用作updateDescriptors的下标指针
    private int updateCount;
    // file descriptors with registration changes pending
    //用作存放 改变过事件类型的fd
    private int[] updateDescriptors = new int[INITIAL_PENDING_UPDATE_SIZE];
//--------------------------------------------------------------------------------------- 
    // opcodes
    //用作epoll_ctl方法的第二个参数,指要对fd和events进行的操作如EPOLL_CTL_ADD就是向操作系统注册要监听的事件类型和 监听channel 的文件描述符
    private static final int EPOLL_CTL_ADD      = 1;
    private static final int EPOLL_CTL_DEL      = 2;
    private static final int EPOLL_CTL_MOD      = 3;
//--------------------------------------------------------------------------------------	
    //由 epoll_create 生成的epoll专用的文件描述符
    private final int epfd;
	
//---------------------------------------------------------------------------------------
        
     //⭐JVM底层操作的一个对象,epoll_wait方法会将网络IO完成的fd信息记录在这个对象中,可将该对象看成数组
    private final AllocatedNativeObject pollArray;
     //记录某次epoll_wait返回时IO完成的fd个数
    int updated;
//---------------------------------------------------------------------------------------    
    //该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。
	private native int epollCreate();
    //该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
    /*
    参数:
		epfd:由 epoll_create 生成的epoll专用的文件描述符;
		op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
		fd:关联的文件描述符,在这里指channel的fd
		events:感兴趣的事件,如OP_READ等
    */
    private native void epollCtl(int epfd, int opcode, int fd, int events);
    //epoll_ctl可以操作epoll_create创建的epoll,如将socket句柄加入到epoll中让其监控,或把epoll正在监控的某个socket句柄移出epoll。原理留到看epoll时才讲.
    private native int epollWait(long pollAddress, int numfds, long timeout,
                                 int epfd) throws IOException;
    
	 void initInterrupt(int fd0, int fd1) {
        outgoingInterruptFD = fd1;
        incomingInterruptFD = fd0;
        epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);
    }
	   /**
     * Add a file descriptor
     */
    //上面讲到的pollWrapper.add(fd)调用的就是这个方法
    void add(int fd) {
        // force the initial update events to 0 as it may be KILLED by a
        // previous registration.
        synchronized (updateLock) {
            assert !registered.get(fd);
            //强制初始化事件为0,也即OP_READ,理由看上面英文解释,我不是很理解
            //不过在这之后还是会调用setInterest()为其设置的
            setUpdateEvents(fd, (byte)0, true);
        }
    }
	 private void setUpdateEvents(int fd, byte events, boolean force) {
        if (fd < MAX_UPDATE_ARRAY_SIZE) {
            if ((eventsLow[fd] != KILLED) || force) {
                //修改eventsLow数组
                eventsLow[fd] = events;
            }
        } else {
            Integer key = Integer.valueOf(fd);
            if (!isEventsHighKilled(key) || force) {
                eventsHigh.put(key, Byte.valueOf(events));
            }
        }
    }
    /**
     * Remove a file descriptor
     */
    //该方法将会在processDeregisterQueue()中调用,目的是在epoll_wait之前删除已关闭的channel的fd(看到doSelect()就懂了)
    void remove(int fd) {
        synchronized (updateLock) {
            // kill pending and future update for this file descriptor
            //将该channel的fd对应的感兴趣时间置为KILLED
            setUpdateEvents(fd, KILLED, false);

            // remove from epoll
            if (registered.get(fd)) {
                //删除epoll中监听的fd
                epollCtl(epfd, EPOLL_CTL_DEL, fd, 0);
                //同时在Java中记录该fd已不存在
                registered.clear(fd);
            }
        }
    }
    //更新某fd的感兴趣的事件,⭐selectionKey.interestOps()方法最终就会来到这里
    void setInterest(int fd, int mask) {
        synchronized (updateLock) {
            // record the file descriptor and events
            int oldCapacity = updateDescriptors.length;
            if (updateCount == oldCapacity) {
                int newCapacity = oldCapacity + INITIAL_PENDING_UPDATE_SIZE;
                int[] newDescriptors = new int[newCapacity];
                System.arraycopy(updateDescriptors, 0, newDescriptors, 0, oldCapacity);
                updateDescriptors = newDescriptors;
            }
            //将修改过的fd记录进updateDescriptors[]
            updateDescriptors[updateCount++] = fd;

            // events are stored as bytes for efficiency reasons
            byte b = (byte)mask;
            assert (b == mask) && (b != KILLED);
            //同时修改eventsLow[]
            setUpdateEvents(fd, b, false);
        }
    }
   

该类除了封装操作系统的epoll调用外,最主要的功能还是对fd→event关联的管理.

四、channel.close()

在看最重点的select()之前,先看看channel是怎么关闭的

public final void close() throws IOException {
        synchronized (closeLock) {
            if (!open)
                return;
            open = false;
            implCloseChannel();
        }
    }
//AbstractSelectableChannel.java
protected final void implCloseChannel() throws IOException {
        implCloseSelectableChannel();
        synchronized (keyLock) {
            int count = (keys == null) ? 0 : keys.length;
            //获得上面所说的channel记录的SelectionKey,逐个调用其cancel方法
            for (int i = 0; i < count; i++) {
                SelectionKey k = keys[i];
                if (k != null)
                    k.cancel();
            }
        }
    }
//AbstractSelectionKey.java
   public final void cancel() {
        
        synchronized (this) {
            if (valid) {
                valid = false;
                //又转去调用Selector的cancel
                ((AbstractSelector)selector()).cancel(this);
            }
        }
    }
//AbstractSelector.java
    void cancel(SelectionKey k) {                       
        synchronized (cancelledKeys) {
            //最终就是在将要删除的SelectionKey记录到cancelledKeys成员中
            cancelledKeys.add(k);
        }
    }

可以看到,channel.close()并没有对epoll对象操作,而是使用了一个cancelledKeys成员记录下这个SelectionKey。其实际的对epoll对象的rbr更新是在select()中。

五、selector.select()

//SelectorImpl.java
public int select() throws IOException {
        return select(0);
    }
public int select(long timeout)
        throws IOException
    {
        if (timeout < 0)
            throw new IllegalArgumentException("Negative timeout");
        return lockAndDoSelect((timeout == 0) ? -1 : timeout);
    }
  private int lockAndDoSelect(long timeout) throws IOException {
        synchronized (this) {
            if (!isOpen())
                throw new ClosedSelectorException();
            synchronized (publicKeys) {
                synchronized (publicSelectedKeys) {
                    return doSelect(timeout);
                }
            }
        }
    }

select()的核心doSelect()

//EpollSelectorImpl.java 
protected int doSelect(long timeout) throws IOException {
        if (closed)
            throw new ClosedSelectorException();
     //注意看这个方法,该方法中的implDereg(ski)会将已取消的SelectionKeyImpl,在pollWrapper.remove(fd)中真正取消一次(只是将该fd置为EPOLL_CTL_DEL),然后在doSelect()的epoll()中的updateRegistrations()方法将这些新增取消操作调用epoll_ctl更新一遍,也就是添加channel的会添加,取消channel的会删除fd
    	//先更新一下epoll,在此之前调用了channel.cancel()的channel将会在这个方法中 把此改动同步到epoll中
        processDeregisterQueue();
        try {
            begin();
            //调用epoll_wait(),监听注册进epoll的fd
            pollWrapper.poll(timeout);
        } finally {
            end();
        }
    //再删除一遍取消的channel
        processDeregisterQueue();
    	//更新SelectedKeys.将fd(channel)对应的SelectionKey放入成员变量selectedKeys(也是publicSelectedKeys)中
        int numKeysUpdated = updateSelectedKeys();
    	//跳过
        if (pollWrapper.interrupted()) {
            // Clear the wakeup pipe
            pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
            synchronized (interruptLock) {
                pollWrapper.clearInterrupted();
                IOUtil.drain(fd0);
                interruptTriggered = false;
            }
        }
        return numKeysUpdated;
    }
①、processDeregisterQueue()
//SelectorImpl.java
void processDeregisterQueue() throws IOException {
        //获得已取消的SelectionKey,如上面所说的调用socketChannel.cancel()时,就会将取消的SelectionKey放入AbstractSelector的cancelledKeys成员中
        Set<SelectionKey> cks = cancelledKeys();{
            return cancelledKeys;
        }
        synchronized (cks) {
            if (!cks.isEmpty()) {
                Iterator<SelectionKey> i = cks.iterator();
                while (i.hasNext()) {
                    SelectionKeyImpl ski = (SelectionKeyImpl)i.next();
                    try {
                        //在这里与EPollArrayWrapper打交道
                        implDereg(ski);
                    } catch (SocketException se) {
                        throw new IOException("Error deregistering key", se);
                    } finally {
                        //返回的直接是成员变量引用,这里删除之后AbstractSelector的cancelledKeys成员也就删除了
                        i.remove();
                    }
                }
            }
        }
    }
//SelectorImpl.java
protected void implDereg(SelectionKeyImpl ski) throws IOException {
        assert (ski.getIndex() >= 0);
        SelChImpl ch = ski.channel;
        int fd = ch.getFDVal();
    //删除该fd→SelectionKey映射
        fdToKey.remove(Integer.valueOf(fd));
    //⭐调用epollCtl(EPOLL_CTL_DEL)将该fd(channel)从epoll中删除了
        pollWrapper.remove(fd);
        ski.setIndex(-1);
        keys.remove(ski);
    //从已IO完成的set中删除该selectionKey
        selectedKeys.remove(ski);
        deregister((AbstractSelectionKey)ski);
        SelectableChannel selch = ski.channel();
        if (!selch.isOpen() && !selch.isRegistered())
            ((SelChImpl)selch).kill();
    }
②、pollWrapper.poll()

又回到了EPollArrayWrapper

//EPollArrayWrapper.java
int poll(long timeout) throws IOException {
        updateRegistrations();
    //监听注册进epoll的fd,返回IO完成的fd个数,并赋值到updated成员中
        updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
        for (int i=0; i<updated; i++) {
            if (getDescriptor(i) == incomingInterruptFD) {
                interruptedIndex = i;
                interrupted = true;
                break;
            }
        }
        return updated;
    }
//更新新注册的channel,上面的方法是向epoll删除已取消的channel,而这里就是向epoll更新新注册的channel
private void updateRegistrations() {
        synchronized (updateLock) {
            int j = 0;
            //向epoll更新所有在updateDescriptors的fd
            while (j < updateCount) {
                //将改变过 事件类型的fd取出
                int fd = updateDescriptors[j];
                //从eventsLow[]成员中取出 改变过 事件类型的fd对应的事件类型
                short events = getUpdateEvents(fd);
                boolean isRegistered = registered.get(fd);
                int opcode = 0;

                if (events != KILLED) {
                    //根据registered的存在性判断决定是向epoll中添加fd还是修改fd的events
                    //在介绍EPollArrayWrapper时曾说过,这个registered会与epoll中的fd存在性保持同步,用处就是在这里.如果epoll中已存在fd,则修改其events;而如果不存在,就添加fd→events以保持fd在epoll中的唯一性
                    if (isRegistered) {
                        //⭐注意看这里,要更新的events=0就意味着想从epoll中删除该fd,也即如果要取消在epoll中的clientSocket除了调用clientSocket.cancel()还可以调用selectionkey.interestOps(0);
                        opcode = (events != 0) ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
                    } else {
                        opcode = (events != 0) ? EPOLL_CTL_ADD : 0;
                    }
                    if (opcode != 0) {
                        //向epoll注册该channel
                        epollCtl(epfd, opcode, fd, events);
                        //同步registered成员
                        if (opcode == EPOLL_CTL_ADD) {
                            registered.set(fd);
                        } else if (opcode == EPOLL_CTL_DEL) {
                            registered.clear(fd);
                        }
                    }
                }
                j++;
            }
            updateCount = 0;
        }
    }
③、updateSelectedKeys()

方法来到这里,就说明epoll已经监听到有channel的网络IO已完成,此时就开始进行selectedKeys的更新了

//EpollSelectorImpl.java
private int updateSelectedKeys() {
    	//获取本次epoll返回时IO完成的fd个数
        int entries = pollWrapper.updated;
        int numKeysUpdated = 0;
        for (int i=0; i<entries; i++) {
            //⭐这个方法正是从EpollArrayWrapper的pollArray成员中获取IO完成的fd
            int nextFD = pollWrapper.getDescriptor(i);
            {
                    int offset = SIZE_EPOLLEVENT * i + FD_OFFSET;
        			return pollArray.getInt(offset);
            }
            //从fdToKey映射中得到SelectionKey
            SelectionKeyImpl ski = fdToKey.get(Integer.valueOf(nextFD));
            // ski is null in the case of an interrupt
            if (ski != null) {
                
                //曾在EPollArrayWrapper中提到过,OP_ACCEPT会经过一次转换才会变成真正放入epoll_ctl的events参数(其实OP_READ、OP_WRITE也会经过转换)
            	//这里从pollArray成员中获取的就是注册进epoll的events值
                int rOps = pollWrapper.getEventOps(i);
                //如果selectedKeys中已经存在这个SelectionKey,则说明是代码出现了读取完socketChannel的数据之后没有调用其close()导致的,调用channel.close(),就会在processDeregisterQueue()中将该selectionKey从selectedKeys中移除
                if (selectedKeys.contains(ski)) {
                    if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
                        numKeysUpdated++;
                    }
                } else {
                    //转换epoll的events到channel定义的events
                    ski.channel.translateAndSetReadyOps(rOps, ski);
                    //个人认为这个判断语句和sk.nioReadyOps()==sk.nioInterestOps()等价
                    if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
                        //⭐往成员selectedKeys中添加SelectionKey,在这之后调用selector.selectedKeys()就会返回这个成员
                        selectedKeys.add(ski);
                        numKeysUpdated++;
                    }
                }
            }
        }
        return numKeysUpdated;
    }
//该接口方法定义在SelChImpl.java中,有多种不同的实现,且最关心的ServerSocketChannel和SocketChannel两者的实现不同
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk);
//-------------------------------ServerSocketChannel实现----------------------------------
    
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
        return translateReadyOps(ops, 0, sk);
    }
 public boolean translateReadyOps(int ops, int initialOps,
                                     SelectionKeyImpl sk) {
     	//epoll层面的感兴趣事件ops是1
     	//取出Selectionkey层面该channel感兴趣的事件,一般是16
        int intOps = sk.nioInterestOps(); // Do this just once, it synchronizes
        int oldOps = sk.nioReadyOps();
     	//0
        int newOps = initialOps;

        if ((ops & Net.POLLNVAL) != 0) {
            // This should only happen if this channel is pre-closed while a
            // selection operation is in progress
            // ## Throw an error if this channel has not been pre-closed
            return false;
        }

        if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) {
            newOps = intOps;
            sk.nioReadyOps(newOps);
            return (newOps & ~oldOps) != 0;
        }

        if (((ops & Net.POLLIN) != 0) &&
            ((intOps & SelectionKey.OP_ACCEPT) != 0))
            //一般都会进来这里,而newOps将会等于SelectionKey.OP_ACCEPT
                newOps |= SelectionKey.OP_ACCEPT;

        sk.nioReadyOps(newOps);{
         //设置SelectionKey的readyOps为SelectionKey.OP_ACCEPT
         readyOps = ops;
     }
        return (newOps & ~oldOps) != 0;
    }
//------------------------------------SocketChannel实现-----------------------------------
//和ServerSocketChannel做的工作差不多,都是转换工作,只不过这里的ops一般会等于intOps
public boolean translateReadyOps(int ops, int initialOps,
                                     SelectionKeyImpl sk) {
        int intOps = sk.nioInterestOps(); // Do this just once, it synchronizes
        int oldOps = sk.nioReadyOps();
        int newOps = initialOps;

        if ((ops & Net.POLLNVAL) != 0) {
            // This should only happen if this channel is pre-closed while a
            // selection operation is in progress
            // ## Throw an error if this channel has not been pre-closed
            return false;
        }

        if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) {
            newOps = intOps;
            sk.nioReadyOps(newOps);
            // No need to poll again in checkConnect,
            // the error will be detected there
            readyToConnect = true;
            return (newOps & ~oldOps) != 0;
        }

        if (((ops & Net.POLLIN) != 0) &&
            ((intOps & SelectionKey.OP_READ) != 0) &&
            (state == ST_CONNECTED))
            newOps |= SelectionKey.OP_READ;

        if (((ops & Net.POLLCONN) != 0) &&
            ((intOps & SelectionKey.OP_CONNECT) != 0) &&
            ((state == ST_UNCONNECTED) || (state == ST_PENDING))) {
            newOps |= SelectionKey.OP_CONNECT;
            readyToConnect = true;
        }

        if (((ops & Net.POLLOUT) != 0) &&
            ((intOps & SelectionKey.OP_WRITE) != 0) &&
            (state == ST_CONNECTED))
            newOps |= SelectionKey.OP_WRITE;

        sk.nioReadyOps(newOps);
        return (newOps & ~oldOps) != 0;
    }

select小结:

  1. 调用processDeregisterQueue(),将调用了channel.close()的channel所属fd从epoll中删除
  2. 调用updateRegistrations(),将调用了channel.register(selector,OP_···)selecitionKey.interestOps(i)从而对channel感兴趣事件做了改动的操作真正应用到(调用epoll_ctl)epoll对象中(要注意的一个点是调用selectionkey.interestOps(0)会将该channel从epoll对象中删除)
  3. updateSelectedKeys()将从JVM操作的AllocatedNativeObject对象中获取已IO完成的fd,将这些fd对应的selectionKey放入selectedKey中,

五、server.configureBlocking()

其调用栈如下:

//ServerSocketChannelImpl.java
protected void implConfigureBlocking(boolean block) throws IOException {
        IOUtil.configureBlocking(fd, block);
    }
public static native void configureBlocking(FileDescriptor fd,
                                                boolean blocking)

由此得知,configureBlocking()设置的是ServerSocket的阻塞状态。若设置了server.configureBlocking(true),之后调用serverSocket.accept()就会阻塞着等待连接,也就是不将Selectoreventpoll放入该Socket对象的等待队列中,而是用最传统的BIO方式将进程引用放入该Socket对象的等待队列中。

参考资料

https://segmentfault.com/a/1190000017798684?utm_source=tag-newest

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值