天天看點

Netty原理二:NioEventLoop 如何啟動監聽事件前言原了解析總結

文章目錄

  • 前言
  • 原了解析
  • 總結

前言

在NIO程式設計中,Server 通常會寫一個無限循環的代碼,Selector 持續監聽新的事件:

//NIO
        System.out.println("等待連接配接....");
        while (true){
            selector.select();
            //處理 selector.selectedKeys 事件
        }
           

但是Netty程式隻需要啟動 ServerBootstrap,然後就開啟監聽事件了,那它是如何處理的?

//Netty
        ChannelFuture future = serverBootstrap.bind(7000).sync();
        System.out.println("伺服器已啟動,端口:7000");
           

原了解析

上一篇文章我們介紹了 ServerBootstrap啟動過程全解析 ,其中講到說 ServerBootstrap 啟動的時候會将 NioServerSocketChannel 注冊到 BossGroup 内部的 NioEventLoop,這個步驟是由 NioEventLoop 内部開啟的線程異步執行,如下圖紅框部分所示:

Netty原理二:NioEventLoop 如何啟動監聽事件前言原了解析總結

答案也就在這裡,外部調用 eventLoop.execute(new Runnable(){ … }) 在EventLoop 獨立線程執行任務。EventLoop 是一個單線程的線程池,内部維護一個 Thread 對象,優先判斷線程是否啟動了,如果沒有就建立新線程,反之不處理。

//SingleThreadEventExecutor.java
    private void execute(Runnable task, boolean immediate) {
        boolean inEventLoop = inEventLoop();
        addTask(task);
        //這裡判斷目前線程是否EventLoop開啟的線程
        if (!inEventLoop) {
        	//這句是關鍵
            startThread();
            
            //以下省略...
        }
    }
           

在 startThread() 内部有一句 SingleThreadEventExecutor.this.run(),該方法就是無限循環監聽的實作(這裡删掉了部分無關代碼):

protected void run() {
        int selectCnt = 0;
        for (;;) {
                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                switch (strategy) {
                case SelectStrategy.CONTINUE:
                    continue;

                case SelectStrategy.BUSY_WAIT:
                    // fall-through to SELECT since the busy-wait is not supported with NIO

                case SelectStrategy.SELECT:
                    long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                    if (curDeadlineNanos == -1L) {
                        curDeadlineNanos = NONE; // nothing on the calendar
                    }
                    nextWakeupNanos.set(curDeadlineNanos);
                    try {
                        if (!hasTasks()) {
                            strategy = select(curDeadlineNanos);
                        }
                    } finally {
                        nextWakeupNanos.lazySet(AWAKE);
                    }

                selectCnt++;
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                if (ioRatio == 100) {
                    try {
                        if (strategy > 0) {
                            processSelectedKeys();
                        }
                    } finally {
                        // Ensure we always run tasks.
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }

                if (ranTasks || strategy > 0) {
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;
                } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
                    selectCnt = 0;
                }
            } finally {
                // Always handle shutdown even if the loop processing threw an exception.
                if (isShuttingDown()) {
                      closeAll();
                      if (confirmShutdown()) {
                          return;
                      }
                  }
            }
        }
    }
           

上面這段代碼比較長,用一個簡單的流程圖表示就是這樣:

Netty原理二:NioEventLoop 如何啟動監聽事件前言原了解析總結

總結

NioEventLoop 在第一次建立線程的時候,會調用其父類 SingleThreadEventExecutor 的 run 方法,在這個方法内部啟動無限循環,監聽并處理事件。