Netty源碼分析(2)-服務端啓動流程

1. 服務端啓動步驟

一個 Netty 服務端配置啓動代碼如下,其大略的流程爲以下幾個步驟:

  1. 創建 NioEventLoopGroup 實例,這個類是 Netty 的 Reactor 線程池實現之一,其實際爲 EventLoop 的容器,而 EventLoop 的主要職責是處理所有註冊到本線程多路複用器 Selector 上的 Channel。代碼中創建了兩個 NioEventLoopGroup 實例,這是 主從 Reactor 多線程模式 的體現
  2. 創建 ServerBootstrap 實例,該類是 Netty 服務端的啓動輔助類,提供一系列方法用於設置服務端啓動相關的參數
  3. ServerBootstrap 對象調用 group() 方法設置並綁定主從 Reactor 線程池
  4. ServerBootstrap 對象調用 channel() 方法設置服務端 Channel 實例類型
  5. ServerBootstrap 對象調用 handler() 方法設置服務端 MainReactor 線程池業務處理器
  6. ServerBootstrap 對象調用帶 child 前綴的方法設置服務端 SubReactor 線程池配置,比如 childHandler() 方法設置SubReactor 線程池業務處理器
  7. ServerBootstrap 對象調用 bind() 方法綁定並啓動監聽端口
public class NettyServer {

    private static final int DEFAULT_PORT = 10086;

    public static void start() {
        new Thread(() -> {
            // 創建監聽線程組, 監聽客戶端請求
            NioEventLoopGroup bossGroup = new NioEventLoopGroup();
            // 創建工作線程組, 處理請求
            NioEventLoopGroup workerGroup = new NioEventLoopGroup();

            // 服務器輔助啓動類配置
            new ServerBootstrap().group(bossGroup, workerGroup)
                    // 設置 channel 類型爲NIO類型
                    .channel(NioServerSocketChannel.class)
                    // 設置連接配置參數
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .handler(new LoggingHandler())
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 配置入站、出站事件handler
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            // 配置入站、出站事件channel
                            ch.pipeline()
                                    .addLast(new LineBasedFrameDecoder(1024))
                                    .addLast(new StringDecoder(CharsetUtil.UTF_8))
                                    .addLast(new StringEncoder(CharsetUtil.UTF_8))
                                    .addLast(new ServerHandler());
                        }
                    })
                    // 綁定端口
                    .bind(DEFAULT_PORT)
                    .addListener(future -> {
                        if (future.isSuccess()) {
                            System.out.println(new Date() + ": 端口[" + DEFAULT_PORT + "]綁定成功!");
                        } else {
                            System.err.println("端口[" + DEFAULT_PORT + "]綁定失敗!");
                        }
                    });
        }, "Server").start();
    }
}

2. 服務端啓動流程詳解

服務端啓動流程圖如下,各組件間的關係可參考 Netty源碼分析(1)-核心組件與架構,其主要流程可以分爲以下幾個部分:

  1. 事件分發組件的配置及初始化
  2. 業務處理組件 Channel 的初始化及註冊,註冊過程即事件循環線程的啓動過程
  3. 綁定服務端 ServerSocketChannel 到指定的端口

在這裏插入圖片描述

2.1 事件分發組件配置及初始化

這部分對應以上流程圖步驟 1-5,其中比較關鍵的流程如下:

  1. new NioEventLoopGroup() 創建 Reactor 線程池實例,其關鍵邏輯在父類 MultithreadEventExecutorGroup 的構造方法中,重要步驟如下:

    1. new ThreadPerTaskExecutor(new DefaultThreadFactory()) 生成 Executor 實例,並指定其線程工廠
    2. 調用 newChild() 方法爲當前 group 新建 NioEventLoop 實例,並指定其 Executor 入參爲 ThreadPerTaskExecutor 對象,該對象後續將用於創建和啓動 EventLoop 線程
    3. 如果有一個 NioEventLoop 實例新建失敗,調用已創建的每個 NioEventLoop 實例的 shutdownGracefully() 方法啓動事件循環線程
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                             EventExecutorChooserFactory chooserFactory, Object... args) {
         if (nThreads <= 0) {
             throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
         }
    
         if (executor == null) {
             // #1
             executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
         }
    
         children = new EventExecutor[nThreads];
    
         for (int i = 0; i < nThreads; i ++) {
             boolean success = false;
             try {
                 // #2
                 children[i] = newChild(executor, args);
                 success = true;
             } catch (Exception e) {
                 // TODO: Think about if this is a good exception type
                 throw new IllegalStateException("failed to create a child event loop", e);
             } finally {
                 if (!success) {
                     for (int j = 0; j < i; j ++) {
                         // #3
                         children[j].shutdownGracefully();
                     }
    
                     for (int j = 0; j < i; j ++) {
                         EventExecutor e = children[j];
                         try {
                             while (!e.isTerminated()) {
                                 e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                             }
                         } catch (InterruptedException interrupted) {
                             // Let the caller handle the interruption.
                             Thread.currentThread().interrupt();
                             break;
                         }
                     }
                 }
             }
         }
    
         chooser = chooserFactory.newChooser(children);
    
         final FutureListener<Object> terminationListener = new FutureListener<Object>() {
             @Override
             public void operationComplete(Future<Object> future) throws Exception {
                 if (terminatedChildren.incrementAndGet() == children.length) {
                     terminationFuture.setSuccess(null);
                 }
             }
         };
    
         for (EventExecutor e: children) {
             e.terminationFuture().addListener(terminationListener);
         }
    
         Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
         Collections.addAll(childrenSet, children);
         readonlyChildren = Collections.unmodifiableSet(childrenSet);
     }
    
    
  2. new ServerBootstrap().group(bossGroup, workerGroup) 新建 ServerBootstrap 對象,並調用其 group() 方法配置好主從 Reactor 線程池。其中 MainReactor 線程池將會保存在 AbstractBootstrap.group 變量,SubReactor 線程池以 ServerBootstrap.childGroup 變量保存

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
         super.group(parentGroup);
         if (childGroup == null) {
             throw new NullPointerException("childGroup");
         }
         if (this.childGroup != null) {
             throw new IllegalStateException("childGroup set already");
         }
         this.childGroup = childGroup;
         return this;
     }
    
  3. ServerBootstrap#channel() 方法實際配置了一個 ReflectiveChannelFactory工廠類,用於創建指定的 NioServerSocketChannel 對象

    public B channel(Class<? extends C> channelClass) {
         if (channelClass == null) {
             throw new NullPointerException("channelClass");
         }
         return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
     }
    
     public B channelFactory(ChannelFactory<? extends C> channelFactory) {
         if (channelFactory == null) {
             throw new NullPointerException("channelFactory");
         }
         if (this.channelFactory != null) {
             throw new IllegalStateException("channelFactory set already");
         }
    
         this.channelFactory = channelFactory;
         return self();
     }
    
  4. ServerBootstrap#childHandler()方法爲例,帶 child 前綴的方法都用來配置 SubReactor 線程池所需的處理器等,這些配置在 MainReactor 將連接註冊到 SubReactor 上之後 IO 讀寫會用到

    public ServerBootstrap childHandler(ChannelHandler childHandler) {
         if (childHandler == null) {
             throw new NullPointerException("childHandler");
         }
         this.childHandler = childHandler;
         return this;
     }
    

2.2 業務處理組件 Channel 的初始化及註冊

  1. ServerBootstrap#bind() 方法實際調用到 AbstractBootstrap#doBind()方法,這個方法是整個服務端啓動的入口,主要分爲了以下 2 個部分,本節主要分析 initAndRegister() 流程

    1. initAndRegister() 初始化 Channel,並將其註冊到 Selector 上
    2. doBind0() 將 Channel 綁定監聽指定端口
    private ChannelFuture doBind(final SocketAddress localAddress) {
         final ChannelFuture regFuture = initAndRegister();
         final Channel channel = regFuture.channel();
         if (regFuture.cause() != null) {
             return regFuture;
         }
    
         if (regFuture.isDone()) {
             // At this point we know that the registration was complete and successful.
             ChannelPromise promise = channel.newPromise();
             doBind0(regFuture, channel, localAddress, promise);
             return promise;
         } else {
             // Registration future is almost always fulfilled already, but just in case it's not.
             final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
             regFuture.addListener(new ChannelFutureListener() {
                 @Override
                 public void operationComplete(ChannelFuture future) throws Exception {
                     Throwable cause = future.cause();
                     if (cause != null) {
                         // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                         // IllegalStateException once we try to access the EventLoop of the Channel.
                         promise.setFailure(cause);
                     } else {
                         // Registration was successful, so set the correct executor to use.
                         // See https://github.com/netty/netty/issues/2586
                         promise.registered();
    
                         doBind0(regFuture, channel, localAddress, promise);
                     }
                 }
             });
             return promise;
         }
     }
    
  2. AbstractBootstrap#initAndRegister()方法的作用見名知意,其作用就是初始化 channel 並完成註冊,其中關鍵的部分如下:

    1. 首先通過配置的Channel 工廠類創建指定 Channel 對象,然後通過 init(channel) 方法初始化 Channel
    2. config().group().register(channel) 將初始化完畢的 Channel 註冊到 MainReactor 中某個事件循環線程 Selector 上
    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
    
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }
    
  3. channelFactory.newChannel() 通過反射調用了 NioServerSocketChannel無參構造方法,可知其 newSocket() 方法實際是返回了一個 JDK 中的 ServerSocketChannel對象, NioServerSocketChannel 實際就是這個對象的進一步封裝。在 NioServerSocketChannel 的有參構造中可以看到,其調用了父類構造方法,並指定了其監聽的就緒事件爲 SelectionKey.OP_ACCEPT。此處往上追溯到其 AbstractNioChannel父類構造方法,可以看到 JDK 中 NIO 設置非阻塞的標準操作,並保留了監聽的就緒事件的標識 readInterestOp這個標識會在之後開始讀取 IO 數據的時候用於設置監聽事件

    public NioServerSocketChannel() {
         this(newSocket(DEFAULT_SELECTOR_PROVIDER));
     }
    
    public NioServerSocketChannel(ServerSocketChannel channel) {
         super(null, channel, SelectionKey.OP_ACCEPT);
         config = new NioServerSocketChannelConfig(this, javaChannel().socket());
     }
    
     // AbstractNioChannel 構造方法
    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
         super(parent);
         this.ch = ch;
         this.readInterestOp = readInterestOp;
         try {
             ch.configureBlocking(false);
         } catch (IOException e) {
             try {
                 ch.close();
             } catch (IOException e2) {
                 if (logger.isWarnEnabled()) {
                     logger.warn(
                             "Failed to close a partially initialized socket.", e2);
                 }
             }
    
             throw new ChannelException("Failed to enter non-blocking mode.", e);
         }
     }
    
  4. 追溯 NioServerSocketChannel 父類構造方法最終來到 AbstractChannel,可以到其主要有兩個動作:

    1. newUnsafe() 新建 NioMessageUnsafe 對象,該類負責實際的 IO 讀寫動作
    2. newChannelPipeline() 新建 DefaultChannelPipeline 對象
    protected AbstractChannel(Channel parent) {
         this.parent = parent;
         id = newId();
         unsafe = newUnsafe();
         pipeline = newChannelPipeline();
     }
    
  5. DefaultChannelPipeline 構造方法中會新建 TailContextHeadContext 對象,並將其前後指針互相指向對象,形成雙向處理鏈表

    protected DefaultChannelPipeline(Channel channel) {
         this.channel = ObjectUtil.checkNotNull(channel, "channel");
         succeededFuture = new SucceededChannelFuture(channel, null);
         voidPromise =  new VoidChannelPromise(channel, true);
    
         tail = new TailContext(this);
         head = new HeadContext(this);
    
         head.next = tail;
         tail.prev = head;
     }
    
  6. Channel 對象的創建告一段落,回到初始化 Channel 的方法AbstractBootstrap#init()。這是個抽象方法,其實現爲 ServerBootstrap#init()。這個方法中最重要的邏輯就是代碼 p.addLast()調用的部分,這部分ChannelInitializer對象被添加到流處理鏈,會在之後的流程中將 ServerBootstrapAcceptor註冊到 MainReactor 的處理鏈中

    void init(Channel channel) throws Exception {
         final Map<ChannelOption<?>, Object> options = options0();
         synchronized (options) {
             setChannelOptions(channel, options, logger);
         }
    
         final Map<AttributeKey<?>, Object> attrs = attrs0();
         synchronized (attrs) {
             for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                 @SuppressWarnings("unchecked")
                 AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                 channel.attr(key).set(e.getValue());
             }
         }
    
         ChannelPipeline p = channel.pipeline();
    
         final EventLoopGroup currentChildGroup = childGroup;
         final ChannelHandler currentChildHandler = childHandler;
         final Entry<ChannelOption<?>, Object>[] currentChildOptions;
         final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
         synchronized (childOptions) {
             currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
         }
         synchronized (childAttrs) {
             currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
         }
    
         p.addLast(new ChannelInitializer<Channel>() {
             @Override
             public void initChannel(final Channel ch) throws Exception {
                 final ChannelPipeline pipeline = ch.pipeline();
                 ChannelHandler handler = config.handler();
                 if (handler != null) {
                     pipeline.addLast(handler);
                 }
    
                 ch.eventLoop().execute(new Runnable() {
                     @Override
                     public void run() {
                         pipeline.addLast(new ServerBootstrapAcceptor(
                                 ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                     }
                 });
             }
         });
     }
    
  7. 進入 DefaultChannelPipeline#addLast() 方法,可以看到其內部主要做了 3 件事:

    1. newContext() 將 ChannelHandler 封裝到 DefaultChannelHandlerContext 對象
    2. addLast0() 將新建的 ChannelHandlerContext 對象加入到雙向鏈表中
    3. 此時 Channel 還沒有完成註冊,callHandlerCallbackLater() 方法將新建一個 PendingHandlerAddedTask 對象,用於註冊完成後執行 callHandlerAdded0() 方法回調處理器實現的 handlerAdd() 方法
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
         final AbstractChannelHandlerContext newCtx;
         synchronized (this) {
             checkMultiplicity(handler);
    
             newCtx = newContext(group, filterName(name, handler), handler);
    
             addLast0(newCtx);
    
             // If the registered is false it means that the channel was not registered on an eventloop yet.
             // In this case we add the context to the pipeline and add a task that will call
             // ChannelHandler.handlerAdded(...) once the channel is registered.
             if (!registered) {
                 newCtx.setAddPending();
                 callHandlerCallbackLater(newCtx, true);
                 return this;
             }
    
             EventExecutor executor = newCtx.executor();
             if (!executor.inEventLoop()) {
                 newCtx.setAddPending();
                 executor.execute(new Runnable() {
                     @Override
                     public void run() {
                         callHandlerAdded0(newCtx);
                     }
                 });
                 return this;
             }
         }
         callHandlerAdded0(newCtx);
         return this;
     }
    
  8. Channel 的初始化結束,接下來就是註冊 Channel。config().group().register(channel) 實際調用到了 NioEventLoopGroup 超類 MultithreadEventLoopGroup#register() 方法,最終其實是選中事件循環線程池中的一個 NioEventLoop 事件循環對象完成註冊,實現是調用其超類 SingleThreadEventLoop#register()方法

      @Override
     public ChannelFuture register(final ChannelPromise promise) {
         ObjectUtil.checkNotNull(promise, "promise");
         promise.channel().unsafe().register(this, promise);
         return promise;
     }
    
  9. 從代碼可以看到,註冊是由 Unsafe 類完成的,其實現爲 AbstractUnsafe#register()。此時事件循環線程還沒有啓動,故其會將 AbstractUnsafe#register0()方法包裝成異步任務扔到事件循環對象中執行

    @Override
         public final void register(EventLoop eventLoop, final ChannelPromise promise) {
             ......
             
             AbstractChannel.this.eventLoop = eventLoop;
    
             if (eventLoop.inEventLoop()) {
                 register0(promise);
             } else {
                 try {
                     eventLoop.execute(new Runnable() {
                         @Override
                         public void run() {
                             register0(promise);
                         }
                     });
                 } catch (Throwable t) {
                     logger.warn(
                             "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                             AbstractChannel.this, t);
                     closeForcibly();
                     closeFuture.setClosed();
                     safeSetFailure(promise, t);
                 }
             }
         }
    
  10. 以上任務其實提交到了 SingleThreadEventExecutor#execute(),這個方法比較關鍵的其實只有兩步:

    1. addTask() 將異步任務添加到任務隊列中
    2. 此時事件循環尚未啓動,調用 startThread() 新建線程並啓動
    @Override
    public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }
    
        boolean inEventLoop = inEventLoop();
        addTask(task);
        if (!inEventLoop) {
            startThread();
            if (isShutdown() && removeTask(task)) {
                reject();
            }
        }
    
        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }
    
  11. 啓動線程調用到 SingleThreadEventExecutor#doStartThread(),此處代碼 executor.execute() 實際是通過 ThreadPerTaskExecutor 新建並啓動線程,至此則 SingleThreadEventExecutor.this.run() 方法被調用

    private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }
    
                boolean success = false;
                updateLastExecutionTime();
                try {
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    ......
                }
            }
        });
    }
    
  12. SingleThreadEventExecutor.this.run() 爲抽象方法,其實現爲NioEventLoop#run(),其主要邏輯如下:

    for 空循環正式啓動事件循環線程。循環中select() 方法通過 Selector 輪詢 IO 就緒事件,之後根據 ioRatio 配置分配processSelectedKeys()處理 IO 事件 和 runAllTasks() 處理其他任務的時間

    protected void run() {
        for (;;) {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
    
                    case SelectStrategy.BUSY_WAIT:
                        // fall-through to SELECT since the busy-wait is not supported with NIO
    
                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                }
    
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        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 {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }
    
  13. SingleThreadEventExecutor#runAllTasks() 會將隊列中的任務處理掉,則之前的異步任務 AbstractUnsafe#register0() 被執行,其處理步驟如下:

    1. doRegister() 完成 ServerSocketChannel 的註冊
    2. pipeline.invokeHandlerAddedIfNeeded() 回調 Handler 處理器的 handlerAdded() 方法
    3. pipeline.fireChannelRegistered() 通知 Channel 註冊事件,由處理器做相應處理
    4. 此時 ServerSocketChannel 如果是激活狀態,且是第一次註冊,則 pipeline.fireChannelActive() 通知 Channel 激活事件,由處理器做對應處理
    private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;
    
                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                pipeline.invokeHandlerAddedIfNeeded();
    
                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // This channel was registered before and autoRead() is set. This means we need to begin read
                        // again so that we process inbound data.
                        //
                        // See https://github.com/netty/netty/issues/4805
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }
    
  14. AbstractNioChannel#doRegister() 中實際調用了 ServerSocketChannel#register() 實現將 Channel 註冊到 Selector 上,這屬於Java 中 NIO 的標準操作。需注意此時 Channel 的監聽標識爲 0,也就是說此時 ServerSocketChannel 僅僅註冊成功了,還不能監聽任何網絡操作。不過之後可以通過 SelectionKey#interestOps() 方法修改監聽操作位爲指定值,也就是步驟 3 提到的標識

    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }
    
  15. pipeline.invokeHandlerAddedIfNeeded() 最終調用到 DefaultChannelPipeline#callHandlerAddedForAllHandlers() ,則步驟 7 中封裝的 PendingHandlerAddedTask#execute執行,可以看到這個任務的核心爲 DefaultChannelPipeline#callHandlerAdded0()

     @Override
        void execute() {
            EventExecutor executor = ctx.executor();
            if (executor.inEventLoop()) {
                callHandlerAdded0(ctx);
            } else {
                try {
                    executor.execute(this);
                } catch (RejectedExecutionException e) {
                    if (logger.isWarnEnabled()) {
                        logger.warn(
                                "Can't invoke handlerAdded() as the EventExecutor {} rejected it, removing handler {}.",
                                executor, ctx.name(), e);
                    }
                    remove0(ctx);
                    ctx.setRemoved();
                }
            }
        }
    
  16. DefaultChannelPipeline#callHandlerAdded0() 會依次回調處理器的 handlerAdded()方法,則步驟6ChannelInitializer#handlerAdded() 會被調用,最終調用 ChannelInitializer#initChannel()ServerBootstrapAcceptor註冊到 MainReactor 的處理鏈中,至此再執行完 步驟13 剩下的步驟則註冊流程完成

     p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }
    
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    

2.3 服務端 ServerSocketChannel 綁定

  1. Channel 初始化並註冊完成後,就進入了綁定流程,也就是方法 AbstractBootstrap#doBind0()的調用。可以看到方法內部的主體是向已經註冊的 Channel 所屬的事件循環線程提交一個異步任務,該任務主要調用到 AbstractChannel#bind() 方法

    private static void doBind0(
             final ChannelFuture regFuture, final Channel channel,
             final SocketAddress localAddress, final ChannelPromise promise) {
    
         // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
         // the pipeline in its channelRegistered() implementation.
         channel.eventLoop().execute(new Runnable() {
             @Override
             public void run() {
                 if (regFuture.isSuccess()) {
                     channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                 } else {
                     promise.setFailure(regFuture.cause());
                 }
             }
         });
     }
    
  2. AbstractChannel#bind() 方法內部調用到 DefaultChannelPipeline#bind()方法,這個方法邏輯很簡單,只是使用 TailContext 的引用調到了 AbstractChannelHandlerContext#bind()方法

     public final ChannelFuture bind(SocketAddress localAddress) {
         return tail.bind(localAddress);
     }
    
  3. AbstractChannelHandlerContext#bind()內部邏輯也不復雜,首先通過 findContextOutbound() 方法從流處理雙向鏈表尾部往前找到一個處理出站事件的處理器,也就是 HeadContext,之後調用其 invokeBind() 方法

    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
         if (localAddress == null) {
             throw new NullPointerException("localAddress");
         }
         if (isNotValidPromise(promise, false)) {
             // cancelled
             return promise;
         }
    
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
         if (executor.inEventLoop()) {
             next.invokeBind(localAddress, promise);
         } else {
             safeExecute(executor, new Runnable() {
                 @Override
                 public void run() {
                     next.invokeBind(localAddress, promise);
                 }
             }, promise, null);
         }
         return promise;
     }
    
  4. HeadContext#invokeBind() 方法其實是藉助了 Unsafe 類進行綁定操作的,最終也就調到了 AbstractUnsafe#bind() 方法。這個方法主要完成了兩件事:

    1. doBind() 完成 ServerSocketChannel 綁定到指定端口
    2. 提交異步任務,調用 pipeline.fireChannelActive() 通知 Channel 已經激活,從而回調處理器中的 channelActive() 方法
    public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
             assertEventLoop();
    
             if (!promise.setUncancellable() || !ensureOpen(promise)) {
                 return;
             }
    
             // See: https://github.com/netty/netty/issues/576
             if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                 localAddress instanceof InetSocketAddress &&
                 !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
                 !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
                 // Warn a user about the fact that a non-root user can't receive a
                 // broadcast packet on *nix if the socket is bound on non-wildcard address.
                 logger.warn(
                         "A non-root user can't receive a broadcast packet if the socket " +
                         "is not bound to a wildcard address; binding to a non-wildcard " +
                         "address (" + localAddress + ") anyway as requested.");
             }
    
             boolean wasActive = isActive();
             try {
                 doBind(localAddress);
             } catch (Throwable t) {
                 safeSetFailure(promise, t);
                 closeIfClosed();
                 return;
             }
    
             if (!wasActive && isActive()) {
                 invokeLater(new Runnable() {
                     @Override
                     public void run() {
                         pipeline.fireChannelActive();
                     }
                 });
             }
    
             safeSetSuccess(promise);
         }
    
  5. dobind() 爲抽象方法,其實現爲 NioServerSocketChannel#doBind(),可以看到內部邏輯其實就是將 ServerSocketChannel 綁定到指定端口進行監聽而已,屬於 Java 中 NIO 的標準操作,至此綁定流程結束

    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章