Netty源码分析第1章(Netty启动流程)----第4节: 注册多路复用
?
Netty源碼分析第一章:Netty啟動流程
?
第四節:注冊多路復用
?
回顧下以上的小節, 我們知道了channel的的創建和初始化過程, 那么channel是如何注冊到selector中的呢?我們繼續分析
回到上一小節的代碼:
final ChannelFuture initAndRegister() {Channel channel = null;try {//創建channelchannel = channelFactory.newChannel();//初始化channel init(channel);} catch (Throwable t) {//忽略非關鍵代碼 }//注冊channelChannelFuture regFuture = config().group().register(channel);//忽略非關鍵代碼return regFuture; }我們講完創建channel和初始化channel的關鍵步驟, 我們繼續跟注冊channel的步驟:
ChannelFuture regFuture = config().group().register(channel);其中, 重點關注下register(channel)這個方法, 這個方法最終會調用到AbstractChannel中內部類AbstractUnsafe的register()方法, 具體如何調用到這個方法, 可以簡單帶大家捋一下
首先看下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
?
回到initAndRegister()方法:
config()返回的是ServerBootstrapConfig對象
再繼續跟到其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()方法
跟到MultithreadEventLoopGroup的register()方法:
public ChannelFuture register(Channel channel) {return next().register(channel); }這里的代碼看起來有點暈, 沒關系, 以后會講到, 現在可以大概做個了解, NioEventLoopGroup是個線程組, 而next()方法就是從線程組中選出一個線程, 也就是NioEventLoop線程, 所以這里的next()方法返回的是NioEventLoop對象, 其中register(channel)最終會調用NioEventLoop的父類SingleThreadEventLoop的register(channel)方法
跟到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()
跟到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() {@Overridepublic 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的初始化
下一節: 綁定端口
?
posted on 2018-12-31 18:11 向南是個萬人迷 閱讀(...) 評論(...) 編輯 收藏轉載于:https://www.cnblogs.com/xiangnan6122/p/10202849.html
總結
以上是生活随笔為你收集整理的Netty源码分析第1章(Netty启动流程)----第4节: 注册多路复用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Golang学习笔记] 05 程序实体
- 下一篇: $python爬虫系列(1)——一个简单