1. 程式人生 > >Netty原始碼分析第1章(Netty啟動流程)---->第4節: 註冊多路複用

Netty原始碼分析第1章(Netty啟動流程)---->第4節: 註冊多路複用

 

第一章:Netty啟動流程

 

第四節:註冊多路複用

 

回顧下以上的小節, 我們知道了channel的的建立和初始化過程, 那麼channel是如何註冊到selector中的呢?我們繼續分析

回到上一小節的程式碼:

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        //建立channel
        channel = channelFactory.newChannel();
        //初始化channel
        init(channel);
    } 
catch (Throwable t) { //忽略非關鍵程式碼 } //註冊channel ChannelFuture regFuture = config().group().register(channel); //忽略非關鍵程式碼 return regFuture; }

我們講完建立channel和初始化channel的關鍵步驟, 我們繼續跟註冊channel的步驟:

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

其中, 重點關注下register(channel)

這個方法, 這個方法最終會呼叫到AbstractChannel中內部類AbstractUnsaferegister()方法, 具體如何呼叫到這個方法, 可以簡單帶大家捋一下

首先看下config()方法, 由於是ServerBootstrap呼叫的, 所以我們跟進去:

public final ServerBootstrapConfig config() {
    return config;
}

返回的configServerBootrap的成員變數config:

private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);

 

跟到ServerBootstrapConfig的構造方法:

ServerBootstrapConfig(ServerBootstrap bootstrap) {
    super(bootstrap);
}

繼續跟到其父類AbstractBootstrapConfig的構造方法:

protected AbstractBootstrapConfig(B bootstrap) {
    this.bootstrap = ObjectUtil.checkNotNull(bootstrap, "bootstrap");
}

我們發現我們建立的ServerBootstrap作為引數初始化了其成員變數bootstrap

 

再繼續跟到configgroup()方法:

public final EventLoopGroup group() {
    return bootstrap.group();
}

這裡呼叫Bootstrapgroup()方法:

public final EventLoopGroup group() {
    return group;
}

這裡返回了AbstractBootstrap的成員變數group, 我們回顧下第一小節, 還記得AbstractBootstrapgroup(EventLoopGroup group)方法嗎?

public B group(EventLoopGroup group) {
    this.group = group;
    return (B) this;
}

group(EventLoopGroup group)方法初始化了我們boss執行緒, group()返回了boss執行緒, 也就是說config().group().register(channel)中的register()方法是boss執行緒物件呼叫的, 由於我們當初初始化的是NioEventLoopGroup, 因此走的是NioEventLoopGroup的父類的MultithreadEventLoopGroupregister()方法:

public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

這裡的程式碼看起來有點暈, 沒關係, 以後會講到, 現在可以大概做個瞭解, NioEventLoopGroup是個執行緒組, next()方法就是從執行緒組中選出一個執行緒, 也就是NioEventLoop執行緒, 所以這裡的next()方法返回的是NioEventLoop物件, 其中register(channel)最終會呼叫NioEventLoop的父類SingleThreadEventLoopregister(channel)方法, 看程式碼:

public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}

其中DefaultChannelPromise類我們之後也會講到, 我們先跟到register(new DefaultChannelPromise(channel, this)):

public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

channel()會返回我們初始化的NioServerSocketChannel, unsafe()會返回我們建立channel的時候初始化的unsafe物件, 跟進去看AbstractChannelunsafe()的實現:

public Unsafe unsafe() {
    return unsafe;
}

這裡返回的unsafe, 就是我們初始化channel建立的unsafe, 回顧下第二小節channel初始化的步驟:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

我們看unsafe的初始化:unsafe=newUnsafe()

 

跟到newUnsafe(), 我們之前講過NioServerSokectChannel的父類是AbstractNioMessageChannel, 所以會呼叫到到AbstractNioMessageChannel類中的newUnsafe():

protected AbstractNioUnsafe newUnsafe() {
    return new NioMessageUnsafe();
}

我們看到這裡建立了NioMessageUnsafe()物件, 所以在

promise.channel().unsafe().register(this, promise);

程式碼中, unsafe()是返回的NioMessageUnsafe()物件, 最後呼叫其父類AbstractUnsafe(也就是AbstractChannel的內部類)register()方法,

 

簡單介紹下unsafe介面, unsafe顧名思義就是不安全的, 因為很多對channelio方法都定義在unsafe, 所以netty將其作為內部類進行封裝, 防止被外部直接呼叫, unsafe介面是Channel介面的內部介面, unsafe的子類也分別封裝在Channel的子類中, 比如我們現在剖析的register()方法, 就是封裝在AbstractChannel類的內部類AbstractUnsafe中的方法, 有關UnsafeChannel的繼承關係如下:

1-4-1

以上內容如果不明白沒有關係, 有關NioEventLoop相關會在後面的章節講到, 目前我們只是瞭解是如何走到AbstractUnsafe類的register()即可

 

我們繼續看看register()方法:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    //程式碼省略
    //所有的複製操作, 都交給eventLoop處理(1)
    AbstractChannel.this.eventLoop = eventLoop;
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    //做實際主註冊(2)
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            //程式碼省略
        }
    }
}

我們跟著註釋的步驟繼續走, 第一步, 繫結eventLoop執行緒:

AbstractChannel.this.eventLoop = eventLoop;

eventLoopAbstractChannel的成員變數, 有關eventLoop, 我們會在緒章節講到, 這裡我們只需要知道, 每個channel繫結唯一的eventLoop執行緒, eventLoop執行緒和channel的繫結關係就是在這裡展現的

再看第二步, 做實際註冊:

我們先看if判斷, if(eventLoop.inEventLoop())

 

這裡是判斷是不是eventLoop執行緒, 顯然我們現在是main()方法所在的執行緒, 所以走的else, eventLoop.execute()是開啟一個eventLoop執行緒, register0(promise)就是再開啟執行緒之後, 通過eventLoop執行緒執行的, 這裡大家暫時作為了解, 我們重點關注register0(promise), 跟進去:

private void register0(ChannelPromise promise) {
    try {
        //做實際的註冊(1)
        doRegister();
        neverRegistered = false;
        registered = true;
        //觸發事件(2)
        pipeline.invokeHandlerAddedIfNeeded();
        safeSetSuccess(promise);
        //觸發註冊成功事件(3)
        pipeline.fireChannelRegistered();
        if (isActive()) {
            if (firstRegistration) {
                //傳播active事件(4)
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        //省略程式碼
    }
}

我們重點關注doRegister()這個方法

 

doRegister()最終會呼叫AbstractNioChanneldoRegister()方法:

protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            //jdk底層的註冊方法
            //第一個引數為selector, 第二個引數表示不關心任何事件
            selectionKey = javaChannel().register(eventLoop().selector, 0, this);
            return;
        } catch (CancelledKeyException e) {
            //省略程式碼
        }
    }
}

我們終於看到和java底層相關的方法了, 跟到javaChannel()的方法中, 我們看到:

protected SelectableChannel javaChannel() {
    return ch;
}

這個ch, 就是本章第二小節建立NioServerSocketChannel中初始化的jdk底層ServerSocketChannel

這裡register(eventLoop().selector, 0, this)方法中eventLoop().selector, 是獲得每一個eventLoop繫結的唯一的selector, 0代表這次只是註冊, 並不監聽任何事件, this是代表將自身(NioEventLoopChannel)作為屬性繫結在返回的selectionKey當中, 這個selectionKey就是與每個channel繫結的jdk底層的SelectionKey物件, 熟悉nio的小夥伴應該不會陌生, 這裡不再贅述

 

回到register0(ChannelPromise promise)方法, 我們看後續步驟:

步驟(2)是觸發handler的需要新增事件, 事件傳遞的內容我們將在後續課程詳細介紹, 這裡不必深究

步驟(3)是觸發註冊成功事件(3), 同上

步驟(4)是傳播active事件(4), 這裡簡單強調一下, 這裡的方法pipeline.fireChannelActive()第一個註冊是執行不到的, 因為isActive()會返回false, 因為鏈路沒完成

本小節梳理了有註冊多路複用的相關邏輯, 同學們可以跟著程式碼自己走一遍以加深印象

 

回顧下以上的小結, 我們知道了channel的的建立和初始化過程, 那麼channel是如何註冊到selector中的呢?我們繼續分析

回到上一小結的程式碼: