淺談 Linux 中 Selector 的實(shí)現(xiàn)原理

概述

Selector是NIO中實(shí)現(xiàn)I/O多路復(fù)用的關(guān)鍵類幔崖。Selector實(shí)現(xiàn)了通過一個線程管理多個Channel食店,從而管理多個網(wǎng)絡(luò)連接的目的。
Channel代表這一個網(wǎng)絡(luò)連接通道赏寇,我們可以將Channel注冊到Selector中以實(shí)現(xiàn)Selector對其的管理吉嫩。一個Channel可以注冊到多個不同的Selector中。
當(dāng)Channel注冊到Selector后會返回一個SelectionKey對象嗅定,該SelectionKey對象則代表這這個Channel和它注冊的Selector間的關(guān)系自娩。并且SelectionKey中維護(hù)著兩個很重要的屬性:interestOps、readyOps
interestOps是我們希望Selector監(jiān)聽Channel的哪些事件。我們將我們感興趣的事件設(shè)置到該字段忙迁,這樣在selection操作時脐彩,當(dāng)發(fā)現(xiàn)該Channel有我們所感興趣的事件發(fā)生時,就會將我們感興趣的事件再設(shè)置到readyOps中姊扔,這樣我們就能得知是哪些事件發(fā)生了以做相應(yīng)處理惠奸。

Selector的中的重要屬性

Selector中維護(hù)3個特別重要的SelectionKey集合,分別是

  • keys:所有注冊到Selector的Channel所表示的SelectionKey都會存在于該集合中恰梢。keys元素的添加會在Channel注冊到Selector時發(fā)生佛南。
  • selectedKeys:該集合中的每個SelectionKey都是其對應(yīng)的Channel在上一次操作selection期間被檢查到至少有一種SelectionKey中所感興趣的操作已經(jīng)準(zhǔn)備好被處理。該集合是keys的一個子集嵌言。
  • cancelledKeys:執(zhí)行了取消操作的SelectionKey會被放入到該集合中嗅回。該集合是keys的一個子集。

下面的源碼解析會說明上面3個集合的用處

Selector 源碼解析

下面我們通過一段對Selector的使用流程講解來進(jìn)一步深入其實(shí)現(xiàn)原理摧茴。
首先先來段Selector最簡單的使用片段

        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        int port = 5566;          
        serverChannel.socket().bind(new InetSocketAddress(port));
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while(true){
            int n = selector.select();
            if(n > 0) {
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey selectionKey = iter.next();
                    ......
                    iter.remove();
                }
            }
        }


1绵载、Selector的構(gòu)建

SocketChannel、ServerSocketChannel和Selector的實(shí)例初始化都通過SelectorProvider類實(shí)現(xiàn)苛白。

ServerSocketChannel.open();

    public static ServerSocketChannel open() throws IOException {
        return SelectorProvider.provider().openServerSocketChannel();
    }



SocketChannel.open();

    public static SocketChannel open() throws IOException {
        return SelectorProvider.provider().openSocketChannel();
    }

Selector.open();

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


我們來進(jìn)一步的了解下SelectorProvider.provider()
    public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

① 如果配置了“java.nio.channels.spi.SelectorProvider”屬性娃豹,則通過該屬性值load對應(yīng)的SelectorProvider對象,如果構(gòu)建失敗則拋異常丸氛。
② 如果provider類已經(jīng)安裝在了對系統(tǒng)類加載程序可見的jar包中培愁,并且該jar包的源碼目錄META-INF/services包含有一個java.nio.channels.spi.SelectorProvider提供類配置文件著摔,則取文件中第一個類名進(jìn)行l(wèi)oad以構(gòu)建對應(yīng)的SelectorProvider對象缓窜,如果構(gòu)建失敗則拋異常。
③ 如果上面兩種情況都不存在谍咆,則返回系統(tǒng)默認(rèn)的SelectorProvider禾锤,即,sun.nio.ch.DefaultSelectorProvider.create();
④ 隨后在調(diào)用該方法摹察,即SelectorProvider.provider()恩掷。則返回第一次調(diào)用的結(jié)果。

不同系統(tǒng)對應(yīng)著不同的sun.nio.ch.DefaultSelectorProvider

這里我們看linux下面的sun.nio.ch.DefaultSelectorProvider

public class DefaultSelectorProvider {

    /**
     * Prevent instantiation.
     */
    private DefaultSelectorProvider() { }

    /**
     * Returns the default SelectorProvider.
     */
    public static SelectorProvider create() {
        return new sun.nio.ch.EPollSelectorProvider();
    }

}

可以看見供嚎,linux系統(tǒng)下sun.nio.ch.DefaultSelectorProvider.create(); 會生成一個sun.nio.ch.EPollSelectorProvider類型的SelectorProvider黄娘,這里對應(yīng)于linux系統(tǒng)的epoll

接下來看下 selector.open():
    /**
     * Opens a selector.
     *
     * <p> The new selector is created by invoking the {@link
     * java.nio.channels.spi.SelectorProvider#openSelector openSelector} method
     * of the system-wide default {@link
     * java.nio.channels.spi.SelectorProvider} object.  </p>
     *
     * @return  A new selector
     *
     * @throws  IOException
     *          If an I/O error occurs
     */
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

在得到sun.nio.ch.EPollSelectorProvider后調(diào)用openSelector()方法構(gòu)建Selector,這里會構(gòu)建一個EPollSelectorImpl對象克滴。

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;
EPollSelectorImpl(SelectorProvider sp) throws IOException {
        super(sp);
        long pipeFds = IOUtil.makePipe(false);
        fd0 = (int) (pipeFds >>> 32);
        fd1 = (int) pipeFds;
        try {
            pollWrapper = new EPollArrayWrapper();
            pollWrapper.initInterrupt(fd0, fd1);
            fdToKey = new HashMap<>();
        } catch (Throwable t) {
            try {
                FileDispatcherImpl.closeIntFD(fd0);
            } catch (IOException ioe0) {
                t.addSuppressed(ioe0);
            }
            try {
                FileDispatcherImpl.closeIntFD(fd1);
            } catch (IOException ioe1) {
                t.addSuppressed(ioe1);
            }
            throw t;
        }
    }

EPollSelectorImpl構(gòu)造函數(shù)完成:
① EPollArrayWrapper的構(gòu)建逼争,EpollArrayWapper將Linux的epoll相關(guān)系統(tǒng)調(diào)用封裝成了native方法供EpollSelectorImpl使用。
② 通過EPollArrayWrapper向epoll注冊中斷事件

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

③ fdToKey:構(gòu)建文件描述符-SelectionKeyImpl映射表劝赔,所有注冊到selector的channel對應(yīng)的SelectionKey和與之對應(yīng)的文件描述符都會放入到該映射表中誓焦。

EPollArrayWrapper

EPollArrayWrapper完成了對epoll文件描述符的構(gòu)建,以及對linux系統(tǒng)的epoll指令操縱的封裝着帽。維護(hù)每次selection操作的結(jié)果杂伟,即epoll_wait結(jié)果的epoll_event數(shù)組移层。
EPollArrayWrapper操縱了一個linux系統(tǒng)下epoll_event結(jié)構(gòu)的本地數(shù)組。

* typedef union epoll_data {
*     void *ptr;
*     int fd;
*     __uint32_t u32;
*     __uint64_t u64;
*  } epoll_data_t;
*
* struct epoll_event {
*     __uint32_t events;
*     epoll_data_t data;
* };

epoll_event的數(shù)據(jù)成員(epoll_data_t data)包含有與通過epoll_ctl將文件描述符注冊到epoll時設(shè)置的數(shù)據(jù)相同的數(shù)據(jù)赫粥。這里data.fd為我們注冊的文件描述符观话。這樣我們在處理事件的時候持有有效的文件描述符了。

EPollArrayWrapper將Linux的epoll相關(guān)系統(tǒng)調(diào)用封裝成了native方法供EpollSelectorImpl使用越平。

    private native int epollCreate();
    private native void epollCtl(int epfd, int opcode, int fd, int events);
    private native int epollWait(long pollAddress, int numfds, long timeout,
                                 int epfd) throws IOException;

上述三個native方法就對應(yīng)Linux下epoll相關(guān)的三個系統(tǒng)調(diào)用
    // The fd of the epoll driver
    private final int epfd;

     // The epoll_event array for results from epoll_wait
    private final AllocatedNativeObject pollArray;

    // Base address of the epoll_event array
    private final long pollArrayAddress;
    // 用于存儲已經(jīng)注冊的文件描述符和其注冊等待改變的事件的關(guān)聯(lián)關(guān)系匪燕。在epoll_wait操作就是要檢測這里文件描述法注冊的事件是否有發(fā)生。
    private final byte[] eventsLow = new byte[MAX_UPDATE_ARRAY_SIZE];
    private final Map<Integer,Byte> eventsHigh = new HashMap<>();
    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();
    }

EPoolArrayWrapper構(gòu)造函數(shù)喧笔,創(chuàng)建了epoll文件描述符帽驯。構(gòu)建了一個用于存放epoll_wait返回結(jié)果的epoll_event數(shù)組。

ServerSocketChannel的構(gòu)建

ServerSocketChannel.open();

返回ServerSocketChannelImpl對象书闸,構(gòu)建linux系統(tǒng)下ServerSocket的文件描述符尼变。

    // Our file descriptor
    private final FileDescriptor fd;

    // fd value needed for dev/poll. This value will remain valid
    // even after the value in the file descriptor object has been set to -1
    private int fdVal;
    ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
        super(sp);
        this.fd =  Net.serverSocket(true);
        this.fdVal = IOUtil.fdVal(fd);
        this.state = ST_INUSE;
    }


將ServerSocketChannel注冊到Selector

serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    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);
            if (k != null) {
                k.interestOps(ops);
                k.attach(att);
            }
            if (k == null) {
                // New registration
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
            }
            return k;
        }
    }
    protected final SelectionKey register(AbstractSelectableChannel ch,
                                          int ops,
                                          Object attachment)
    {
        if (!(ch instanceof SelChImpl))
            throw new IllegalSelectorException();
        SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
        k.attach(attachment);
        synchronized (publicKeys) {
            implRegister(k);
        }
        k.interestOps(ops);
        return k;
    }

① 構(gòu)建代表channel和selector間關(guān)系的SelectionKey對象
② implRegister(k)將channel注冊到epoll中
③ k.interestOps(int) 完成下面兩個操作:
a) 會將注冊的感興趣的事件和其對應(yīng)的文件描述存儲到EPollArrayWrapper對象的eventsLow或eventsHigh中,這是給底層實(shí)現(xiàn)epoll_wait時使用的浆劲。
b) 同時該操作還會將設(shè)置SelectionKey的interestOps字段嫌术,這是給我們程序員獲取使用的。

EPollSelectorImpl. implRegister
    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);
    }

① 將channel對應(yīng)的fd(文件描述符)和對應(yīng)的SelectionKeyImpl放到fdToKey映射表中牌借。
② 將channel對應(yīng)的fd(文件描述符)添加到EPollArrayWrapper中度气,并強(qiáng)制初始化fd的事件為0 ( 強(qiáng)制初始更新事件為0,因?yàn)樵撌录赡艽嬖谟谥氨蝗∠^的注冊中膨报。)
③ 將selectionKey放入到keys集合中磷籍。

Selection操作

selection操作有3中類型:
① select():該方法會一直阻塞直到至少一個channel被選擇(即,該channel注冊的事件發(fā)生了)為止现柠,除非當(dāng)前線程發(fā)生中斷或者selector的wakeup方法被調(diào)用院领。
② select(long time):該方法和select()類似,該方法也會導(dǎo)致阻塞直到至少一個channel被選擇(即够吩,該channel注冊的事件發(fā)生了)為止比然,除非下面3種情況任意一種發(fā)生:a) 設(shè)置的超時時間到達(dá);b) 當(dāng)前線程發(fā)生中斷周循;c) selector的wakeup方法被調(diào)用
③ selectNow():該方法不會發(fā)生阻塞强法,如果沒有一個channel被選擇也會立即返回。

我們主要來看看select()的實(shí)現(xiàn) :int n = selector.select();
    public int select() throws IOException {
        return select(0);
    }

最終會調(diào)用到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;
    }

① 先處理注銷的selectionKey隊(duì)列
② 進(jìn)行底層的epoll_wait操作
③ 再次對注銷的selectionKey隊(duì)列進(jìn)行處理
④ 更新被選擇的selectionKey

先來看processDeregisterQueue():
    void processDeregisterQueue() throws IOException {
        Set var1 = this.cancelledKeys();
        synchronized(var1) {
            if (!var1.isEmpty()) {
                Iterator var3 = var1.iterator();

                while(var3.hasNext()) {
                    SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();

                    try {
                        this.implDereg(var4);
                    } catch (SocketException var12) {
                        IOException var6 = new IOException("Error deregistering key");
                        var6.initCause(var12);
                        throw var6;
                    } finally {
                        var3.remove();
                    }
                }
            }

        }
    }

從cancelledKeys集合中依次取出注銷的SelectionKey湾笛,執(zhí)行注銷操作饮怯,將處理后的SelectionKey從cancelledKeys集合中移除。執(zhí)行processDeregisterQueue()后cancelledKeys集合會為空迄本。

    protected void implDereg(SelectionKeyImpl ski) throws IOException {
        assert (ski.getIndex() >= 0);
        SelChImpl ch = ski.channel;
        int fd = ch.getFDVal();
        fdToKey.remove(Integer.valueOf(fd));
        pollWrapper.remove(fd);
        ski.setIndex(-1);
        keys.remove(ski);
        selectedKeys.remove(ski);
        deregister((AbstractSelectionKey)ski);
        SelectableChannel selch = ski.channel();
        if (!selch.isOpen() && !selch.isRegistered())
            ((SelChImpl)selch).kill();
    }

注銷會完成下面的操作:
① 將已經(jīng)注銷的selectionKey從fdToKey( 文件描述與SelectionKeyImpl的映射表 )中移除
② 將selectionKey所代表的channel的文件描述符從EPollArrayWrapper中移除
③ 將selectionKey從keys集合中移除硕淑,這樣下次selector.select()就不會再將該selectionKey注冊到epoll中監(jiān)聽
④ 也會將selectionKey從對應(yīng)的channel中注銷
⑤ 最后如果對應(yīng)的channel已經(jīng)關(guān)閉并且沒有注冊其他的selector了,則將該channel關(guān)閉
完成??的操作后,注銷的SelectionKey就不會出現(xiàn)先在keys置媳、selectedKeys以及cancelKeys這3個集合中的任何一個于樟。

接著我們來看EPollArrayWrapper.poll(timeout):
    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;
    }

updateRegistrations()方法會將已經(jīng)注冊到該selector的事件(eventsLow或eventsHigh)通過調(diào)用epollCtl(epfd, opcode, fd, events); 注冊到linux系統(tǒng)中。
這里epollWait就會調(diào)用linux底層的epoll_wait方法拇囊,并返回在epoll_wait期間有事件觸發(fā)的entry的個數(shù)

再看updateSelectedKeys():
    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;
    }

該方法會從通過EPollArrayWrapper pollWrapper 以及 fdToKey( 構(gòu)建文件描述符-SelectorKeyImpl映射表 )來獲取有事件觸發(fā)的SelectionKeyImpl對象迂曲,然后將SelectionKeyImpl放到selectedKey集合( 有事件觸發(fā)的selectionKey集合,可以通過selector.selectedKeys()方法獲得 )中寥袭,即selectedKeys路捧。并重新設(shè)置SelectionKeyImpl中相關(guān)的readyOps值。
但是传黄,這里要注意兩點(diǎn):
① 如果SelectionKeyImpl已經(jīng)存在于selectedKeys集合中杰扫,并且發(fā)現(xiàn)觸發(fā)的事件已經(jīng)存在于readyOps中了,則不會使numKeysUpdated++膘掰;這樣會使得我們無法得知該事件的變化章姓。
??這點(diǎn)說明了為什么我們要在每次從selectedKey中獲取到Selectionkey后,將其從selectedKey集合移除识埋,就是為了當(dāng)有事件觸發(fā)使selectionKey能正確到放入selectedKey集合中凡伊,并正確的通知給調(diào)用者。
再者窒舟,如果不將已經(jīng)處理的SelectionKey從selectedKey集合中移除系忙,那么下次有新事件到來時,在遍歷selectedKey集合時又會遍歷到這個SelectionKey,這個時候就很可能出錯了。比如砸泛,如果沒有在處理完OP_ACCEPT事件后將對應(yīng)SelectionKey從selectedKey集合移除,那么下次遍歷selectedKey集合時见剩,處理到到該SelectionKey杀糯,相應(yīng)的ServerSocketChannel.accept()將返回一個空(null)的SocketChannel扫俺。
② 如果發(fā)現(xiàn)channel所發(fā)生I/O事件不是當(dāng)前SelectionKey所感興趣,則不會將SelectionKeyImpl放入selectedKeys集合中固翰,也不會使numKeysUpdated++

epoll原理

select狼纬,poll,epoll都是IO多路復(fù)用的機(jī)制骂际。I/O多路復(fù)用就是通過一種機(jī)制疗琉,一個進(jìn)程可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒)歉铝,能夠通知程序進(jìn)行相應(yīng)的讀寫操作盈简。但select,poll,epoll本質(zhì)上都是同步I/O柠贤,因?yàn)樗麄兌夹枰谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫香浩,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負(fù)責(zé)進(jìn)行讀寫臼勉,異步I/O的實(shí)現(xiàn)會負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間邻吭。

epoll是Linux下的一種IO多路復(fù)用技術(shù),可以非常高效的處理數(shù)以百萬計(jì)的socket句柄宴霸。

在 select/poll中囱晴,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對所有監(jiān)視的文件描述符進(jìn)行掃描瓢谢,而epoll事先通過epoll_ctl()來注冊一 個文件描述符畸写,一旦基于某個文件描述符就緒時,內(nèi)核會采用類似callback的回調(diào)機(jī)制氓扛,迅速激活這個文件描述符艺糜,當(dāng)進(jìn)程調(diào)用epoll_wait() 時便得到通知。(此處去掉了遍歷文件描述符幢尚,而是通過監(jiān)聽回調(diào)的的機(jī)制破停。這正是epoll的魅力所在。)
如果沒有大量的idle -connection或者dead-connection尉剩,epoll的效率并不會比select/poll高很多真慢,但是當(dāng)遇到大量的idle- connection,就會發(fā)現(xiàn)epoll的效率大大高于select/poll理茎。

注意:linux下Selector底層是通過epoll來實(shí)現(xiàn)的黑界,當(dāng)創(chuàng)建好epoll句柄后,它就會占用一個fd值皂林,在linux下如果查看/proc/進(jìn)程id/fd/朗鸠,是能夠看到這個fd的,所以在使用完epoll后础倍,必須調(diào)用close()關(guān)閉烛占,否則可能導(dǎo)致fd被耗盡。


先看看使用c封裝的3個epoll系統(tǒng)調(diào)用:

  • int epoll_create(int size)
    epoll_create建立一個epoll對象沟启。參數(shù)size是內(nèi)核保證能夠正確處理的最大句柄數(shù)忆家,多于這個最大數(shù)時內(nèi)核可不保證效果。
  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    epoll_ctl可以操作epoll_create創(chuàng)建的epoll德迹,如將socket句柄加入到epoll中讓其監(jiān)控芽卿,或把epoll正在監(jiān)控的某個socket句柄移出epoll。
  • int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout)
    epoll_wait在調(diào)用時胳搞,在給定的timeout時間內(nèi)卸例,所監(jiān)控的句柄中有事件發(fā)生時称杨,就返回用戶態(tài)的進(jìn)程。


    大概看看epoll內(nèi)部是怎么實(shí)現(xiàn)的:
  1. epoll初始化時筷转,會向內(nèi)核注冊一個文件系統(tǒng)列另,用于存儲被監(jiān)控的句柄文件,調(diào)用epoll_create時旦装,會在這個文件系統(tǒng)中創(chuàng)建一個file節(jié)點(diǎn)页衙。同時epoll會開辟自己的內(nèi)核高速緩存區(qū),以紅黑樹的結(jié)構(gòu)保存句柄阴绢,以支持快速的查找店乐、插入、刪除呻袭。還會再建立一個list鏈表眨八,用于存儲準(zhǔn)備就緒的事件。
  2. 當(dāng)執(zhí)行epoll_ctl時左电,除了把socket句柄放到epoll文件系統(tǒng)里file對象對應(yīng)的紅黑樹上之外廉侧,還會給內(nèi)核中斷處理程序注冊一個回調(diào)函數(shù),告訴內(nèi)核篓足,如果這個句柄的中斷到了段誊,就把它放到準(zhǔn)備就緒list鏈表里。所以栈拖,當(dāng)一個socket上有數(shù)據(jù)到了连舍,內(nèi)核在把網(wǎng)卡上的數(shù)據(jù)copy到內(nèi)核中后,就把socket插入到就緒鏈表里涩哟。
  3. 當(dāng)epoll_wait調(diào)用時索赏,僅僅觀察就緒鏈表里有沒有數(shù)據(jù),如果有數(shù)據(jù)就返回贴彼,否則就sleep潜腻,超時時立刻返回。


    epoll的兩種工作模式:
  • LT:level-trigger器仗,水平觸發(fā)模式融涣,只要某個socket處于readable/writable狀態(tài),無論什么時候進(jìn)行epoll_wait都會返回該socket青灼。
  • ET:edge-trigger暴心,邊緣觸發(fā)模式,只有某個socket從unreadable變?yōu)閞eadable或從unwritable變?yōu)閣ritable時杂拨,epoll_wait才會返回該socket。

socket讀數(shù)據(jù)

socket寫數(shù)據(jù)

最后順便說下在Linux系統(tǒng)中JDK NIO使用的是 LT 悯衬,而Netty epoll使用的是 ET弹沽。

后記

因?yàn)楸救藢τ?jì)算機(jī)系統(tǒng)組成以及C語言等知識比較欠缺檀夹,因?yàn)槲闹邢嚓P(guān)知識點(diǎn)的表示也相當(dāng)“膚淺”,如有不對不妥的地方望讀者指出策橘。同時我也會繼續(xù)加強(qiáng)對該方面知識點(diǎn)的學(xué)習(xí)~

參考

http://www.reibang.com/p/0d497fe5484a
http://remcarpediem.com/2017/04/02/Netty源碼-三-I-O模型和Java-NIO底層原理/
圣思園netty課程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炸渡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丽已,更是在濱河造成了極大的恐慌蚌堵,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沛婴,死亡現(xiàn)場離奇詭異吼畏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嘁灯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門泻蚊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丑婿,你說我怎么就攤上這事性雄。” “怎么了羹奉?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵秒旋,是天一觀的道長。 經(jīng)常有香客問我诀拭,道長滩褥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任炫加,我火速辦了婚禮瑰煎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俗孝。我一直安慰自己酒甸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布赋铝。 她就那樣靜靜地躺著插勤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪革骨。 梳的紋絲不亂的頭發(fā)上农尖,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機(jī)與錄音良哲,去河邊找鬼盛卡。 笑死,一個胖子當(dāng)著我的面吹牛筑凫,可吹牛的內(nèi)容都是我干的滑沧。 我是一名探鬼主播并村,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼滓技!你這毒婦竟也來了哩牍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤令漂,失蹤者是張志新(化名)和其女友劉穎膝昆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叠必,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荚孵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挠唆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片处窥。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玄组,靈堂內(nèi)的尸體忽然破棺而出滔驾,到底是詐尸還是另有隱情,我是刑警寧澤俄讹,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布哆致,位于F島的核電站,受9級特大地震影響患膛,放射性物質(zhì)發(fā)生泄漏摊阀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一踪蹬、第九天 我趴在偏房一處隱蔽的房頂上張望胞此。 院中可真熱鬧,春花似錦跃捣、人聲如沸漱牵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酣胀。三九已至,卻和暖如春娶聘,著一層夾襖步出監(jiān)牢的瞬間闻镶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工丸升, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铆农,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓发钝,卻偏偏與公主長得像顿涣,于是被迫代替她去往敵國和親波闹。 傳聞我的和親對象是個殘疾皇子酝豪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容