摘要:用這種方式很好的規(guī)避了多線程所帶來的問題,很值得我們借鑒那么這個怎么來的呢看一下的方法如果為,就返回。
Netty channelRegisteredChannelActive---源碼分析
經(jīng)過下面的分析我們可以了解netty讀數(shù)據(jù)的一個過程,以及為什么channelActive方法、channelReadComplete方法會回調(diào)ChannelOutboundHandler的read方法。
上文說到在HeadContext的channelActive方法中會調(diào)用readIfIsAutoRead();該方法同樣會在HeadContext的channelReadComplete(xxx)中調(diào)用。 readIfIsAutoRead();源碼如下:
private void readIfIsAutoRead() { if (channel.config().isAutoRead()) { channel.read(); } }
channel.config().isAutoRead()可以通過ChannelOption.AUTO_READ設(shè)置。如果設(shè)置為false,那么channel便不會主動讀數(shù)據(jù),除非顯示的調(diào)用ChannelHandlerContext的read()
AbstractChannel的read()如下
@Override public Channel read() { pipeline.read(); return this; }
在read()方法中調(diào)用了pipeline的read()方法
DefaultChannelPipeline的read()方法
@Override public final ChannelPipeline read() { tail.read(); return this; }2.TailContext
那么接下來的重點(diǎn)就是DefaultChannelPipeline的tail及tail.read()方法了。先看一下tail對應(yīng)的TailContext類,TailContext是DefaultChannelPipeline的內(nèi)部類
DefaultChannelPipeline
final AbstractChannelHandlerContext head; final AbstractChannelHandlerContext tail; ...省略代碼... protected DefaultChannelPipeline(Channel channel) { this.channel = ObjectUtil.checkNotNull(channel, "channel"); succeededFuture = new SucceededChannelFuture(channel, null); voidPromise = new VoidChannelPromise(channel, true); tail = new TailContext(this); head = new HeadContext(this); head.next = tail; tail.prev = head; }
TailContext
// A special catch-all handler that handles both bytes and messages. final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler { TailContext(DefaultChannelPipeline pipeline) { super(pipeline, null, TAIL_NAME, true, false); setAddComplete(); } @Override public ChannelHandler handler() { return this; } ...省略代碼...
TailContext繼承自AbstractChannelHandlerContext,同時實(shí)現(xiàn)了ChannelInboundHandler,也是多重身份。
TailContext的read()方法是繼承自AbstractChannelHandlerContext,TailContext沒有重寫。
AbstractChannleHandlerContext的read()如下:
@Override public ChannelHandlerContext read() { final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeRead(); } else { Runnable task = next.invokeReadTask; if (task == null) { next.invokeReadTask = task = new Runnable() { @Override public void run() { next.invokeRead(); } }; } executor.execute(task); } return this; } private AbstractChannelHandlerContext findContextOutbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while (!ctx.outbound); return ctx; }
其中findContextOutbound()是找到下一個Outbound的ChannelHandlerContext。那么由tail.read()所代表的含義便是從pipeline中的尾部的最后一個ChannelInboundHandler開始往前查找是Outbound的HandlerContext.
然后該HandlerContext的invokeRead()方法被調(diào)用。
以下分析和read過程沒多大關(guān)系也可以跳過
AbstractChannleHandlerContext的read()方法中的
if (executor.inEventLoop()) { next.invokeRead(); } else { Runnable task = next.invokeReadTask; if (task == null) { next.invokeReadTask = task = new Runnable() { @Override public void run() { next.invokeRead(); } }; } executor.execute(task); }
AbstractEventExecutor的inEventLoop()
@Override public boolean inEventLoop() { return inEventLoop(Thread.currentThread()); }
上面代碼的含義是如果調(diào)用ChannelHandlerContext read() 所在的線程和executor是同一個線程,那么直接執(zhí)行AbstractChannelHandlerContext的invokeRead()方法,否則封裝成任務(wù),放到executor的任務(wù)隊(duì)列,去等待執(zhí)行。 這種類似的代碼在netty中很常見,這是netty中不用考慮多線程問題的原因。netty用這種方式很好的規(guī)避了多線程所帶來的問題,很值得我們借鑒
那么這個executor怎么來的呢?看一下AbstractChannelHandlerContext的executor()方法
@Override public EventExecutor executor() { if (executor == null) { return channel().eventLoop(); } else { return executor; } }
如果executor 為null,就返回channel().eventLoop()。這里channel().eventLoop()就是每個channel所對應(yīng)的EventLoop,專門用來處理IO事件,因此不能被阻塞,不能執(zhí)行耗時任務(wù),該eventLoop會在channel創(chuàng)建時會和channel綁定,ChannelInboundHandler的channelRegistered()也就會被回調(diào)。我們創(chuàng)建ServerBootstrap是會指定一個WokerGroup例如NioEventLoopGroup,那么這個eventLoop便會是其中的一員。
那如果executor不為null,executor是怎么來的呢?
AbstractChannelHandlerContext的構(gòu)造方法
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, boolean inbound, boolean outbound) { this.name = ObjectUtil.checkNotNull(name, "name"); this.pipeline = pipeline; this.executor = executor; this.inbound = inbound; this.outbound = outbound; // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor. ordered = executor == null || executor instanceof OrderedEventExecutor; }
executor是通過構(gòu)造方法傳進(jìn)來的。pipeline在添加handler時可以指定EventExecutorGroup(可以查看ChannelPipeline接口的API),便是這么傳進(jìn)來的,具體的分析過程此處略去(可查看netty 耗時任務(wù)如何處理去查看具體分析過程),因?yàn)椴皇谴似恼碌闹攸c(diǎn)。
這樣我們就能處理耗時任務(wù),而不阻塞IO線程了。
第2小節(jié)分析到AbstractChannelHandlerContext的invokeRead()方法會被調(diào)用,那么invokeRead()實(shí)現(xiàn)了什么功能?
private void invokeRead() { if (invokeHandler()) { try { ((ChannelOutboundHandler) handler()).read(this); } catch (Throwable t) { notifyHandlerException(t); } } else { read(); } }
該方法所表達(dá)的含義很簡單就是回調(diào)ChannelOutboundHandler的read(xxx)方法。如果我們的自定義的ChannelOutboundHandler繼承自ChannelOutboundHandlerAdapter,并且沒有重寫該方法,或者在重寫的方法中調(diào)用了super.read(ctx); 那就會重復(fù)調(diào)用ChannelHandlerContext的read(),即AbstractChannelHandlerContext的read()方法。這樣read(xxx)回調(diào)便會在ChannelHandlerContext的作用下從pipleline的ChannelOutboundHandler中的尾部傳遞到頭部,直到DefaultChannelPipeline的DefaultChannelPipeline的HeadContext.
HeadContext的read(xxx)方法如下,HeadContext本身也是ChannelOutboundHandler
@Override public void read(ChannelHandlerContext ctx) { unsafe.beginRead(); }
以NioChannel為例,unsafe.beginRead();最終會調(diào)用到AbstractNioChannel的doBeginRead()方法,其對應(yīng)的源碼如下:
@Override 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); } }
該方法里就是Java Nio的相關(guān)操作,SelectionKey的性趣集中添加OP_READ,最終實(shí)現(xiàn)讀數(shù)據(jù)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69143.html
摘要:目錄源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結(jié)的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環(huán)境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端源碼分析之一揭開神秘的紅蓋頭服務(wù)器 目錄 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NIO 的前生今世 之一 簡介 Java NIO 的前生今世 ...
摘要:目錄源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結(jié)的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環(huán)境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端源碼分析之一揭開神秘的紅蓋頭服務(wù)器 目錄 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NIO 的前生今世 之一 簡介 Java NIO 的前生今世 ...
摘要:對于,目前大家只知道是個線程組,其內(nèi)部到底如何實(shí)現(xiàn)的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,這里不作詳細(xì)介紹,后面會有文章作專門詳解。 在上一篇《ServerBootstrap 與 Bootstrap 初探》中,我們已經(jīng)初步的了解了ServerBootstrap是netty進(jìn)行服務(wù)端開發(fā)的引導(dǎo)類。 且在上一篇的服務(wù)端示例中,我們也看到了,在使用netty進(jìn)行網(wǎng)絡(luò)編程時,我...
摘要:背景最近發(fā)現(xiàn)的回調(diào)方法,在連接創(chuàng)建成功和讀取數(shù)據(jù)后都會被回調(diào)。那我也嘗試著從源碼找到答案吧。回調(diào)流程分析的回調(diào)流程和流程沒有什么區(qū)別,可參考上文分析。但是在的方法中會調(diào)用這個是讀數(shù)據(jù)的關(guān)鍵讀數(shù)據(jù)分析讀數(shù)據(jù)分析 背景 最近發(fā)現(xiàn)ChannelOutboundHandlerAdapter的read()回調(diào)方法,在連接創(chuàng)建成功和讀取數(shù)據(jù)后都會被回調(diào)。因此就產(chǎn)生了疑問為什么建立連接和讀取數(shù)據(jù)后r...
閱讀 2643·2021-11-11 16:55
閱讀 680·2021-09-04 16:40
閱讀 3078·2019-08-30 15:54
閱讀 2615·2019-08-30 15:54
閱讀 2403·2019-08-30 15:46
閱讀 404·2019-08-30 15:43
閱讀 3227·2019-08-30 11:11
閱讀 2983·2019-08-28 18:17