Netty服務端啟動過程相關原始碼分析

monkjavaer發表於2019-08-06

1、Netty 是怎麼建立服務端Channel的呢?

我們在使用ServerBootstrap.bind(埠)方法時,最終呼叫其父類AbstractBootstrap中的doBind方法,相關原始碼如下:

 private ChannelFuture doBind(final SocketAddress localAddress) {
        //初始化和註冊
        final ChannelFuture regFuture = initAndRegister();
        .....

我們繼續跟進initAndRegister()這個方法,發現是使用channelFactory.newChannel() 完成channel的建立:

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

ChannelFactory在實現類ReflectiveChannelFactory中的實現細節,內部使用了反射的方式建立Channel:

    public T newChannel() {
        try {
            return clazz.newInstance();
        } catch (Throwable t) {......}
    }

這裡的ChannelFactory是通過 bootstrap.channel(NioServerSocketChannel.class) 加入的:

    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        }
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }
  • 綜上所述,Netty就是呼叫jdk底層方法建立NIO的channel,也就是通過反射完成NIO的channel建立。最後其包裝成Netty自己的Channel.

2、初始化服務端Channel是怎麼樣的執行流程?

在建立了Channel之後就呼叫AbstractBootstrap的init(channel)抽象方法完成初始化:

    abstract void init(Channel channel) throws Exception;

伺服器端ServerBootstrap的init方法從原始碼來看主要完成工作為:

  • 配置相關的Options和Attribute。
  • 通過ChannelPipeline 新增相關邏輯處理器 ChannelHandler。

最後這些屬性會傳入ServerBootstrapAcceptor聯結器,通過ServerBootstrapAcceptor聯結器完成相應的初始化。

                // We add this handler via the EventLoop as the user may have used a ChannelInitializer as handler.
                // In this case the initChannel(...) method will only be called after this method returns. Because
                // of this we need to ensure we add our handler in a delayed fashion so all the users handler are
                // placed in front of the ServerBootstrapAcceptor.
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });

3、怎麼註冊selector?

在Java NIO 中註冊通道Channel到多路複用器Selector,並說明關注點SelectionKey.OP_ACCEPT,監聽ACCEPT事件通常我們會這樣寫:

serverSocket.register(selector, SelectionKey.OP_ACCEPT);

Netty在底層將Channel註冊到事件輪詢器selector上就是基於此方法:

  • 首先在初始化Channel後執行:
ChannelFuture regFuture = config().group().register(channel);

上面的程式碼實際是呼叫AbstractChannel的register方法,完成eventLoop的繫結。內部方法register0()中會呼叫AbstractNioChannel的doRegister() 方法:

    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException e) {......}
        }
    }

這裡其實就是呼叫NIO SelectableChannel的register方法。

  • 從原始碼可以知道,註冊成功後這裡會以此執行伺服器handler中的回撥方法:handlerAdded ,channelActive

4、埠怎麼繫結呢?

一切OK之後就會呼叫AbstractChannel中的 bind 方法,這個方法又會呼叫NioServerSocketChannel 的 doBind 方法,從doBind方法可知是呼叫的原生NIO 的bind做繫結:

    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

繫結完成後會執行程式碼:

            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

這裡會呼叫DefaultChannelPipeline中的內部類HeadContext的channelActive方法進行事件傳播:

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

那麼服務端到底是在哪裡 accept 連線的呢?
通過上面的程式碼我們跟進 AbstractChannel 的beginRead() 方法,繼而找到 AbstractNioChannel 的 doBeginRead() 方法:

    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

上面的程式碼就是NIO程式設計中常用的寫法,這裡監聽ACCEPT事件就是NioServerSocketChannel建構函式呼叫父類傳入的SelectionKey.OP_ACCEPT:

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

相關文章