1-netty源碼分析之Server

1-netty源碼分析之Server

  • 看netty源碼之后進行總結的第一篇筆記,無非幫助自己對于看代碼的一個總結,方便自己回顧學習;依然保持從demo出發,服務端、客戶端、線程模型、管道四篇核心點記錄;
一.demo出發,啟動server。
public final class EchoServer {
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(3);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     p.addLast(new EchoServerHandler());
                 }
             });
    
            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();
            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

以上就是netty啟動server的經典demo了,截自源碼;

  • 1.EventLoopGroup:netty框架中的reactor線程模型中"線程"就是由它提供,這個也是netty框架的核心概念之一,一個group可以包含多個EventLoop(即多個線程)。

  • 2.ServerBootstrap:netty服務端啟動引擎,也可以認為是netty啟動的輔助類,以build的方式組裝netty的相關配置及組件,最終串氣各個組件啟動服務;

  • 3.NioServerSocketChannel:服務端持有的channel,字面翻譯是“渠道”,那么它代表一個具體的與客戶端的連接,或者與IO相關的操作,它會和一個特定的EventLoop(即線程)綁定用以處理相關的IO操作;

  • 4.ChannelOption:socket相關參數,標識當服務器請求處理線程全滿時,用于臨時存放已完成三次握手的請求的隊列的最大長度

  • 5.Handler:處理器,處理的就是整個通信過程中的相關事件或者數據,我們擴展自己的handler就可以實現通信數據的業務處理了;

  • 6.ChannelInitializer:一種ChannelInboundHandler,看initChannel方法里,提供了ChannelPipeline用以組裝各種handler,那么ChannelInitializer就是組裝相關handler的作用;

  • 7.ChannelPipeline:理解為管道,那么管道里鏈路管理著各種handler,數據經過管道流向各種handler節點,處理后流出或者流入;

  • 8.ChannelFuture:jdk多線程的Future相似,異步處理的回調監聽結果,即在整個netty框架的異步操作成功或者失敗都會觸發監聽即可得到相關結果;

用一張圖將以上組件進行串起來:


image.png

啟動組件大致概念簡單介紹如此,后面詳細概念繼續以debug模式講解


1.EventLoopGroup初始化
EventLoopGroup bossGroup = new NioEventLoopGroup(3);
EventLoopGroup workerGroup = new NioEventLoopGroup();

看一下類圖:


image.png

前面講過,EventLoopGroup是netty線程模型中的線程部分,那么這里是怎么體現的呢?其實這里的父類MultithreadEventExecutorGroup里面封裝了一個EventExecutor[] 數組,而EventExecutor是一個接口,找到最下層的實現結構如下:

public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
    private final EventExecutor[] children;
    ...
}
image.png

這里可以想下NioEventLoop就是一個具體的“線程”了,為啥?依照上圖找到父類SingleThreadEventExecutor,可以看到里面包裝了一個成員變量Thread,有次可知:SingleThreadEventExecutor即是一個線程的抽象,因而NioEventLoop可以理解為一個線程了,相關的執行操作都是委托到里面的thread去執行。只不過NioEventLoop有更多的大于線程的能力,比如schedule等,這里代碼繼承關系可以提現。

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
    private final Thread thread;
    ...
}

知道了 EventLoopGroup 是一個線程數組,那么就回到初始化的地方,跟著代碼debug進入看看初始化的具體干了什么。

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
    this(nThreads, threadFactory, SelectorProvider.provider());
}

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
    this(nThreads, threadFactory, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {

    /** reject:設置任務隊列線程池拒絕策略,默認直接拋異常 */
    super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}

此處連續三次調用可以看出做了一些額外操作,分別是

  • 1.獲取SelectorProvider
  • 2.獲取SelectStrategyFactory
  • 3.有關任務隊列的拒絕策略:RejectedExecutionHandlers.reject()

繼續往下:


image.png
image.png

根據debug可以看出,這里有個線程數的默認值,根據當前處理器核心數 * 2計算得出,當然我們傳的是3,因此直接使用3創建一個new SingleThreadEventExecutor[nThreads]數組;核心初始化線程如下:

protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
    ...
    /** 創建一個大小為 nThreads 的 SingleThreadEventExecutor 數組 */
    children = new SingleThreadEventExecutor[nThreads];

    /**
     * 根據 nThreads 的大小, 創建不同的 Chooser,
     * 即如果 nThreads 是 2 的冪, 則使用 PowerOfTwoEventExecutorChooser, 反之使用 GenericEventExecutorChooser.
     * 不論使用哪個 Chooser, 它們的功能都是一樣的, 即從 children 數組中選出一個合適的 EventExecutor 實例.
     */
    if (isPowerOfTwo(children.length)) {
        chooser = new PowerOfTwoEventExecutorChooser();
    } else {
        chooser = new GenericEventExecutorChooser();
    }

    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {

            /**
             * 調用 newChhild 方法初始化 children 數組.
             * 具體子類實現
             */
            children[i] = newChild(threadFactory, args);
            success = true;
        } catch (Exception e) {
            ...
        } finally {
            if (!success) {
               ...
            }
        }
    }
}

主要干了這兩件事:

  • 1.根據線程數創建一個ExecutorChooser,其實這里也是體現netty對于性能的追求
  • 2.調用模板方法創建具體的 線程

進入子類具體的方法:NioEventLoopGroup


image.png
NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, threadFactory, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
    if (selectorProvider == null) {
        throw new NullPointerException("selectorProvider");
    }
    if (strategy == null) {
        throw new NullPointerException("selectStrategy");
    }
    provider = selectorProvider;
    final SelectorTuple selectorTuple = openSelector();
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}

一連串的super調用之后進入核心線程創建方法:


image.png
protected SingleThreadEventExecutor(EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
    if (threadFactory == null) {
        throw new NullPointerException("threadFactory");
    }

    this.parent = parent;
    this.addTaskWakesUp = addTaskWakesUp;

    thread = threadFactory.newThread(new Runnable() {
        @Override
        public void run() {
            boolean success = false;
            updateLastExecutionTime();
            try {

                /** 多態,調用NioEventLoop的run方法 */
                SingleThreadEventExecutor.this.run();
                success = true;
            } catch (Throwable t) {
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
                ...
            }
        }
    });
    ...
}

可以看到這里初始化了具體的線程,并且指定了run方法的調用執行為:

SingleThreadEventExecutor.this.run();

那這里的run方法就是EventLoop的核心了,干了什么后面詳細說明,先看下run方法干了什么:

/**
 * Netty 的事件循環機制
 * 當 taskQueue 中沒有任務時, 那么 Netty 可以阻塞地等待 IO 就緒事件;
 * 而當 taskQueue 中有任務時, 我們自然地希望所提交的任務可以盡快地執行, 因此 Netty 會調用非阻塞的 selectNow() 方法, 以保證 taskQueue 中的任務盡快可以執行.
 *
 * 1.輪詢IO事件
 * 2.處理輪詢到的事件
 * 3.執行任務隊列中的任務
 * */
@Override
protected void run() {
    for (;;) {
        try {
            /** 如果任務隊列沒有任務,則進行一次selectNow() */
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:

                    /**
                     * 輪詢出注冊在selector上面的IO事件
                     *
                     * wakenUp 表示是否應該喚醒正在阻塞的select操作,
                     * 可以看到netty在進行一次新的loop之前,都會將wakeUp 被設置成false,標志新的一輪loop的開始
                     */
                    select(wakenUp.getAndSet(false));
                    
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                    // fall through
                default:
            }

            cancelledKeys = 0;

            /**
             * 第一步是通過 select/selectNow 調用查詢當前是否有就緒的 IO 事件. 那么當有 IO 事件就緒時, 第二步自然就是處理這些 IO 事件啦.
             */
            needsToSelectAgain = false;

            /**
             * 此線程分配給 IO 操作所占的時間比
             * 即運行 processSelectedKeys 耗時在整個循環中所占用的時間
             */
            final int ioRatio = this.ioRatio;

            /** 當 ioRatio 為 100 時, Netty 就不考慮 IO 耗時的占比, 而是分別調用 processSelectedKeys()、runAllTasks();   */
            if (ioRatio == 100) {
                try {

                    /** 查詢就緒的 IO 事件后 進行處理 */
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    /** 運行 taskQueue 中的任務. */
                    runAllTasks();
                }
            }

            /**
             * ioRatio 不為 100時, 則執行到 else 分支, 在這個分支中, 首先記錄下 processSelectedKeys() 所執行的時間(即 IO 操作的耗時),
             * 然后根據公式, 計算出執行 task 所占用的時間, 然后以此為參數, 調用 runAllTasks().
             */
            else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
           ...
    }
}

哈哈,無限死循環,無非就是輪訓,那么netty的reactor線程模型就此方法為核心點跟進即可;
EventLoopGroup初始化先講到這里,線程模型后面再詳細筆記。


二.ServerBootstrap組裝組件
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
 .channel(NioServerSocketChannel.class)
 .option(ChannelOption.SO_BACKLOG, 100)
 .handler(new LoggingHandler(LogLevel.INFO))
 .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
         if (sslCtx != null) {
             p.addLast(sslCtx.newHandler(ch.alloc()));
         }
         p.addLast(new EchoServerHandler());
     }
 });
  • 1.組裝兩個EventGroup:bossGroup, workerGroup;bossGroup用來專門負責綁定到端口監聽連接事件,workerGroup用來處理每個接收到的連接


    image.png
image.png

可以看到bossGroup只是簡單的賦值給父類的成員變量,workerGroup賦值給ServerBootstrap的ServerBootstrap的childGroup屬性,既簡單的賦值而已;

  • 2.組裝channel


    image.png

這里不是將傳入的NioServerSocketChannel作為類型構造一個工廠類賦值給自己的channelFactory,后續在啟動初始化時利用其構造channel;

  • 3.option、handler、childHandler只是簡單的賦值而已,不多解釋;ChannelInitializer在前面講過,用以封裝業務handler鏈,后面啟動時會講解。

三.啟動服務
ChannelFuture f = b.bind(PORT).sync();

可以說啟動的核心邏輯就在這里,確實做了不少事情,可以想象nio多路復用、事件輪訓、selector與channel的注冊、channel與pipeline的綁定、channel與eventLoop的綁定等等都在這一步做的,那么詳細debug走起。


image.png

進入核心方法doBind;

  • initAndRegister
final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        /** 初始化channel --> client 獲取 NioSocketChannel;  --> server 獲取 ServerSocketChannel*/
        channel = channelFactory().newChannel();
        init(channel);
    } catch (Throwable t) {
        ...
    }
    /**
     * Channel 注冊過程:
     * 1.將 Channel 與對應的 EventLoop 關聯,
     * 因此這也體現了, 在 Netty 中, 每個 Channel 都會關聯一個特定的 EventLoop, 并且這個 Channel 中的所有 IO 操作都是在這個 EventLoop 中執行的;
     *
     * 2.當關聯好 Channel 和 EventLoop 后, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 注冊到指定的 selector 中.
     * 通過這兩步, 就完成了 Netty Channel 的注冊過程.
     *
     * 3.若是服務端注冊,則group()返回的是bossGroup
     */
    ChannelFuture regFuture = group().register(channel);
    return regFuture;
}

做了三件事:

  • 1.根據之前構造的工廠new一個channel
  • 2.將創建的channel進行一系列初始化動作
  • 3.將channel與對應的EventLoopGroup關聯,即channel綁定到指定的線程;

一個一個進行分解:

  • 1.new channel


    image.png

利用構造器進行instance那么找到構造器:


image.png

1.構造器中直接調用SelectorProvider打開一個ServerSocketChannel,可以看到這一步到了與nio交互了;
2.直接super父類構造中,不斷super就會到AbstractChannel中核心點


image.png

看上面三行:

  • 1.將channel進行賦值,此時為空

  • 2.構造一個UnSafe對象,這里netty真正的讀寫等IO事件都是交給UnSafe去操作的,這里返回的是一個NioMessageUnsafe,服務端需要的UnSafe對象,將新的連接注冊到worker線程組【netty將一個新連接的建立也當作一個io操作來處理,這里的Message的含義我們可以當作是一個SelectableChannel,讀的意思就是accept一個SelectableChannel,寫的意思是針對一些無連接的協議,比如UDP來操作的】

  • 3.創建一個數據自己的piepline,用以后續組裝handler

    protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
    
        /** 維護了一個以 AbstractChannelHandlerContext 為節點的雙向鏈表 */
        tail = new TailContext(this);
        head = new HeadContext(this);
    
        head.next = tail;
        tail.prev = head;
    }
    

這里是上面構造piepline的方法,可以看到這里的鏈表結構,首先將channel綁定自己,然后構造head,tail收尾節點,同時這里對對于head,tail類型是ChannelHandlerContext,理解為一個handler的context,可以做一些額外的動作。


image.png

可以看到這里有個重要的屬性inbound,這個是干啥的呢?
ChannelHandler有兩個子類ChannelInboundHandler和ChannelOutboundHandler,這兩個類對應了兩個數據流向,如果數據是從外部流入我們的應用程序,我們就看做是inbound,相反便是outbound,因此這個代表handler的流向節點意思了; 同時head和tail都要與具體的UnSafe綁定,因為這里是數據流向的首尾節點,那么自然就是具體的非讀即寫數據了,此處自然交給綁定的UnSafe去操作,因此這就是綁定UnSafe的理由了。
OK,new channel介紹到這里,繼續回到initAndRegister中的init方法:

  • 2.init(channel)

    @Override
    void init(Channel channel) throws Exception {
    ...
    ChannelPipeline p = channel.pipeline();
    ...
    /**
     * 加入新連接處理器,用來專門接受新連接
     * 初始化channel時,加入匿名ChannelHandler, 作用就是在register channel到selector時回調init方法,將boss上的handler加入pipeline中,并且ServerBootstrapAcceptor handler用以綁定childGroup和NioSocketChannel
     */
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
    
            /** 這里的handler返回的是主.handler(new LoggingHandler(LogLevel.INFO)) 中的handler*/
            ChannelHandler handler = handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
    
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    /** 這里的 childGroup.register 就是將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯 */
                    pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
    }
    

    省略非主要代碼,這里拿到構造的pipeline,然后加入一個ChannelInitializer的handler,并且植入一個initChannel方法,說下initChannel的作用【該方法當然此時不會執行】:

    • 1.獲取主handler并加入piepline

    • 2.在taskQueu中offer一個任務,具體執行的是將ServerBootstrapAcceptor加入到pieline中 ,而ServerBootstrapAcceptor主要作用是將workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯,具體如何觸發在后面client發起連接時會細說。

    initChannel做的事不多,繼續回到initAndRegister方法

  • 3.group().register(channel)

    /**
     * Channel 注冊過程:
     * 1.將 Channel 與對應的 EventLoop 關聯,
     * 因此這也體現了, 在 Netty 中, 每個 Channel 都會關聯一個特定的 EventLoop, 并且這個 Channel 中的所有 IO 操作都是在這個 EventLoop 中執行的;
     *
     * 2.當關聯好 Channel 和 EventLoop 后, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 注冊到指定的 selector 中.
     * 通過這兩步, 就完成了 Netty Channel 的注冊過程.
     *
     * 3.若是服務端注冊,則group()返回的是bossGroup
     */
    ChannelFuture regFuture = group().register(channel);
    

    其實前面講過,每個channel都會綁定一個EventLoop用以專門處理跟此Channel相關的IO事件,看代碼跟蹤,這里先記一下bossGroup對象:


    image.png
image.png

可以很明顯的跟蹤到這里group()返回的就是bossGroup,繼續往下走:MultithreadEventLoopGroup的register方法體:首先解剖下next()做了什么:

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}
@Override
public EventLoop next() {
    /** 獲取一個可用的 SingleThreadEventLoop */
    return (EventLoop) super.next();
}
image.png

這里看到了chooser,也就是EventExecutorChooser,記得我們當初設置的是3,非2的冪,初始化EventLopp的時候,因此取得GenericEventExecutorChooser類型的選擇器:

if (isPowerOfTwo(children.length)) {
    chooser = new PowerOfTwoEventExecutorChooser();
} else {
    chooser = new GenericEventExecutorChooser();
}
private final class GenericEventExecutorChooser implements EventExecutorChooser {
    @Override
    public EventExecutor next() {
        return children[Math.abs(childIndex.getAndIncrement() % children.length)];
    }
}

看邏輯abs算法出一個主線程,用以實際的操作,其實這里有個很重要的要點明:
主線程無論初始化多少,最終執行操作的永遠只有一個線程,因此這里在初始化EventLoopGroup bossGroup = new NioEventLoopGroup(3);時直接將參數設為1即可

繼續回到register:


image.png
image.png
image.png
image.png

生成DefaultChannelPromise后,調用UnSafe對象進行 register進行具體的底層相關的注冊操作,這就實現了注釋中的第二部:

2.當關聯好 Channel 和 EventLoop 后, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 注冊到指定的 selector 中.通過這兩步, 就完成了 Netty Channel 的注冊過程.

這里由于是用戶線程,因此轉成EventLoop的任務扔進隊列里等待去執行,于是會有異步回調就靠這個ChannelPromise了。什么時候會執行任務,那就要先啟動線程:
繼續跟蹤代碼:


image.png

具體做了:

  • 1.判斷是不是EventLoop線程,如果是直接offer任務;

  • 2.如果使用戶線程,先啟動線程EventLoop線程,再offer任務

那么很明顯清楚,這里對任務進行了歸一處理,猜想啟動了EventLoop線程后,大概就是輪訓task隊列的任務了,那么繼續跟蹤啟動的邏輯:

image.png

這里的thread到底是什么,回到EventLoop初始化的地方:

protected SingleThreadEventExecutor(EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
    thread = threadFactory.newThread(new Runnable() {
        @Override
        public void run() {
        
            try {

                /** 多態,調用NioEventLoop的run方法 */
                SingleThreadEventExecutor.this.run();
                success = true;
            } catch (Throwable t) {
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
            }
        }
    });
}

標注核心邏輯,這里的run就是當初的那個NioEventLoop的具體實現的run,防止走丟,再貼一次代碼:

/**
 * Netty 的事件循環機制
 * 當 taskQueue 中沒有任務時, 那么 Netty 可以阻塞地等待 IO 就緒事件;
 * 而當 taskQueue 中有任務時, 我們自然地希望所提交的任務可以盡快地執行, 因此 Netty 會調用非阻塞的 selectNow() 方法, 以保證 taskQueue 中的任務盡快可以執行.
 *
 * 1.輪詢IO事件
 * 2.處理輪詢到的事件
 * 3.執行任務隊列中的任務
 * */
@Override
protected void run() {
    for (;;) {
        try {
            /** 如果任務隊列沒有任務,則進行一次selectNow() */
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:

                    /**
                     * 輪詢出注冊在selector上面的IO事件
                     *
                     * wakenUp 表示是否應該喚醒正在阻塞的select操作,
                     * 可以看到netty在進行一次新的loop之前,都會將wakeUp 被設置成false,標志新的一輪loop的開始
                     */
                    select(wakenUp.getAndSet(false));

                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                    // fall through
                default:
            }

            cancelledKeys = 0;

            /**
             * 第一步是通過 select/selectNow 調用查詢當前是否有就緒的 IO 事件. 那么當有 IO 事件就緒時, 第二步自然就是處理這些 IO 事件啦.
             */
            needsToSelectAgain = false;

            /**
             * 此線程分配給 IO 操作所占的時間比
             * 即運行 processSelectedKeys 耗時在整個循環中所占用的時間
             */
            final int ioRatio = this.ioRatio;

            /** 當 ioRatio 為 100 時, Netty 就不考慮 IO 耗時的占比, 而是分別調用 processSelectedKeys()、runAllTasks();   */
            if (ioRatio == 100) {
                try {

                    /** 查詢就緒的 IO 事件后 進行處理 */
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    /** 運行 taskQueue 中的任務. */
                    runAllTasks();
                }
            }

            /**
             * ioRatio 不為 100時, 則執行到 else 分支, 在這個分支中, 首先記錄下 processSelectedKeys() 所執行的時間(即 IO 操作的耗時),
             * 然后根據公式, 計算出執行 task 所占用的時間, 然后以此為參數, 調用 runAllTasks().
             */
            else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
            ...
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

看到輪訓了,那么EventLoop線程啟動了,很自然就能找到當時人進去的任務,其中有個register0的邏輯,線程啟動了,自認就該執行任務了,回到注冊邏輯:


image.png
image.png
image.png

出現了selectionKey,這一步就將 Channel 對應的 Java NIO SockerChannel 注冊到一個 eventLoop 的 Selector 中, 并且將當前 Channel 作為 attachment.回到register0

pipeline.invokeHandlerAddedIfNeeded()

這句是干嘛的呢? 還記得初始化channel--> init(channel)的方法嗎,里面有個動作是這樣的:

p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) throws Exception {
        final ChannelPipeline pipeline = ch.pipeline();

        /** 這里的handler返回的是主.handler(new LoggingHandler(LogLevel.INFO)) 中的handler*/
        ChannelHandler handler = handler();
        if (handler != null) {
            pipeline.addLast(handler);
        }

        ch.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                /** 這里的 childGroup.register 就是將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯 */
                pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });
    }
});

這里的invokeHandlerAddedIfNeeded就會觸發這個ChannelInitializer中的initChannel方法,然后順其自然的將ChannelHandler加進piepline,緊接著觸發ServerBootstrapAcceptor將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯;很隨意的將整個線程run起來。

跟蹤一下:


image.png
image.png

執行效果確實如此。
回過頭看下ChildHandler是不是跟這個時候的一樣,比較一下:


image.png
image.png

經對比,確實如此,可以說這里就是將childHandler加入piepline的地方了。
還記的我們在初始化ServerBootstrap時有b.group(bossGroup, workerGroup)這樣一個組裝線程的地方,但是會發現整個服務端啟動過程都不會涉及workerGroup相關的啟動,其實這里也是關鍵,這里先將workGroup賦值給ServerBootstrap#ServerBootstrapAcceptor的屬性,在客戶端發起請求時觸發channelRead方法,緊接著就啟動了workGroup,進而輪訓處理相關的IO事件啦,看下代碼:

/**
 * 這里講workerGroup綁定到channel,那么這里如何被觸發呢?
 * 其實當一個 client 連接到 server 時, Java 底層的 NIO ServerSocketChannel 會有一個 SelectionKey.OP_ACCEPT 就緒事件, 接著就會調用到 NioServerSocketChannel.doReadMessages
 *
 * 新建此連接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 對應的 pipeline 中, 并將此 channel 綁定到 workerGroup 中的某個 eventLoop 中
 */
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    /** 將msg轉換成對應的channel */
    final Channel child = (Channel) msg;

    /** 添加用戶自定義的childHandler */
    child.pipeline().addLast(childHandler);

    /** 設置 NioSocketChannel 對應的 attr和option */
    setChannelOptions(child, childOptions, logger);

    for (Entry<AttributeKey<?>, Object> e: childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }

    try {

        /** 將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯 */
        childGroup.register(child).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    forceClose(child, future.cause());
                }
            }
        });
    } catch (Throwable t) {
        forceClose(child, t);
    }
}

register就跟bossGroup類似,調用UnSafe去完成具體的操作,中間又把相關的用戶線程收冗成EventLoop進行具體任務操作,完成一系列動作。

OK,大致注冊先介紹到這里

到此為止,整個channel注冊過程完成,繼續回到起點

  • 4.doBind
image.png
image.png

經過piepline事件的傳遞,走到AbstractChannel#AbstractUnsafe的bind方法:


image.png
image.png

到這里,調用底層的socketChannel進行具體adress綁定工作,整個bind結束。接下來就會調用pipeline.fireChannelActive();進行下達工作了。

下面進行個總結

  • 1.初始化相關的組件
  • 2.設置好EventLoop,包括bossGroup及workGroup
  • 3.初始化channel,并且綁定EventLoop
  • 4.啟動線程,將用戶線程進行的動作(比如注冊、綁定等)進行任務化,交給EventLoop處理
  • 5.BossEventLoop輪訓事件,接受客戶端請求,觸發WorkerEventLoop啟動處理IO讀寫等操作
  • 6.進行任務處理,主要包括將 Channel 對應的 Java NIO SockerChannel 注冊到一個 eventLoop 的 Selector 中,并綁定到具體的地址進行監聽。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容