NIO框架之MINA原始碼解析(二):mina核心引擎

chaofanwei發表於2014-09-03

NIO框架之MINA原始碼解析(一):背景



MINA的底層還是利用了jdk提供了nio功能,mina只是對nio進行封裝,包括MINA用的執行緒池都是jdk直接提供的。


MINA的server端主要有accept、processor、session三部分組成的。其中accept主要負責在指定的埠監聽,若有新連線則建立一個新的session;processor則負責處理session對應的傳送資料和接收資料並呼叫上層處理;session則快取當前連線資料

MINA採用了執行緒懶啟動的技術,即最少啟動執行緒,在MINA server啟動的時候,只有一個執行緒-accept,並且accept執行緒只有一個,在指定的埠進行監聽(可以同時監聽多個埠,mina可以繫結多埠)。


1、acceptor


先看下acceptor的主要類圖吧。



mina server的啟動入口是在NioSocketAcceptor.bind(InetSocketAddress)或者NioSocketAcceptor.bind(SocketAddress...)方法, acceptor.bind(new InetSocketAddress(1234));  
然後會呼叫AbstractPollingIoAcceptor.bindInternal(List<? extends SocketAddress>)方法,在bindInternal方法裡面會呼叫startupAcceptor()方法提交一個accept執行緒到執行緒池裡面(只提交一次),並初始化acceptor端的Selector,就這樣一個acceptor執行緒啟動了。

acceptor端業務相對簡單,相當於在當前Selector裡面監聽acceptor事件,處理新連線並新建一個session放到對應的processor裡面。




acceptor 程式碼,很簡單。

 private class Acceptor implements Runnable {
        public void run() {
            assert (acceptorRef.get() == this);

            int nHandles = 0;

            // Release the lock
            lock.release();

            while (selectable) {
                try {
                    // Detect if we have some keys ready to be processed
                    // The select() will be woke up if some new connection
                    // have occurred, or if the selector has been explicitly
                    // woke up
                    int selected = select();

                    // this actually sets the selector to OP_ACCEPT,
                    // and binds to the port on which this class will
                    // listen on  在通道里面註冊連線事件
                    nHandles += registerHandles();

                    // Now, if the number of registred handles is 0, we can
                    // quit the loop: we don't have any socket listening
                    // for incoming connection.
                    if (nHandles == 0) {
                        acceptorRef.set(null);

                        if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {
                            assert (acceptorRef.get() != this);
                            break;
                        }

                        if (!acceptorRef.compareAndSet(null, this)) {
                            assert (acceptorRef.get() != this);
                            break;
                        }

                        assert (acceptorRef.get() == this);
                    }

                    if (selected > 0) {
                        // We have some connection request, let's process
                        // them here.處理連線
                        processHandles(selectedHandles());
                    }

                    // check to see if any cancellation request has been made.
                    nHandles -= unregisterHandles();
                } catch (ClosedSelectorException cse) {
                    // If the selector has been closed, we can exit the loop
                    break;
                } catch (Throwable e) {
                    ExceptionMonitor.getInstance().exceptionCaught(e);

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e1) {
                        ExceptionMonitor.getInstance().exceptionCaught(e1);
                    }
                }
            }

            // Cleanup all the processors, and shutdown the acceptor.
            if (selectable && isDisposing()) {
                selectable = false;
                try {
                    if (createdProcessor) {
                        processor.dispose();
                    }
                } finally {
                    try {
                        synchronized (disposalLock) {
                            if (isDisposing()) {
                                destroy();
                            }
                        }
                    } catch (Exception e) {
                        ExceptionMonitor.getInstance().exceptionCaught(e);
                    } finally {
                        disposalFuture.setDone();
                    }
                }
            }
        }

2、processor



processor顧名思義,就是進行IO處理,處理當前session的資料讀寫,並進行業務處理。


在mina server初始化的時候,會初始化一個processor池,通過NioSocketAcceptor的構造器傳入池的大小,預設是當前處理器的個數+1。


processor池裡面有一個jdk提供的 執行緒池 - Executors.newCachedThreadPool()。各個processor執行緒會引用此執行緒池,即每個processor執行緒都在這個執行緒池裡面執行。


在mina server實際處理時,每個processor相當於一個執行緒,輪流處理當前的session佇列裡面的資料(每個processor裡面的session相當於順序處理,共享一個執行緒)。


每個processor有一個Selector物件


processor類圖



processor端的處理邏輯相對有點複雜,看下面的流程圖。




1、把新新增進來的session註冊到當前processor的Selector裡面的read事件,並初始化session;
2、判斷當前Selector是否有讀寫事件;
3、若第2步有讀事件時,進入步驟4,若沒有的話,直接到第6步;
4、處理當前讀事件,並把處理後的資料放入到flush佇列裡面;
5、把第4步執行的結果flush到客戶端;
6、處理session,比如session idle時間等。
7、重新執行第1步,迴圈執行。


processor端程式碼。

 private class Processor implements Runnable {
        public void run() {
            assert (processorRef.get() == this);

            int nSessions = 0;
            lastIdleCheckTime = System.currentTimeMillis();

            for (;;) {
                try {
                    // This select has a timeout so that we can manage
                    // idle session when we get out of the select every
                    // second. (note : this is a hack to avoid creating
                    // a dedicated thread).
                    long t0 = System.currentTimeMillis();
                    int selected = select(SELECT_TIMEOUT);
                    long t1 = System.currentTimeMillis();
                    long delta = (t1 - t0);

                    if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) {
                        // Last chance : the select() may have been
                        // interrupted because we have had an closed channel.
                        if (isBrokenConnection()) {
                            LOG.warn("Broken connection");

                            // we can reselect immediately
                            // set back the flag to false
                            wakeupCalled.getAndSet(false);

                            continue;
                        } else {
                            LOG.warn("Create a new selector. Selected is 0, delta = " + (t1 - t0));
                            // Ok, we are hit by the nasty epoll
                            // spinning.
                            // Basically, there is a race condition
                            // which causes a closing file descriptor not to be
                            // considered as available as a selected channel, but
                            // it stopped the select. The next time we will
                            // call select(), it will exit immediately for the same
                            // reason, and do so forever, consuming 100%
                            // CPU.
                            // We have to destroy the selector, and
                            // register all the socket on a new one.
                            registerNewSelector();
                        }

                        // Set back the flag to false
                        wakeupCalled.getAndSet(false);

                        // and continue the loop
                        continue;
                    }

                    // Manage newly created session first  處理新新增進來的session,並註冊到當前processor的Selector讀事件
                    nSessions += handleNewSessions();

                    updateTrafficMask();

                    // Now, if we have had some incoming or outgoing events,
                    // deal with them
                    if (selected > 0) {
                        //LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test...處理讀事件,並把結果放入flush佇列裡面
                        process();
                    }

                    // Write the pending requests  把flush佇列裡面的session的處理完的資料傳送給客戶端
                    long currentTime = System.currentTimeMillis();
                    flush(currentTime);

                    // And manage removed sessions
                    nSessions -= removeSessions();

                    // Last, not least, send Idle events to the idle sessions
                    notifyIdleSessions(currentTime);

                    // Get a chance to exit the infinite loop if there are no
                    // more sessions on this Processor
                    if (nSessions == 0) {
                        processorRef.set(null);

                        if (newSessions.isEmpty() && isSelectorEmpty()) {
                            // newSessions.add() precedes startupProcessor
                            assert (processorRef.get() != this);
                            break;
                        }

                        assert (processorRef.get() != this);

                        if (!processorRef.compareAndSet(null, this)) {
                            // startupProcessor won race, so must exit processor
                            assert (processorRef.get() != this);
                            break;
                        }

                        assert (processorRef.get() == this);
                    }

                    // Disconnect all sessions immediately if disposal has been
                    // requested so that we exit this loop eventually.
                    if (isDisposing()) {
                        for (Iterator<S> i = allSessions(); i.hasNext();) {
                            scheduleRemove(i.next());
                        }

                        wakeup();
                    }
                } catch (ClosedSelectorException cse) {
                    // If the selector has been closed, we can exit the loop
                    break;
                } catch (Throwable t) {
                    ExceptionMonitor.getInstance().exceptionCaught(t);

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e1) {
                        ExceptionMonitor.getInstance().exceptionCaught(e1);
                    }
                }
            }

            try {
                synchronized (disposalLock) {
                    if (disposing) {
                        doDispose();
                    }
                }
            } catch (Throwable t) {
                ExceptionMonitor.getInstance().exceptionCaught(t);
            } finally {
                disposalFuture.setValue(true);
            }
        }
    }

3、session


session做為一個連線的具體物件,快取當前連線使用者的一些資訊。

session類圖



session物件是繫結在SelectableChannel的一個attach。


class  NioProcessor
 @Override
    protected void init(NioSession session) throws Exception {
        SelectableChannel ch = (SelectableChannel) session.getChannel();
        ch.configureBlocking(false);
        session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session));//chanel 註冊讀事件,並把session當做一個attach輔導SelectableChannel裡面。
    }



相關文章