NioServerSocketChannel的註冊源碼解析

作者:源碼學徒
鏈接:https://juejin.cn/post/6982471960550703112
來源:掘金

我們上一章分析了Netty中NioServerSocketChaennl的創建於初始化,本章節將繼續分析NioServerSocketChannel的分析,NioServerSocketChannel是Netty官方封裝的一個通道對象,旨用來代替或者包裝JDK原生的SocketChannel對象,那麼他是如何講NioServerSocketChannel於JDK的NIO相關代碼關聯起來的呢?

一、源碼入口尋找

我們上一節課主要分析的源碼方法是initAndRegister方法,其實從名字可以看出來,這裏是做通道的初始化於註冊的,我們繼續回到這個方法,該方法的尋找,參照上一章節:

AbstractBootstrap#initAndRegister

我們跳過上節課已經分析的代碼,直接來到註冊相關的邏輯:

ChannelFuture regFuture = config().group().register(channel);

我們逐個方法進行分析:

config()

現在我們創建的ServerBootstrap,所以爲什麼選這個我就不多說了:

private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);

我們可以看到,他返回的是這個對象,該對象是再創建ServerBootstrap的時候自動創建的,我們看,他構造方法裏面穿了一個this,證明他持有一個ServerBootstrap的引用,這代表着他可以通過這個對象,獲取ServerBootstrap內所有的屬性和方法!獲取到這個類之後幹嘛了呢?

config().group()

估計大家很多都已經猜出來了,我們直接點進group裏面去驗證一下:

@SuppressWarnings("deprecation")
public final EventLoopGroup group() {
    return bootstrap.group();
}

該代碼是獲取到了我們再構建ServerBootstrap的時候設置的bossGroup對象,有興趣的可以追一下,這裏比較簡單就不做太多的闡述了,我們繼續回到主線,

config().group().register(channel);

我們通過上述代碼的分析,知道了group方法返回的是NioEventLoopGroup,我們進入到register方法:

我們發現這裏並沒有NioEventLoopGroup,但是通過前幾章我們的學習,我們知道NioEventLoopGroup是MultithreadEventLoopGroup的子類,所以我們子類沒有往父類找,我們進入到MultithreadEventLoopGroup源碼裏面:

@Override
public ChannelFuture register(Channel channel) {
    //一般來說這裏獲取的NioEventLoop 他有繼承與  SingleThreadEventLoop
    return next().register(channel);
}

在這裏,我們看到了一個我們前面分析過得代碼,next(),他調用的是chooser.next();, chooser是我們在構建NioEventLoopGroup的時候創建的一個執行器的選擇器,next方法的功能是輪訓的返回一個線程執行器:NioEventLoop!記不太清的同學可以回頭看NioEventLoopGroup初始化源碼解析的那一章代碼!

現在我們根據前幾章的基礎,我們知道了next()方法返回的是一個NioEventLoop類,我們進入到register()方法查看:

但是,我們發現NioEventLoop相關的實現,但是我們根據前面所學,我們可以知道,NioEventLoop的父類是SingleThreadEventLoop,所以我們進入到 SingleThreadEventLoop#register(io.netty.channel.Channel):

@Override
public ChannelFuture register(Channel channel) {
    //調用本身的註冊方法
    return register(new DefaultChannelPromise(channel, this));
}

//沒什麼可說的繼續往下追
@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

我們一定能夠猜到,這裏的主要代碼是:promise.channel().unsafe().register(this, promise);

我們上一章分析過 unsafe是 NioMessageUnsafe, 但是register卻沒有他的實現:

我們還是需要往父類追,進入到io.netty.channel.AbstractChannel.AbstractUnsafe#register(this, promise):

我們這裏先關注一下參數 :

this:傳入的是他本身,他本身是個什麼 NioEventLoop,也就是說,他傳入了一個執行器

promise:NioServerSocketChannel的包裝對象

我們進入到 register方法中,分析主要代碼:

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ......................暫時忽略不必要代碼.............................
    AbstractChannel.this.eventLoop = eventLoop;
    //注意此時的thread = null 所以返回false
    if (eventLoop.inEventLoop()) {
        //實際的註冊 註冊selector 觸發 handlerAdded事件和 channelRegistered事件
        register0(promise);
    } else {
        .......................暫時忽略不必要代碼......................
    }
}
AbstractChannel.this.eventLoop = eventLoop;

首先我們將上一步獲取的執行器保存在NioServerSocketChannel中! 這行代碼有力的證明了,每一個Channel綁定一個NioEventLoop對象!

if (eventLoop.inEventLoop()) {
    //實際的註冊 註冊selector 觸發 handlerAdded事件和 channelRegistered事件
    register0(promise);
}

注意:這裏我需要澄清一點,真實的調試過程中,並不會走這個分支,而是會走else分支異步進行註冊,這裏爲了更方便大家理解,我就依照if分支進行源碼分析,其實沒有太大變化,都是調用register0方法進行註冊,只不過一個同步一個異步,關於異步,是Netty中及其重要的一個知識點,我將放到後面單獨開一章進行講解!

我們進入到register0源碼裏面:

private void register0(ChannelPromise promise) {
    try {
        ..............忽略代碼..................
]
        //實際的註冊  調用jdk底層的數據註冊selector
        // 調用 JDK 底層的 register() 進行註冊
        //io.netty.channel.nio.AbstractNioChannel.doRegister
        doRegister();
        neverRegistered = false;
        registered = true;

        //通知管道  傳播handlerAdded事件
        //觸發 handlerAdded 事件 觸發任務 add事件
        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        //通知管道  傳播channelRegistered事件
        // 觸發 channelRegistered 事件
        pipeline.fireChannelRegistered();
        // 如果從未註冊過頻道,則僅觸發channelActive。
        // 如果取消註冊並重新註冊通道,則多個通道處於活動狀態。
        //isActive() 返回false
        // 此時 Channel 還未註冊綁定地址,所以處於非活躍狀態
        if (isActive()) {
            ....................忽略不必要代碼..................
        }
    } catch (Throwable t) {
        // 直接關閉通道以避免FD泄漏。
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

二、源碼解析

doRegister();

doRegister();

真正的註冊方法,該方法是將Netty本身的NioServerSocket與JDK連接起來的最重要的一個類!

selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

javaChannel()方法是返回JDK原生的SocketChannel,他是再NioServerSocketChannel初始化的時候被保存的,還記得我們再講述NIO開發Socket的時候的流程嗎

我們重點關注一下javaChannel().register的參數:

eventLoop().unwrappedSelector():NioEventLoop再創建的時候,會保存兩個選擇器,一個是JDK的原始的選擇器,一個是經過Netty包裝的選擇器,這裏返回的是原生的選擇器!

0:不關注任何事件

this:this代表着當前類,他是NioServerSocketChannel類型的,他將一個NioServerSocketChannel的對象,綁定到了JDK原生的選擇器,後續只需要通過SelectionKey.attachment(),就能獲取到NioServerSocketChannel,而一個NioServerSocketChannel裏面又包含一個JDK原生的Channel對象,就可以基於該jdk原生的Channel來進行各種讀寫操作!

到現在爲止,我們就完成JDK中的NIO的將通道綁定到選擇器上,我們回到上一步:

pipeline.invokeHandlerAddedIfNeeded

pipeline.invokeHandlerAddedIfNeeded();

開始回調pipeline通道里面添加自定義事件:

final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        // 現在,我們已註冊到EventLoop。現在該調用ChannelHandler的回調了,
        // 在完成註冊之前添加的內容。
        callHandlerAddedForAllHandlers();
    }
}

//callHandlerAddedForAllHandlers
private void callHandlerAddedForAllHandlers() {
    //task = PendingHandlerAddedTask
    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        task.execute();
        task = task.next;
    }
}

需要注意的是 PendingHandlerCallback task 是PendingHandlerAddedTask類型的,他是什麼時候加載的呢?實在我們初始化NioServerSocketChannel的時候調用addLast方法的時候被賦的值,有興趣的小夥伴可以自己去跟一下源碼,這裏直接進入到:

if (executor.inEventLoop()) {
    callHandlerAdded0(ctx);
}

//進入到 callHandlerAdded0源碼邏輯
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try{
        ctx.callHandlerAdded();
    }
    .......................
}

//進入到ctx.callHandlerAdded();
final void callHandlerAdded() throws Exception {
    if (setAddComplete()) {
        handler().handlerAdded(this);
    }
}

還接記得handler()嗎,我再NioServerSocketChannel初始化的時候說過,當時程序向pipeline中添加了一個ChannelInitializer,這裏返回的就是那個ChannelInitializer! 我們進入到ChannelInitializer#handlerAdded方法裏面:

@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isRegistered()) {
        if (initChannel(ctx)) {
            removeState(ctx);
        }
    }
}

首先我們重點關注一個 initChannel(ctx)

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    // 防止再次進入。
    if (initMap.add(ctx)) {
        try {
            // 調用 ChannelInitializer 實現的 initChannel() 方法
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            ................................
        } finally {
            ChannelPipeline pipeline = ctx.pipeline();
            if (pipeline.context(this) != null) {
                // 將 ChannelInitializer 自身從 Pipeline 中移出
                pipeline.remove(this);
            }
        }
        return true;
    }
    return false;
}
initChannel((C) ctx.channel());

該方法會回調ChannelInitializer的抽象方法initChannel,該抽象方法在我們初始化的時候完成,我們就要找到實現這個抽象方法的地方,我們回到上一節課的代碼: io.netty.bootstrap.ServerBootstrap#init

void init(Channel channel) {
    ..........................;
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) {
            final ChannelPipeline pipeline = ch.pipeline();
            //將用戶自定義的handler添加進管道  handler 是在構建ServerBootStr的時候傳入的  handler
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(() -> {
                pipeline.addLast(new ServerBootstrapAcceptor(
                    ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            });
    }    
}

上一節課講的時候,我們將這一段邏輯略過了,只說是會向通道中添加一個ChannelInitializer實現,現在開始回調他的initChannel方法了:

ChannelHandler handler = config.handler();
if (handler != null) {
    pipeline.addLast(handler);
}

這段代碼會將客戶再構建ServerBootstrap的時候傳入的handler添加進通道,我們爲了方便理解,假設用戶沒有設置handler,所以這個handler判斷不通過,跳過,我們繼續往下:

ch.eventLoop().execute(() -> {
    pipeline.addLast(new ServerBootstrapAcceptor(
        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
});

這裏異步的向管道流注冊一個默認的Handler, 爲什麼說是異步的,我們暫且不說,我們暫且認爲是同步的進行add,此時我們的通道如下:

ServerBootstrapAcceptor的作用是專門用於新連接接入的,

ServerBootstrapAcceptor(
                final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
                Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
    this.childGroup = childGroup;
    this.childHandler = childHandler;
    this.childOptions = childOptions;
    this.childAttrs = childAttrs;

    enableAutoReadTask = new Runnable() {
        @Override
        public void run() {
            channel.config().setAutoRead(true);
        }
    };
}

我們可以看到,他會保存一系列的參數,包括WorkGroup、childHandler、childOptions、childAttrs這些參數都是我們再創建serverBootstrap的時候傳入的參數,這也證明了,這些參數是作用於客戶端Socket連接的!

有關ServerBootstrapAcceptor,後續會進行一個詳細的分析,我們接着說,這裏需要重點講一下addLast方法,

@Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        ........................忽略.........................
        //通知添加方法回調
        callHandlerAdded0(newCtx);
        return this;
    }

在進行添加的時候,他會回調內部產生的handlerAdded方法,還記得,我們在介紹Netty的基本架構的業務通道章節嗎?

再調用addLast之後,該方法會被回調!

這樣就講所有的方法註冊完畢了,我們繼續回到ChannelInitializer#handlerAdded方法,當initChannel(ctx)調用完了之後:

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    // 防止再次進入。
    if (initMap.add(ctx)) {
        try {
            // 調用 ChannelInitializer 實現的 initChannel() 方法
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            ................................
        } finally {
            ChannelPipeline pipeline = ctx.pipeline();
            if (pipeline.context(this) != null) {
                // 將 ChannelInitializer 自身從 Pipeline 中移出
                pipeline.remove(this);
            }
        }
        return true;
    }
    return false;
}

我們會進入到finally裏面,我們會看到,此時會做一個操作,刪除當前的類,當前的類是誰,是ChannelInitializer,所以刪除完畢後,此時管道對象的結構如圖所示:

至此 invokeHandlerAddedIfNeeded 分析完畢

pipeline.fireChannelRegistered();

@Override
public final ChannelPipeline fireChannelRegistered() {
    AbstractChannelHandlerContext.invokeChannelRegistered(head);
    return this;
}

這行代碼其實沒什麼可說的,大家可以自己跟一下調試一下代碼,這個代碼的意義是從HeadContext節點開始傳播channelRegistered方法:

至此,NioServerSocketChannel的註冊基本就分析完了,有的同學可能覺得少分析了一段:

if (isActive()) {
    if (firstRegistration) {
        //Channel 當前狀態爲活躍時,觸發 channelActive 事件
        pipeline.fireChannelActive();
    } else if (config().isAutoRead()) {
        // 該通道已註冊,並已設置autoRead()。這意味着我們需要開始閱讀
        // 再次,以便我們處理入站數據。
        //
        // See https://github.com/netty/netty/issues/4805
        //開始讀事件
        beginRead();
    }
}

這段代碼再第一次啓動的時候並不會被調用,因爲此時通道還沒有綁定端口正式啓動起了,所以這裏isActive會返回false,有關邏輯,會在新連接接入講解的時候進行分析!

三、總結

  1. Netty會調用JDK底層的註冊方法,同時將本身的NioServerSocketChannel作爲att綁定到選擇事件上!
  2. 當註冊完成後會回調 handlerAdded方法
  3. Netty會回調再初始化NioServerSocketChannel的時候註冊的Channelinitialization, 添加一個新連接接入器ServerBootstrapAcceptor,並刪除本身!
  4. 當註冊完成後會回調Channelregistered方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章