摘要:在上一篇源碼實戰(zhàn)系列三全剖析中,我們詳細分析了的初始化過程,并得出了如下結(jié)論在中,每一個都有一個對象,并且其內(nèi)部本質(zhì)上就是一個雙向鏈表本篇我們將深入源碼內(nèi)部,對其一探究竟,給大家一個全方位解析。
在上一篇《Netty4.x 源碼實戰(zhàn)系列(三):NioServerSocketChannel全剖析》中,我們詳細分析了NioServerSocketChannel的初始化過程,并得出了如下結(jié)論:
在netty中,每一個channel都有一個pipeline對象,并且其內(nèi)部本質(zhì)上就是一個雙向鏈表
本篇我們將深入Pipeline源碼內(nèi)部,對其一探究竟,給大家一個全方位解析。
Pipeline初始化過程分析在上一篇中,我們得知channel中的pipeline其實就是DefaultChannelPipeline的實例,首先我們先看看DefaultChannelPipeline的類繼承結(jié)構(gòu)圖:
根據(jù)類繼承結(jié)構(gòu)圖,我們看到DefaultChannelPipeline實現(xiàn)了 ChannelInboundInvoker及ChannelOutboundInvoker兩個接口。
顧名思義,一個是處理通道的inbound事件調(diào)用器,另一個是處理通道的outbound事件調(diào)用器。
inbound: 本質(zhì)上就是執(zhí)行I/O線程將從外部read到的數(shù)據(jù) 傳遞給 業(yè)務線程的一個過程。
outbound: 本質(zhì)上就是業(yè)務線程 將數(shù)據(jù) 傳遞給I/O線程, 直至發(fā)送給外部的一個過程。
如下圖所示:
我們再回到DefaultChannelPipeline這個類,看看其構(gòu)造方法:
protected DefaultChannelPipeline(Channel 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; }
此構(gòu)造方法主要做了三件事:
1、綁定了當前NioServerSocketChannel實例
2、初始化pipeline雙向鏈表的頭、尾節(jié)點
關(guān)于NioServerSocketChannel,我在前一篇中已經(jīng)做過詳細描述,現(xiàn)在我們著重看看head及tail這兩個屬性。
從上面的構(gòu)造方法得知,head是HeadContext的實例,tail是TailContext的實例,HeadContext與TailContext都是DefaultChannelPipeline的內(nèi)部類,它們的類繼承結(jié)構(gòu)圖如下:
HeadContext類繼承結(jié)構(gòu)圖
TailContext類繼承結(jié)構(gòu)圖
從類繼承圖我們可以看出:
1、HeadContext與TailContext都是通道的handler(中文一般叫做處理器)
2、HeadContext既可以用于outbound過程的handler,也可以用于inbound過程的handler (關(guān)于inboun和outbound上面已經(jīng)作了解釋)
3、TailContext只可以用于inbound過程的handler
4、HeadContext 與 TailContext 同時也是一個處理器上下文對象
下面我將以HeadContext為例,看看它初始化過程中到底作了哪些工作
head = new HeadContext(this);
在DefaultChannelPipeline的構(gòu)造方法中,我們看到head結(jié)點初始化代碼如上面所示,對應構(gòu)造器代碼如下:
HeadContext(DefaultChannelPipeline pipeline) { super(pipeline, null, HEAD_NAME, false, true); unsafe = pipeline.channel().unsafe(); setAddComplete(); }
在其內(nèi)部,它會繼續(xù)調(diào)用父類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; ordered = executor == null || executor instanceof OrderedEventExecutor; }
此構(gòu)造方法,只是設(shè)置了當前context對象對應的Pipeline以及此context是作用于outbound。
AbstractChannelHandlerContext類還有另外兩個額外屬性,他們是實現(xiàn)雙向鏈表的關(guān)鍵:
volatile AbstractChannelHandlerContext next; // 指定下一個結(jié)點 volatile AbstractChannelHandlerContext prev; // 指定前一個結(jié)點
HeadContext 同時還綁定了unsafe對象,我們再回顧一下unsafe對象。
我們從上一篇已經(jīng)得知 unsafe其實就是對java nio 通道底層調(diào)用進行的封裝,就相當于一個代理類對象。
而DefaultChannelPipeline初始化時,已經(jīng)綁定了channel,且由于是服務端,所以此channel是NioServerSocketChannel
protected DefaultChannelPipeline(Channel channel) { // 通道綁定channel對象 this.channel = ObjectUtil.checkNotNull(channel, "channel"); ... //非相關(guān)代碼已省略 }
所以
unsafe = pipeline.channel().unsafe();
就是
unsafe = new NioMessageUnsafe();
關(guān)于pipeline的tail結(jié)點初始化過程跟head差不多,這里就不作贅述了。
階段性總結(jié):Pipeline在服務端bind過程中的應用
1、每個channel初始化時,都會創(chuàng)建一個與之對應的pipeline;
2、此pipeline內(nèi)部就是一個雙向鏈表;
3、雙向鏈表的頭結(jié)點是處理outbound過程的handler,尾節(jié)點是處理inbound過程的handler;
4、雙向鏈表的結(jié)點同時還是handler上下文對象;
通過《Netty4.x 源碼實戰(zhàn)系列(二):服務端bind流程詳解》 一文,我們知道,服務端channel在初始化過程中,會調(diào)用addLast方法,并傳遞了一個ChannelInitializer對象
@Override void init(Channel channel) throws Exception { // 非相關(guān)代碼已省略 ChannelPipeline p = channel.pipeline(); p.addLast(new ChannelInitializer() { @Override public void initChannel(final Channel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); ChannelHandler handler = config.handler(); if (handler != null) { pipeline.addLast(handler); } ch.eventLoop().execute(new Runnable() { @Override public void run() { pipeline.addLast(new ServerBootstrapAcceptor( ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); } }); }
本節(jié)我們將詳細分析一下addLast的過程 與 ChannelInitializer。
ChannelInitializer
我們先看一下ChannelInitializer的類繼承結(jié)構(gòu)圖
通過類繼承圖,我們得知ChannelInitializer的匿名對象其實就是一個處理inbound過程的處理器,與pipeline中的tail一樣,目前稍有不同的就是ChannelInitializer的匿名對象并不是一個context對象。
關(guān)于ChannelInitializer匿名對象的initChannel方法實現(xiàn)的內(nèi)容,本篇先不作詳述,當講到EventLoop時,我們再來回顧一下。
pipeline.addLast方法
addLast具體實現(xiàn)如下:
@Override public final ChannelPipeline addLast(ChannelHandler... handlers) { return addLast(null, handlers); }
通過此方法參數(shù),我們也可以得出,init(Channel channel)方法中的addLast其實就是想Pipeline中添加一個處理器。
addLast內(nèi)部繼續(xù)調(diào)用另一個重載方法:
@Override public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) { if (handlers == null) { throw new NullPointerException("handlers"); } for (ChannelHandler h: handlers) { if (h == null) { break; } addLast(executor, null, h); } return this; }
最終調(diào)用的是下面的重載方法(已省略非相關(guān)代碼):
@Override public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) { final AbstractChannelHandlerContext newCtx; synchronized (this) { checkMultiplicity(handler); newCtx = newContext(group, filterName(name, handler), handler); addLast0(newCtx); } return this; }
newContext方法的作用就是對傳入的handler進行包裝,最后返回一個綁定了handler的context對象:
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) { return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler); }
新的對象是DefaultChannelHandlerContext類的實例。
接著我們再看看addLast0方法
private void addLast0(AbstractChannelHandlerContext newCtx) { AbstractChannelHandlerContext prev = tail.prev; newCtx.prev = prev; newCtx.next = tail; prev.next = newCtx; tail.prev = newCtx; }
經(jīng)過addLast0,新包裝的context已經(jīng)添加至pipeline中了,此時的pipeline結(jié)果變化過程如下:
從addLast0代碼片段得知, 每個新添加的結(jié)點,都是從tail結(jié)點之前插入
本篇總結(jié):
經(jīng)過本篇的代碼研究,對于Pipeline得出以下結(jié)論:
1、channel初始化時,會同時創(chuàng)建一個與之對應的pipeline;
2、此pipeline本質(zhì)上是一個handler處理器雙向鏈表, 用于將處理inbound及outbound過程的handler都串聯(lián)起來;
3、在netty中,對于I/O處理分為兩種流向,對于獲取外部數(shù)據(jù)資源進行處理的,都是對應inbound,比如read等,而對于向外部發(fā)送數(shù)據(jù)資源的,都對于outbound,比如connetct及write等。
關(guān)于pipeline中的handler調(diào)用過程,后面的章節(jié)我們會做詳細分析。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/68101.html
摘要:對于,目前大家只知道是個線程組,其內(nèi)部到底如何實現(xiàn)的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,這里不作詳細介紹,后面會有文章作專門詳解。 在上一篇《ServerBootstrap 與 Bootstrap 初探》中,我們已經(jīng)初步的了解了ServerBootstrap是netty進行服務端開發(fā)的引導類。 且在上一篇的服務端示例中,我們也看到了,在使用netty進行網(wǎng)絡(luò)編程時,我...
摘要:本篇將通過實例化過程,來深入剖析。及初始化完成后,它們會相互連接。我們在回到的構(gòu)造方法父類構(gòu)造方法調(diào)用完成后,還要初始化一下自己的配置對象是的內(nèi)部類而又是繼承自,通過代碼分析,此對象就是就會對底層一些配置設(shè)置行為的封裝。 根據(jù)上一篇《Netty4.x 源碼實戰(zhàn)系列(二):服務端bind流程詳解》所述,在進行服務端開發(fā)時,必須通過ServerBootstrap引導類的channel方法來...
摘要:而用于主線程池的屬性都定義在中本篇只是簡單介紹了一下引導類的配置屬性,下一篇我將詳細介紹服務端引導類的過程分析。 從Java1.4開始, Java引入了non-blocking IO,簡稱NIO。NIO與傳統(tǒng)socket最大的不同就是引入了Channel和多路復用selector的概念。傳統(tǒng)的socket是基于stream的,它是單向的,有InputStream表示read和Outpu...
摘要:接下來的兩篇文章,我將從源碼角度為大家深入淺出的剖析的線程模型工作機制。我們看一下的源碼通過的代碼發(fā)現(xiàn),實現(xiàn)了接口,其內(nèi)部會通過指定的默認線程工廠來創(chuàng)建線程,并執(zhí)行相應的任務。至此,初始化完成了。下一篇我們將詳細介紹,敬請期待。 我們都知道Netty的線程模型是基于React的線程模型,并且我們都知道Netty是一個高性能的NIO框架,那么其線程模型必定是它的重要貢獻之一。 在使用ne...
摘要:一些想法這個系列想開很久了,自己使用也有一段時間了,利用也編寫了一個簡單的框架,并運用到工作中了,感覺還不錯,趁著這段時間工作不是很忙,來分析一波源碼,提升下技術(shù)硬實力。 一些想法 這個系列想開很久了,自己使用netty也有一段時間了,利用netty也編寫了一個簡單的框架,并運用到工作中了,感覺還不錯,趁著這段時間工作不是很忙,來分析一波源碼,提升下技術(shù)硬實力。 結(jié)構(gòu) 這里先看下net...
閱讀 3340·2022-01-04 14:20
閱讀 3110·2021-09-22 15:08
閱讀 2188·2021-09-03 10:44
閱讀 2316·2019-08-30 15:44
閱讀 1491·2019-08-29 18:40
閱讀 2655·2019-08-29 17:09
閱讀 2989·2019-08-26 13:53
閱讀 3222·2019-08-26 13:37