nio的epoll和selector实现流程分析

一、NETTY底层使用的是NIO的selector和epoll进行实现的,select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说把数据从内核拷贝到用户空间是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

二、测试实例

1.C++

/*

*\ 服务器端的源代码

*/

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <iostream>
#include <signal.h>
#include <sys/epoll.h>

#define MAXFDS 256
#define EVENTS 100
#define PORT 8888

int epfd;
bool setNonBlock(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    if(-1 == fcntl(fd, F_SETFL, flags))
        return false;
    return true;
}

int main(int argc, char *argv[], char *evp[])
{
    int fd, nfds, confd;
    int on = 1;
    char *buffer[512];
    struct sockaddr_in saddr, caddr;
    struct epoll_event ev, events[EVENTS];

    if(-1 == socket(AF_INET, SOCKSTREAM), 0)
    {
        std::cout << "创建套接字出错啦" << std::endl;
        return -1;
    }


    struct sigaction sig;
    sigemptyset(&sig.sa_mask);
    sig_handler = SIG_IGN;
    sigaction(SIGPIPE, &N > sig, NULL);

    epfd = epoll_create(MAXFDS);

    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons((short)(PORT));
    saddr.sin_addr.s_addr = INADDR_ANY;
    if(-1 == bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)))
    {
        std::cout << "套接字不能绑定到服务器上" << std::endl;
        return -1;
    }

    if(-1 == listen(fd, 32))
    {
        std::cout << "监听套接字的时候出错了" << std::endl;
        return -1;
    }

    ev.data.fd = fd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

    while(true)
    {
        nfds = epoll_wait(epfd, &events, MAXFDS, 0);

        for(int i = 0; i < nfds; ++ i)
        {
            if(fd == events[i].data.fd)
            {
                memset(&caddr, sizeof(caddr));
                cfd = accept(fd, (struct sockaddr *)&caddr, &sizeof(caddr));
                if(-1 == cfd)
                {
                    std::cout << "服务器接收套接字的时候出问题了" << std::endl;
                    break;
                }

                setNonBlock(cfd);

                ev.data.fd = cfd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
            }
            else if(events[i].data.fd & EPOLLIN)
            {
                bzero(&buffer, sizeof(buffer));
                std::cout << "服务器端要读取客户端发过来的消息" << std::endl;
                ret = recv(events[i].data.fd, buffer, sizeof(buffer), 0);
                if(ret < 0)
                {
                    std::cout << "服务器收到的消息出错了" << endl;
                    return -1;
                }
                std::cout << "接收到的消息为:" << (char *) buffer << std::endl;
                ev.data.fd = events[i].data.fd;
                ev.events = EPOLLOUT;
                epoll_ctl(epfd, EPOLL_CTL_MOD, events[i].data.fd, &ev);
            }
            else if(events[i].data.fd & EPOLLOUT)
            {
                bzero(&buffer, sizeof(buffer));
                bcopy("The Author@: magicminglee@Hotmail.com", buffer, sizeof("The Author@: magicminglee@Hotmail.com"));
                ret = send(events[i].data.fd, buffer, strlen(buffer));
                if(ret < 0)
                {
                    std::cout << "服务器发送消息给客户端的时候出错啦" << std::endl;
                    return -1;
                }
                ev.data.fd = events[i].data.fd;
                epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
            }
        }
    }
    if(fd > 0)
    {
        shutdown(fd, SHUT_RDWR);
        close(fd);
    }
}

2.JAVA

package com.tpw.summaryday.nio;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <h3>summaryday</h3>
 * <p></p>
 *
 * @author : lipengyao
 * @date : 2021-09-16 16:50:54
 **/
@Slf4j
public class ServerReactor3 {
    public  static final  int workReactorNums = Runtime.getRuntime().availableProcessors() * 2;
    public static class WorkReactor {
        public static ExecutorService executorService = Executors.newFixedThreadPool(ServerReactor3.workReactorNums * 2);

        private Selector selector;
        private int reactorIndex;
        private int channelCnt;
        private Map<SocketChannel, ArrayDeque<String>> unWriteDataMap = new ConcurrentHashMap<>();
        private List<SocketChannel> waitRegisterChannels = new ArrayList<>();
        private Lock lock = new ReentrantLock();
        private int maxItemKeyCnt = 0;

        public WorkReactor(int reactorIndex) throws IOException {
            this.selector = Selector.open();
            this.reactorIndex = reactorIndex;
            this.channelCnt = 0;
            select();
            log.debug("register init channelCnt:{},reactorIndex:{},selectionKey:{}"
                    , this.channelCnt, this.reactorIndex,this.selector);
        }

        public void register(SocketChannel socketChannel) {
            lock.lock();
            try{
                waitRegisterChannels.add(socketChannel);
                log.debug("register add socket channel waitRegisterChannels:{}",waitRegisterChannels.size());
            }finally {
                lock.unlock();
            }

        }

        public void registerAllWaitChannels(){
            lock.lock();
            try{
                if (!this.waitRegisterChannels.isEmpty()){
                    log.debug("registerAllWaitChannels will register begin waitRegisterChannels:{}",waitRegisterChannels.size());
                    for (int i = 0; i < this.waitRegisterChannels.size(); i++) {
                        this.interRegister(this.waitRegisterChannels.get(i));
                    }
                    this.waitRegisterChannels.clear();
                    log.debug("registerAllWaitChannels  register end waitRegisterChannels:{}",waitRegisterChannels.size());
                }
            }finally {
                lock.unlock();
            }

        }

        public void interRegister(SocketChannel socketChannel){
            try {
                selector.wakeup();
                SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ /*| SelectionKey.OP_WRITE*/);
                 int read = SelectionKey.OP_READ;
                this.channelCnt++;
                unWriteDataMap.put(socketChannel, new ArrayDeque<String>());
                log.debug("register channelCnt:{},reactorIndex:{},selectionKey:{}"
                        , this.channelCnt, this.reactorIndex, selectionKey);
            }catch (  Exception e){
                e.printStackTrace();
            }
        }



        public void select() throws ClosedChannelException {
            executorService.submit(() -> {

                log.debug("begin channelCnt:{},reactorIndex:{}", this.channelCnt, this.reactorIndex);
                while (true) {
                    try {
                        if (selector.select(10) <= 0) {
//                            TimeUnit.MILLISECONDS.sleep(10);
//                            log.debug(" has no event,continue,reactorIndex:{}", this.reactorIndex);
                            this.registerAllWaitChannels();
                            continue;
                        }
                        log.debug(" get some io event,will handle,reactorIndex:{}", this.reactorIndex);
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
                        Set<SelectionKey> allKeys = selector.keys();
                        if (allKeys.size() > maxItemKeyCnt){
                            maxItemKeyCnt = allKeys.size();
                        }
                        log.debug(" get select key--> selectionKeys:{},channelCnt:{},reactorIndex:{},allKeys:{},maxItemKeyCnt:{}",
                                selectionKeys.size(), this.channelCnt, this.reactorIndex,allKeys.size(),maxItemKeyCnt);
                        while (selectionKeyIterator.hasNext()) {
                            SelectionKey t = selectionKeyIterator.next();
                            selectionKeyIterator.remove();
                            if (t.isReadable() && t.isValid()) {
                                SocketChannel socketChannel = (SocketChannel) t.channel();
                                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                                int count = socketChannel.read(byteBuffer);
                                log.debug(" read socketHandler:{},count:{}", socketChannel.getRemoteAddress(),count);
                                if (count < 0) {
                                    log.debug(" read error ,will close channel remote Address:{}"
                                            , socketChannel.getRemoteAddress());
                                    t.cancel();
                                    socketChannel.close();
                                    this.channelCnt--;
                                    continue;
                                }
                                byteBuffer.flip();
                                byte[] inputData = new byte[byteBuffer.limit()];
                                byteBuffer.get(inputData);
                                String body = new String(inputData);
                                log.debug(" read ok , ,reactorIndex:{},data:{}", reactorIndex, body);

                                if (socketChannel.isConnected() && socketChannel.isOpen()) {
                                    byteBuffer.clear();
                                    String response = "server rsp:" + body + "\r\n";
                                    log.debug(" read ok , ,response:{}", response);
//                                unWriteDataMap.get(socketChannel).addLast(response);
                                    byte[] writeByets = response.getBytes("utf-8");
                                    byteBuffer.put(writeByets);
                                    byteBuffer.flip();
                                    socketChannel.write(byteBuffer);
                                }
                            } else if (t.isWritable()) {
                                SocketChannel socketChannel = (SocketChannel) t.channel();
                                while (!unWriteDataMap.get(socketChannel).isEmpty()) {
                                    String response = unWriteDataMap.get(socketChannel).pollFirst();
                                    log.debug(" write ok , ,response:{}", response);
                                    byte[] writeByets = response.getBytes("utf-8");
                                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                                    byteBuffer.put(writeByets);
                                    byteBuffer.flip();
                                    socketChannel.write(byteBuffer);
                                }

                            }
                            selectionKeys.remove(t);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }

    }


    @Getter
    public static class SocketHandler {
        private String name;
        private Object channel;


        public SocketHandler(String name, Object channel) {
            this.name = name;
            this.channel = channel;
        }
    }

    public void start() throws IOException {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        int port = 7020;
        serverSocketChannel.bind(new InetSocketAddress(port));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, new SocketHandler("acceptHandler", serverSocketChannel));
        log.debug(" start bind success port:{}", port);
        int clientIndex = 0;

        WorkReactor[] workReactors = new WorkReactor[workReactorNums];
        for (int i = 0; i < workReactorNums; i++) {
            workReactors[i] = new WorkReactor(i);
        }
        while (selector.select() > 0) {
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
            while (selectionKeyIterator.hasNext()) {
                SelectionKey t = selectionKeyIterator.next();
                selectionKeys.remove(t);
                if (t.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) t.channel();
                    SocketHandler socketHandler = (SocketHandler) t.attachment();
//                    log.debug(" accept socketHandler:{}", socketHandler.getName());
                    SocketChannel socketChannel = serverSocketChannel1.accept();
                    socketChannel.configureBlocking(false);
                    int reactorNumIndex = clientIndex++ % workReactorNums;
                    log.debug(" accept new channel remote Address:{},reactorNumIndex:{}"
                            , socketChannel.getRemoteAddress(), reactorNumIndex);
                    workReactors[reactorNumIndex].register(socketChannel);
                }
            }
        }
    }


    public static void main(String[] args) {

        ServerReactor3 serverReactor = new ServerReactor3();
        try {
            serverReactor.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.这里面的accept-reactor 一个selector,处理所有的接入连接。就是我们主类的start函数,会不断接收连接,然后将新获取到的socketChannel注册到work-reactor的处理类中。

2.work-reactor CPU核数selector 和CPU核心*2 的线程。所有的客户端连接采用roundRobin方式均匀分配到这些线程上。这里面的work-reactor为WorkReactor类,每个类会创建一个selector,并处理部分的socketChannel连接,分担任务。

3.注意:对一个selector的register和select等所有操作要放到同一个线程处理,否则会出现死锁。像accept-reactor在注册新连接到的socketChannel到work-reactor的selector时,不能直接注册,需放在和work-reactor的select方法同一个线程处理,否则会出现竞争死锁。

三、Selector.open初始化流程分析

1.首先会调用Selector.open()创建一个selector对象。注意,一般nio程序会部署在linux环境,所以我们查看linux下的JDK实现源码(一般下载open-jdk的linux源码进行分析)。

public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
}

2.provider由sun.nio.ch.DefaultSelectorProvider.create()创建。create方法内部通过系统的名称来创建创建SelectorProvider,在这里由于linux环境,创建了sun.nio.ch.EPollSelectorProvider

 public static SelectorProvider create() {
	   String osName = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));
	   if (osName .equals("SunOS")) {
	       return createProvider("sun.nio.ch.DevPollSelectorProvider");
	   } else {
	       return (SelectorProvider)(osName .equals("Linux") ? createProvider("sun.nio.ch.EPollSelectorProvider") : new PollSelectorProvider());
	   }
}

3.继续回到Selector的open()中,获取到SelecProvider实例之后,继续调用openSelector(),很自然进入EPollSelectorProvider的openSelector()方法

    public AbstractSelector openSelector() throws IOException {
        return new EPollSelectorImpl(this);
    }

4.EPollSelectorImpl为linux下的实现,继承图如下

 5.我们来看EPollSelectorImpl初始化做的事情,EPollArrayWrapper为对linux的epoll操作的三个接口epoll_create,epoll_ctl,epoll_wait进行逻辑封装。

    EPollSelectorImpl(SelectorProvider sp) throws IOException {
        super(sp);
        long pipeFds = IOUtil.makePipe(false);
        fd0 = (int) (pipeFds >>> 32);
        fd1 = (int) pipeFds;
        pollWrapper = new EPollArrayWrapper();
        pollWrapper.initInterrupt(fd0, fd1);
        fdToKey = new HashMap<>();
    }

创建fd0,fd1,并且调用pollWrapper.initInterrupt是将这两个本地描述符加到epoll的监控事件中,以便调用wakeUp函数时能跳出select函数,可以快速唤醒,结束等待或者阻塞,进而跳出循环或者注册新的事件。

    void initInterrupt(int fd0, int fd1) {
        outgoingInterruptFD = fd1;
        incomingInterruptFD = fd0;
        epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);
    }

6.父类SelectorImp的构造方法。JDK中对于注册到Selector上的IO事件关系是使用SelectionKey来表示,代表了Channel感兴趣的事件,如Read,Write,Connect,Accept。内部初始化publicKeys和publicSelectedKeys,用到的容器是HashSet,前者用来保存所有的感兴趣的事件,后者准备好的事件。publicKeys 和publicSelectedKeys 引用这包装了一层权限控制,内部还是指向前面的HashSet。实际的堆内存中只有两个HashSet对象。下面是父类SelectorImpl的属性和构造方法:

    protected SelectorImpl(SelectorProvider sp) {
        super(sp);
        keys = ConcurrentHashMap.newKeySet();
        selectedKeys = new HashSet<>();
        publicKeys = Collections.unmodifiableSet(keys);
        publicSelectedKeys = Util.ungrowableSet(selectedKeys);
    }

7.EPollArrayWrapper完成了对epoll文件描述符的构建,以及对linux系统的epoll指令操纵的封装。维护每次selection操作的结果,即epoll_wait结果的epoll_event数组。这里面创建了epoll_create返回的句柄,pollArray就是int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );这个方法中的epoll_event数组对象,用来接收IO改变的事件,由内核 进行修改此本地对象。

    EPollArrayWrapper() throws IOException {
        // creates the epoll file descriptor
        epfd = epollCreate();

        // the epoll_event array passed to epoll_wait
        int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT;
        pollArray = new AllocatedNativeObject(allocationSize, true);
        pollArrayAddress = pollArray.address();

        // eventHigh needed when using file descriptors > 64k
        if (OPEN_MAX > MAX_UPDATE_ARRAY_SIZE)
            eventsHigh = new HashMap<>();
    }

8.eventsLow,eventsHigh都是存放socket的FD所注册关注的事件列表。 eventsLow以FD为数组下标,值为关注事件。eventsHigh则是以FD做为KEY,关注事件作为VALUEL,小于64K的FD放在低端事件数组,否则放在高端事件数组。

private final byte[] eventsLow = new byte[MAX_UPDATE_ARRAY_SIZE];
private Map<Integer,Byte> eventsHigh;
9.这里面还有几个参数,updateCount表示已注册事件的数目,updateDescriptors为更新事件的FD列表。registered表示这个FD是否已经注册到EPOLL句柄中,主要是用来在epll_wait中注册事件时作判断使用,首先注册则是要调用epoll_ctl(ctl_add),其它则是要调用epoll_ctl(ctl_modify),如果注销了epoll_ctl(ctl_del)
// number of file descriptors with registration changes pending
private int updateCount;

// file descriptors with registration changes pending
private int[] updateDescriptors = new int[INITIAL_PENDING_UPDATE_SIZE];
// Used by release and updateRegistrations to track whether a file
// descriptor is registered with epoll.
private final BitSet registered = new BitSet();

四、channel.register注册流程分析

1.channel.register首先会调用基类的java.nio.channels.spi.AbstractSelectableChannel.register方法,

  1. 如果该channel和selector已经注册过,则直接添加事件和附件
  2. 否则通过selector实现注册过程。继续调用SelectorImp的register方法
  3. addKey(k)是将注册的key添加到socketchannel的成员变量keys[]中

public abstract class AbstractSelectableChannel extends SelectableChannel {
    private final SelectorProvider provider;
    private SelectionKey[] keys = null;

 public final SelectionKey register(Selector var1, int var2, Object var3) throws ClosedChannelException {
        synchronized(this.regLock) {
            if (!this.isOpen()) {
                throw new ClosedChannelException();
            }  else {
                SelectionKey var5 = this.findKey(var1);
                if (var5 != null) {
                    var5.interestOps(var2);
                    var5.attach(var3);
                }
                if (var5 == null) {
                    synchronized(this.keyLock) {
                        var5 = ((AbstractSelector)var1).register(this, var2, var3);
                        this.addKey(var5);
                    }
                }
                return var5;
            }
        }
    }

2.接着会调用selector.register方法,这个会调用selectorImpl这个基类的注册方法。这里会创建一个SelectionKeyImpl,里面包装了socketChannel,和当前的selector作为成员变量,并可以添加一个任意对象为附加数据。

   protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
        if (!(var1 instanceof SelChImpl)) {
            throw new IllegalSelectorException();
        } else {
            SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
            var4.attach(var3);
            synchronized(this.publicKeys) {
                this.implRegister(var4);
            }

            var4.interestOps(var2);
            return var4;
        }
    }

3.EPollSelectorImpl.implRegister,将channel对应的fd(文件描述符)和对应的SelectionKey放到fdToKey映射表中。fdToKey是一个map类型的结构,用来保存fd和key的映射关系。
将channel对应的fd(文件描述符)添加到EPollArrayWrapper中,并强制初始化fd的事件为0 ( 强制初始更新事件为0,因为该事件可能存在于之前被取消过的注册中。)
将selectionKey放入到keys集合中。

    protected void implRegister(SelectionKeyImpl ski) {
        if (closed)
            throw new ClosedSelectorException();
        SelChImpl ch = ski.channel;
        int fd = Integer.valueOf(ch.getFDVal());
        fdToKey.put(fd, ski);
        pollWrapper.add(fd);
        keys.add(ski);
    }
ch.getFDVal()就是socketChannel中的FD.keys就是我们前面讲的全量注册到selector的selectKey对象。

4.EpollWrapper的add方法内部调用了setUpdateEvents方法,并且把第二个参数事件类型(events)设置为0,即为初始值。在setUpdateEvents中,把fd作为数组的下表,值为事件类型。如果fd大于64*1024,则把fd和事件类型存入eventsHigh中,就是上面讲的EpollWrapper中的成员变量,两个事件数组和HASHMAP.

    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);
            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[fd] = events;
            }
        } else {
            Integer key = Integer.valueOf(fd);
            if (!isEventsHighKilled(key) || force) {
                eventsHigh.put(key, Byte.valueOf(events));
            }
        }
    }

注意,这里add时首先会判断registered中是否已经有此FD,有则报错,不能重复加入。

5.我们再回到第二步selectorImpl.register方法中,会调用SelectionKeyImpl.interestOps(var2);进行事件添加,接着调用到SelectionKeyImpl.nioInterestOps。

    public SelectionKey nioInterestOps(int var1) {
        if ((var1 & ~this.channel().validOps()) != 0) {
            throw new IllegalArgumentException();
        } else {
            this.channel.translateAndSetInterestOps(var1, this);
            this.interestOps = var1;
            return this;
        }
    }

6.上面的channel就是socketChannel,var1为关注的事件。sun.nio.ch.SocketChannelImpl.translateAndSetInterestOps 进行关注事件转换和设置。

    public void translateAndSetInterestOps(int var1, SelectionKeyImpl var2) {
        int var3 = 0;
        if ((var1 & 1) != 0) {
            var3 |= Net.POLLIN;
        }

        if ((var1 & 4) != 0) {
            var3 |= Net.POLLOUT;
        }

        if ((var1 & 8) != 0) {
            var3 |= Net.POLLCONN;
        }

        var2.selector.putEventOps(var2, var3);
    }

这里面的1,4,8分别为SelectionKey.OP_READ,OP_WRITE,OP_CONNECT,把这三个转换为Net的定义。

7.接着又调用了EpollSelectorImpl.putEventOps配置事件。

    public void putEventOps(SelectionKeyImpl ski, int ops) {
        if (closed)
            throw new ClosedSelectorException();
        SelChImpl ch = ski.channel;
        pollWrapper.setInterest(ch.getFDVal(), ops);
    }

8.接着调用了EpollWrapper的setInterest对FD设置关注事件。首先会判断存放fd的数组updateDescriptors是否已满,如果满了,则进行扩容,在原来的基础上加64,然后将fd保存到数组中。然后调用setUpdateEvents,设置fd的事件,在前面已经分析过一次。

    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;
            }
            updateDescriptors[updateCount++] = fd;

            // events are stored as bytes for efficiency reasons
            byte b = (byte)mask;
            assert (b == mask) && (b != KILLED);
            setUpdateEvents(fd, b, false);
        }
    }

setUpdateEvent方法会将注册的感兴趣的事件和其对应的文件描述存储到EPollArrayWrapper对象的eventsLow或eventsHigh中,这是给底层实现epoll_wait时使用的。同时selectorImpl.register还会将设置SelectionKey的interestOps字段,这是给我们程序员获取使用的

这里会把新关注的事件的FD保存到updateDescriptors中。

五、selector.select注册流程分析

1.首先触发selectorImpl.select方法,为抽象基类的方法,这里面都使用了模板的设计模式,将具体的业务实现放在继承类中。抽象基类实现框架的流程。

    public int select(long var1) throws IOException {
        if (var1 < 0L) {
            throw new IllegalArgumentException("Negative timeout");
        } else {
            return this.lockAndDoSelect(var1 == 0L ? -1L : var1);
        }
    }

    private int lockAndDoSelect(long var1) throws IOException {
        synchronized(this) {
                synchronized(this.publicKeys) {
                    synchronized(this.publicSelectedKeys) {
                        var10000 = this.doSelect(var1);
                    }
                }

                return var10000;
        }
    }
    protected abstract int doSelect(long var1) throws IOException;

2.该方法会一直阻塞直到至少一个channel被选择(即,该channel注册的事件发生了为止,除非当前线程发生中断或者selector的wakeup方法被调用。Selector.select方法最终调用的是EPollSelectorImpl的doSelect方法

 protected int doSelect(long timeout) throws IOException {
        if (closed)
            throw new ClosedSelectorException();
        processDeregisterQueue();
        try {
            begin();
            pollWrapper.poll(timeout);
        } finally {
            end();
        }
        processDeregisterQueue();
        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方法,将cancel的selectionKey从selector中删除,底层会调用epoll_ctl方法移除被epoll所监听的文件描述符;
begin和end方法主要是为了处理线程中断,将线程的中断转化为Selector的wakeup方法,避免线程堵塞在IO操作上;
通过fdToKey查找文件描述符对应的SelectionKey,并更新之。

3.继续看poll的实现

 int poll(long timeout) throws IOException {
        updateRegistrations();
        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;
    }

4.updateRegistrations方法 updateRegistrations()方法会将已经注册到该selector的fd和事件(eventsLow或eventsHigh)通过调用epollCtl(epfd, opcode, fd, events),注册到linux系统中。具体的实现如下:

 private void updateRegistrations() {
        synchronized (updateLock) {
            int j = 0;
            while (j < updateCount) {
                int fd = updateDescriptors[j];
                short events = getUpdateEvents(fd);
                boolean isRegistered = registered.get(fd);
                int opcode = 0;

                if (events != KILLED) {
                    if (isRegistered) {
                        opcode = (events != 0) ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
                    } else {
                        opcode = (events != 0) ? EPOLL_CTL_ADD : 0;
                    }
                    if (opcode != 0) {
                        epollCtl(epfd, opcode, fd, events);
                        if (opcode == EPOLL_CTL_ADD) {
                            registered.set(fd);
                        } else if (opcode == EPOLL_CTL_DEL) {
                            registered.clear(fd);
                        }
                    }
                }
                j++;
            }
            updateCount = 0;
        }
    }

    private byte getUpdateEvents(int fd) {
        if (fd < MAX_UPDATE_ARRAY_SIZE) {
            return eventsLow[fd];
        } else {
            Byte result = eventsHigh.get(Integer.valueOf(fd));
            // result should never be null
            return result.byteValue();
        }
    }

这里首先从updateDescriptors中获取已注册的FD,然后通过fd从event_low,event_high中获取关注事件。再根据是否已注册来判断是调用ctl_add,还是ctl_modify,并配置到epoll事件模型中。

5.然后调用updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);进行IO事件等待,updated返回已更新的EPOLLEVENTS个数,并且内核会将新的IO事件放以pollArrayAddress(pollArray)。

6.继承回到第二步doSelect,有IO事件返回后,会调用updateSelectedKeys将接收到的IO事件更新到EpollSelectorImpl的keys,selectkeys集合中。

 private int updateSelectedKeys() {
        int entries = pollWrapper.updated;
        int numKeysUpdated = 0;
        for (int i=0; i<entries; i++) {
            int nextFD = pollWrapper.getDescriptor(i);
            SelectionKeyImpl ski = fdToKey.get(Integer.valueOf(nextFD));
            // ski is null in the case of an interrupt
            if (ski != null) {
                int rOps = pollWrapper.getEventOps(i);
                if (selectedKeys.contains(ski)) {
                    if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
                        numKeysUpdated++;
                    }
                } else {
                    ski.channel.translateAndSetReadyOps(rOps, ski);
                    if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
                        selectedKeys.add(ski);
                        numKeysUpdated++;
                    }
                }
            }
        }
        return numKeysUpdated;
    }

7.pollWrapper.getDescriptor从pollArray成员变量中根据索引和偏移位置获取EPOLLEVENT对象数组中的索引对象的描述符数据,pollWrapper.getEventOps则是获取新的事件。由于pollArray为本地对象,所以都是通过偏移位置去获取值的。

    int getEventOps(int i) {
        int offset = SIZE_EPOLLEVENT * i + EVENT_OFFSET;
        return pollArray.getInt(offset);
    }

    int getDescriptor(int i) {
        int offset = SIZE_EPOLLEVENT * i + FD_OFFSET;
        return pollArray.getInt(offset);
    }

8.通过fdToKey的MAP对象获取FD所对应的selectKey,后面根据EpollSelectorImpl的成员变量selectKeys集合对象是否包含此selectKey,包含则直接将关注事件转换为应用层事件并设置到selectKeyImpl的interKeys关注事件成员变量中就可以,没有包含,则先要加入到集合中。

9.接着调用selectkey.channel就是socketChannel的translateAndSetReadyOps

sun.nio.ch.SocketChannelImpl
    
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl ski) {
        return translateReadyOps(ops, 0, ski);
    }

  public boolean translateReadyOps(int ops, int initialOps, SelectionKeyImpl ski) {
        int intOps = ski.nioInterestOps();
        int oldOps = ski.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;
            ski.nioReadyOps(newOps);
            return (newOps & ~oldOps) != 0;
        }

        boolean connected = isConnected();
        if (((ops & Net.POLLIN) != 0) &&
            ((intOps & SelectionKey.OP_READ) != 0) && connected)
            newOps |= SelectionKey.OP_READ;

        if (((ops & Net.POLLCONN) != 0) &&
            ((intOps & SelectionKey.OP_CONNECT) != 0) && isConnectionPending())
            newOps |= SelectionKey.OP_CONNECT;

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

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

这里面把Net(就是EPOLL底层)的IO事件转换为SelectionKey的事件。然后又调用SelectKeyImpl的nioReadyOps再设置到关注事件成员变量readOps中。

sun.nio.ch.SelectionKeyImpl    
public void nioReadyOps(int ops) {
        readyOps = ops;
    }

六、selector.selectedKeys()流程分析

1.这个方法比较简单,直接返回SelectorImpl的publicSelectedKeys,这个就是上面EpollSelectorImpl中的selectedKeys的引用,就不用重复讲了,在上面方法已经更新进去了。

    public Set<SelectionKey> selectedKeys() {
        if (!this.isOpen() && !Util.atBugLevel("1.4")) {
            throw new ClosedSelectorException();
        } else {
            return this.publicSelectedKeys;
        }
    }

部分摘自下面文章
原文链接:https://blog.csdn.net/TheLudlows/article/details/82931478

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值