Netty系列文章之服務端啟動分析

pjmike_pj發表於2018-09-23

原文部落格地址: pjmike的部落格

前言

本文主要分析 Netty服務端的啟動,以便對Netty框架有一個基本的認識,我用的Netty版本是 netty-4.1.29,之前的文章Netty 系列文章之基本元件概覽 對Netty的基本元件做了一個簡單的介紹,算是對本文分析Netty服務端的啟動做一個基礎鋪墊

服務端程式碼

該原始碼出自 netty官方提供的 服務端demo,詳細地址: github.com/netty/netty…

我做了一點小改動,程式碼如下:

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(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        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();
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });

            // 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();
        }
    }
}
複製程式碼

服務端啟動流程概覽:

  • 建立 ServerBootStrap啟動類
  • 設定並繫結 NioEventLoopGroup執行緒池
  • 建立服務端 Channel
  • 新增並設定 ChannelHandler
  • 繫結並啟動監聽埠

在之前的文章我就提到過,ServerBootstrap是Netty服務端的啟動輔助類,幫助Netty服務端完成初始化,下面我將深入程式碼,仔細分析Netty服務端啟動過程中各元件的建立與初始化

Channel的建立和初始化過程

Channel是Netty的網路操作抽象類,對應於JDK底層的 Socket,Netty服務端的Channel型別是 NioServerSocketChannel。下面來分析NioServerSocketChannel的建立和初始化

NioServerSocketChannel的建立

NioServerSocketChannel的建立實際上是從 ServerBootStrapbind()方法開始的,進入bind()原始碼分析(AbstractBootstrap的bind()方法):

......
public ChannelFuture bind(int inetPort) {
    return bind(new InetSocketAddress(inetPort));
}
......
/**
 * Create a new {@link Channel} and bind it.
 */
public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    return doBind(localAddress);
}

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;
    }
    ......
}
複製程式碼

在原始碼裡注意到一個 initAndRegister()方法,這個方法就負責 NioServerSocketChannel的初始化和註冊操作,走進initAndRegister()方法,如下圖所示:

channel_init_and_create

從上圖可以看出,原始碼裡是呼叫 channelFactory.newChannel()來建立 channel , 走進ChannelFactory發現該介面被 @Deprecated註解標註了,說明是一個過時的介面:

@Deprecated
public interface ChannelFactory<T extends Channel> {
    /**
     * Creates a new channel.
     */
    T newChannel();
}
複製程式碼

我用的Netty版本是 Netty-4.1.29,其Netty API 文件 中介紹 io.netty.bootstrap.ChannelFactory提到用 io.netty.channel.ChannelFactory代替。

這裡 ChannelFactory只是一個工廠介面,真正建立 Channel的是ReflectiveChannelFactory類,它是ChannelFactory的一個重要實現類,該類通過反射方式建立 Channel,原始碼如下:

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Class<? extends T> clazz;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        this.clazz = clazz;
    }

    @Override
    public T newChannel() {
        try {
            return clazz.getConstructor().newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }

    @Override
    public String toString() {
        return StringUtil.simpleClassName(clazz) + ".class";
    }
}
複製程式碼

其中 newChannel()方法通過 clazz.getConstructor().newInstance()來建立 Channel,即通過反射方式來建立 Channel,而這個 clazz就是 通過ServerBootStrapchannel方法傳入的,最開始的服務端程式碼傳入的NioServerSocketChannel,所以對應通過反射建立了NioServerSocketChannel,並且 ChannelFactory的初始化也是在該方法中進行的,程式碼如下:

public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
複製程式碼

到此,NioServerSocketChannel的建立過程大體結束,再次總結一下:

  • ServerBootstrap中的ChannelFactory的實現是 ReflectiveChannelFactory
  • 生成的 Channel的具體型別是 NioServerSocketChannel
  • Channel的例項化過程其實就是呼叫 ChannelFactory.newChannel方法,實際上是通過反射方式進行建立的

NioServerSocketChanel的例項化過程

在前面的分析中,NioServerSocketChannel是通過反射建立的,它的構造方法如下:

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        /**
         *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
         *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
         *
         *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
         */
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException(
                "Failed to open a server socket.", e);
    }
}
/**
 * Create a new instance
 */
public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
複製程式碼

方法newSocket利用 provider.openServerSocketChannel()生成Nio中的 ServerSocketChannel物件,然後呼叫過載的構造器:

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
複製程式碼

該構造器中,呼叫了父類的構造器,傳入的引數是 SelectionKey.OP_ACCEPT,這個引數對於有Java NIO程式設計經驗的人來說應該非常熟悉,在Java NIO中服務端要監聽客戶端的連線請求,就向多路複用器 Selector註冊 SelectionKey.OP_ACCEPT 客戶端連線事件,而Netty又是基於 Java NIO開發的,這裡可見一斑。接著進入父類構造器:

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}
複製程式碼

然後:

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);
    }
}
複製程式碼

設定當前 ServerSocketChannel為非阻塞通道,然後再次進入父類構造器 AbstractChannel(Channel parent):

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}
複製程式碼
  • parent屬性設定為null
  • 初始化unsafe,用來負責底層的connect,register,read和write操作
  • 初始化pipeline,在例項化一個Channel的同時,當有事件發生的時候,pipeline負責呼叫相應的Handler進行處理

關於 unsafe

Netty中的unsafe不是JDK中的sun.misc.Unsafe,該unsafe實際是封裝了 Java 底層 Socket的操作,因此是溝通 Netty上層和Java 底層重要的橋樑。

ChannelPipeline的初始化

每個Channel都有對應的 ChannelPipeline,當一個Channel被建立時,對應的ChannelPipeline也會自動建立,在上面分析 NioServerSocketChannel例項化過程就看到,在其父類構造器中,有初始化一個 pipeline,對應原始碼如下:

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
    .....
    private final Channel parent;
    private final ChannelId id;
    private final Unsafe unsafe;
    private final DefaultChannelPipeline pipeline;
    ......
    /**
     * Creates a new instance.
     *
     * @param parent
     *        the parent of this channel. {@code null} if there's no parent.
     */
    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }
    ......
        /**
     * Returns a new {@link DefaultChannelPipeline} instance.
     */
    protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }
複製程式碼

從上面程式碼看到,pipeline最終被初始化為一個 DefaultChannelPipelineDefaultChannelPipelineChannelPipeline的實現類,進入它的構造方法,如下:

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;
}
複製程式碼

該構造方法有一個引數 channel,這個 channel就是我們之前傳入的 NioServerSocketChannel。關於該構造器其他方法以及ChannelPipeline更詳細的介紹將在後續文章分析。

NioEventLoopGroup

在我們最開始的Netty服務端程式碼中初始化了兩個 NioEventLoopGroup,即一個處理客戶端連線請求的執行緒池——bossGroup,一個處理客戶端讀寫操作的執行緒池——workerGroup

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
複製程式碼

NioEventLoopGroup的類繼承結構圖如下所示:

nioeventLoopGroup

從圖中可以看到,NioEventLoopGroup實現了 Executor介面,Executor框架可以用來建立執行緒池的,也是一個執行緒執行器。關於 Executor框架更加詳細的介紹請參閱 《Java併發程式設計的藝術》

NioEventLoopGroup

看了NioEventLoopGroup的類繼承結構,下面來分析一下它的初始化過程,構造器原始碼如下:

......
public NioEventLoopGroup(int nThreads) {
    this(nThreads, (Executor) null);
}
......
public NioEventLoopGroup(int nThreads, Executor executor) {
    this(nThreads, executor, SelectorProvider.provider());
}
......
public NioEventLoopGroup(
        int nThreads, Executor executor, final SelectorProvider selectorProvider) {
    this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                         final SelectStrategyFactory selectStrategyFactory) {
    super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
複製程式碼

上面幾個過載構造器其實沒做啥,最終呼叫父類 MultithreadEventLoopGroup 的構造器,

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
複製程式碼

這裡需要注意的是如果我們傳入的執行緒數 nThreads 是 0 的話,那麼Netty將會為我們設定預設的執行緒數 DEFAULT_EVENT_LOOP_THREADS,這個預設值是處理器核心數 * 2,如下:

static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}
複製程式碼

bossGroup這個執行緒池我們傳入的 nThread是1,實際上在 bossGroup中只會有一個執行緒用於處理客戶端連線請求,所以這裡設定為1,而不使用預設的執行緒數,至於為什麼只用一個執行緒處理連線請求還需用執行緒池,在Stack Overflow有相關問題的討論。

然後回來再次進入父類MultithreadEventExecutorGroup的構造器,

......
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
......
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) {
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }

    children = new EventExecutor[nThreads];

    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
            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 ++) {
                    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);
}
複製程式碼

MultithreadEventExecutorGroup管理著 eventLoop的生命週期,它有幾個變數:

  • children: EventExecutor陣列,儲存eventloop
  • chooser: 執行緒選擇器,從children中選取一個 eventloop的策略

MultithreadEventExecutorGroup的構造器主要分為以下幾個步驟:

  • 建立執行緒執行器——ThreadPerTaskExecutor
  • 呼叫 newChild方法初始化 children陣列
  • 建立執行緒選擇器——chooser

建立ThreadPerTaskExecutor

我們一開始初始化 NioEventLoopGroup,並沒有傳入 Executor引數:

public NioEventLoopGroup(int nThreads) {
    this(nThreads, (Executor) null);
}
複製程式碼

所以到父類MultithreadEventExecutorGroup構造器時,executor 為null, 然後執行:

if (executor == null) {
    executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
複製程式碼

ThreadPerTaskExecutor是一個執行緒執行器,它實現了 Executor介面,

public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        this.threadFactory = threadFactory;
    }

    @Override
    public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }
}
複製程式碼

ThreadPerTaskExecutor 實現了 execute方法,每次通過呼叫execute方法執行執行緒任務

呼叫 newChild 方法初始化 children 陣列

children[i] = newChild(executor, args);
複製程式碼

在一個for迴圈裡,nThread執行緒數是總的迴圈次數,通過 newChild方法初始化 EventExecutor陣列的每個元素,而 newChild方法如下:

@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    return new NioEventLoop(this, executor, (SelectorProvider) args[0],
        ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
複製程式碼

每次迴圈通過newChild例項化一個NioEventLoop物件。

建立執行緒選擇器——chooser

public EventExecutorChooser newChooser(EventExecutor[] executors) {
    if (isPowerOfTwo(executors.length)) {
        return new PowerOfTwoEventExecutorChooser(executors);
    } else {
        return new GenericEventExecutorChooser(executors);
    }
}
......
private static boolean isPowerOfTwo(int val) {
    return (val & -val) == val;
}
複製程式碼

根據EventExecutor[]陣列的大小,採用不同策略初始化一個 Chooser,如果大小為 2的冪次方則採用 PowerOfTwoEventExecutorChooser,否則使用 GenericEventExecutorChooser。 無論使用哪個 chooser,它們的功能都是一樣的,即從 EventExecutor[]陣列中,這裡也就是 NioEventLoop陣列中,選擇一個合適的 NioEventLoop

NioEventLoop的初始化

protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    return new NioEventLoop(this, executor, (SelectorProvider) args[0],
        ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
複製程式碼

從前面的分析就可以看到,通過 newChild方法初始化 NioEventLoopGroup中的 NioEventLoop,下面來看下NioEventLoop的構造方法是怎樣的:

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, 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;
}
複製程式碼

之前在 NioEventLoopGroup的構造器中通過 SelectorProvider.provider()建立了一個 SelectorProvider,這裡傳遞給了NioEventLoop中的provider,而NioEventLoop又通過 openSelector()方法獲取一個 selector物件,實際上是通過 provideropenSelector方法。這不就是 對應Java NIO中的建立多路複用器 selector。(這裡只是簡單闡述NioEventLoop的構造方法,後續文章會對NioEventLoop做更加詳細的分析)

Channel的註冊過程

前面已經介紹了 Channel的建立和初始化過程,是在 initAndRegister方法中進行的,這個方法裡還會將初始化好的 channel註冊到 EventLoop執行緒中去

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        channel = channelFactory.newChannel();
        init(channel);
    } catch (Throwable t) {
       .....
    }

    ChannelFuture regFuture = config().group().register(channel);
    ......
}
複製程式碼

呼叫config().group().register方法將 channel註冊到 EventLoopGroup中去,其目的就是為了實現NIO中把ServerSocketChannel註冊到 Selector中去,這樣就是可以實現client請求的監聽,程式碼如下:

public ChannelFuture register(Channel channel) {
    return next().register(channel);
}
......
public EventLoop next() {
    return (EventLoop) super.next();
}
複製程式碼

父類MultithreadEventExecutorGroup的next()方法,next方法使用 chooser策略從 EventExecutor[]陣列中選擇一個 SingleThreadEventLoop

public EventExecutor next() {
    return chooser.next();
}
.....
public EventExecutor next() {
    return executors[idx.getAndIncrement() & executors.length - 1];
}
複製程式碼

然後再執行 SingleThreadEventLoopregister()註冊方法:

public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}
...
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}
複製程式碼

上面程式碼呼叫了 unsafe的register方法,具體是AbstractUnsafe.register,而unsafe主要用於實現底層的 rergister,read,write等操作。該register方法是:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
        ......
        AbstractChannel.this.eventLoop = eventLoop;

        if (eventLoop.inEventLoop()) {
            register0(promise);
        } else {
            ......
        }
    }
複製程式碼

將 eventLoop 賦值給 Channel 的 eventLoop 屬性,然後又呼叫了 register0()方法:

private void register0(ChannelPromise promise) {
        try {
            ......
            boolean firstRegistration = neverRegistered;
            doRegister();
            neverRegistered = false;
            registered = true;
            pipeline.invokeHandlerAddedIfNeeded();
            safeSetSuccess(promise);
            pipeline.fireChannelRegistered();
            if (isActive()) {
                if (firstRegistration) {
                    pipeline.fireChannelActive();
                } else if (config().isAutoRead()) {
                    beginRead();
                }
            }
        } catch (Throwable t) {
            ......
        }
    }

複製程式碼

上面有個關鍵方法就是 doRegister(),doRegister才是最終Nio的註冊方法:

protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                eventLoop().selectNow();
                selected = true;
            } else {
                throw e;
            }
        }
    }
}
複製程式碼

通過javaChannel().register(eventLoop().unwrappedSelector(), 0, this)將 Channel對應的Java NIO ServerSocketChannel註冊到 EventLoop中的Selector上,最終完成了channel向eventLoop的註冊過程。

這裡總結下 Channel註冊過程中函式呼叫鏈: AbstractBootstrap.initAndRegister -> MultithreadEventLoopGroup.register -> SingleThreadEventLoop.register -> AbstractUnsafe.register -> AbstractUnsafe.register0 -> AbstractNioChannel.doRegister()

新增 ChannelHandler

在之前的 initAndRegister()方法裡,裡面有個 init()方法,如下:

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));
                    }
                });
            }
        });
    }

複製程式碼

init()方法中設定服務端自定義的 ChannelOptions,ChannelAttrs屬性和為服務端Channel建立出來的新連線的Channel設定的自定義屬性ChildOptions,ChildAttrs,這裡就不多敘述設定引數的問題了,重點關注 pipelineaddLast方法,該方法就是新增用於處理出站和入站資料流的 ChannelHandler,而pipeline是從 channel中獲取的,之前分析過當建立channel時會自動建立一個對應的 channelPipeline

至於ChannelInitializer又是什麼,來看下它的類繼承結構圖就知道了:

channelHandler

ChannelInitializer是一個抽象類,實現了ChannelHandler介面,它有一個抽象方法initChannel,上面程式碼實現了該方法並且新增了bootstrap的handler,邏輯如下:

.....
//1
ChannelHandler handler = config.handler();
if (handler != null) {
    pipeline.addLast(handler);
}
......
//2
public final ChannelHandler handler() {
    return bootstrap.handler();
}
......
//3
final ChannelHandler handler() {
    return handler;
}

複製程式碼

initChannel新增的Handler就是我們服務端程式碼中 serverbootstrap設定的handler,如下:

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) {
                 ChannelPipeline p = ch.pipeline();
                 //p.addLast(new LoggingHandler(LogLevel.INFO));
                 p.addLast(serverHandler);
             }
         });
複製程式碼

示例程式碼設定的handler為LoggingHandler,用於處理日誌,這裡不細說。上面的initChannel方法可以新增 Handler,這裡的serverbootstrap啟動類還增加了 childHandler方法,也是用來新增 handler,只不過是向已經連線的 channel客戶端的 channnelpipeline新增handler

serverbootstrap.handler()設定的handler在初始化就會執行,而 serverbootstrap.childHandler()設定的childHandler在客戶端連線成功才會執行

小結

由於自身知識與經驗有限,對Netty的服務端啟動原始碼分析得不是很全面,在此過程中也參考了一些大佬的Netty原始碼分析文章,本文如有錯誤之處,歡迎指出。

參考資料 & 鳴謝

相關文章