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)
首先看下config()方法, 由於是ServerBootstrap呼叫的, 所以我們跟進去:
public final ServerBootstrapConfig config() { return config; }
返回的config是ServerBootrap的成員變數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
再繼續跟到config的group()方法:
public final EventLoopGroup group() { return bootstrap.group(); }
這裡呼叫Bootstrap的group()方法:
public final EventLoopGroup group() { return group; }
這裡返回了AbstractBootstrap的成員變數group, 我們回顧下第一小節, 還記得AbstractBootstrap的group(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的父類的MultithreadEventLoopGroup的register()方法:
public ChannelFuture register(Channel channel) { return next().register(channel); }
這裡的程式碼看起來有點暈, 沒關係, 以後會講到, 現在可以大概做個瞭解, NioEventLoopGroup是個執行緒組, 而next()方法就是從執行緒組中選出一個執行緒, 也就是NioEventLoop執行緒, 所以這裡的next()方法返回的是NioEventLoop物件, 其中register(channel)最終會呼叫NioEventLoop的父類SingleThreadEventLoop的register(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物件, 跟進去看AbstractChannel的unsafe()的實現:
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顧名思義就是不安全的, 因為很多對channel的io方法都定義在unsafe中, 所以netty將其作為內部類進行封裝, 防止被外部直接呼叫, unsafe介面是Channel介面的內部介面, unsafe的子類也分別封裝在Channel的子類中, 比如我們現在剖析的register()方法, 就是封裝在AbstractChannel類的內部類AbstractUnsafe中的方法, 有關Unsafe和Channel的繼承關係如下:
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;
eventLoop是AbstractChannel的成員變數, 有關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()最終會呼叫AbstractNioChannel的doRegister()方法:
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中的呢?我們繼續分析
回到上一小結的程式碼: