在上一篇《Netty4.x 源碼實戰系列(三):NioServerSocketChannel全剖析》中,咱們詳細分析了NioServerSocketChannel的初始化過程,並得出了以下結論:java
在netty中,每個channel都有一個pipeline對象,而且其內部本質上就是一個雙向鏈表
本篇咱們將深刻Pipeline源碼內部,對其一探究竟,給你們一個全方位解析。segmentfault
在上一篇中,咱們得知channel中的pipeline其實就是DefaultChannelPipeline的實例,首先咱們先看看DefaultChannelPipeline的類繼承結構圖:ide
根據類繼承結構圖,咱們看到DefaultChannelPipeline實現了 ChannelInboundInvoker及ChannelOutboundInvoker兩個接口。
顧名思義,一個是處理通道的inbound事件調用器,另外一個是處理通道的outbound事件調用器。oop
inbound: 本質上就是執行I/O線程將從外部read到的數據 傳遞給 業務線程的一個過程。
outbound: 本質上就是業務線程 將數據 傳遞給I/O線程, 直至發送給外部的一個過程。this
以下圖所示:spa
咱們再回到DefaultChannelPipeline這個類,看看其構造方法:線程
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; }
此構造方法主要作了三件事:
一、綁定了當前NioServerSocketChannel實例
二、初始化pipeline雙向鏈表的頭、尾節點代理
關於NioServerSocketChannel,我在前一篇中已經作過詳細描述,如今咱們着重看看head及tail這兩個屬性。netty
從上面的構造方法得知,head是HeadContext的實例,tail是TailContext的實例,HeadContext與TailContext都是DefaultChannelPipeline的內部類,它們的類繼承結構圖以下:code
HeadContext類繼承結構圖
TailContext類繼承結構圖
從類繼承圖咱們能夠看出:
一、HeadContext與TailContext都是通道的handler(中文通常叫作處理器)
二、HeadContext既能夠用於outbound過程的handler,也能夠用於inbound過程的handler (關於inboun和outbound上面已經做了解釋)
三、TailContext只能夠用於inbound過程的handler
四、HeadContext 與 TailContext 同時也是一個處理器上下文對象
下面我將以HeadContext爲例,看看它初始化過程當中到底做了哪些工做
head = new HeadContext(this);
在DefaultChannelPipeline的構造方法中,咱們看到head結點初始化代碼如上面所示,對應構造器代碼以下:
HeadContext(DefaultChannelPipeline pipeline) { super(pipeline, null, HEAD_NAME, false, true); unsafe = pipeline.channel().unsafe(); setAddComplete(); }
在其內部,它會繼續調用父類AbstractChannelHandlerContext的構造器
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; }
此構造方法,只是設置了當前context對象對應的Pipeline以及此context是做用於outbound。
AbstractChannelHandlerContext類還有另外兩個額外屬性,他們是實現雙向鏈表的關鍵:
volatile AbstractChannelHandlerContext next; // 指定下一個結點 volatile AbstractChannelHandlerContext prev; // 指定前一個結點
HeadContext 同時還綁定了unsafe對象,咱們再回顧一下unsafe對象。
咱們從上一篇已經得知 unsafe其實就是對java nio 通道底層調用進行的封裝,就至關於一個代理類對象。
而DefaultChannelPipeline初始化時,已經綁定了channel,且因爲是服務端,因此此channel是NioServerSocketChannel
protected DefaultChannelPipeline(Channel channel) { // 通道綁定channel對象 this.channel = ObjectUtil.checkNotNull(channel, "channel"); ... //非相關代碼已省略 }
因此
unsafe = pipeline.channel().unsafe();
就是
unsafe = new NioMessageUnsafe();
關於pipeline的tail結點初始化過程跟head差很少,這裏就不做贅述了。
階段性總結:
一、每一個channel初始化時,都會建立一個與之對應的pipeline;
二、此pipeline內部就是一個雙向鏈表;
三、雙向鏈表的頭結點是處理outbound過程的handler,尾節點是處理inbound過程的handler;
四、雙向鏈表的結點同時仍是handler上下文對象;
經過《Netty4.x 源碼實戰系列(二):服務端bind流程詳解》 一文,咱們知道,服務端channel在初始化過程當中,會調用addLast方法,並傳遞了一個ChannelInitializer對象
@Override void init(Channel channel) throws Exception { // 非相關代碼已省略 ChannelPipeline p = channel.pipeline(); p.addLast(new ChannelInitializer<Channel>() { @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)); } }); } }); }
本節咱們將詳細分析一下addLast的過程 與 ChannelInitializer。
ChannelInitializer
咱們先看一下ChannelInitializer的類繼承結構圖
經過類繼承圖,咱們得知ChannelInitializer的匿名對象其實就是一個處理inbound過程的處理器,與pipeline中的tail同樣,目前稍有不一樣的就是ChannelInitializer的匿名對象並非一個context對象。
關於ChannelInitializer匿名對象的initChannel方法實現的內容,本篇先不做詳述,當講到EventLoop時,咱們再來回顧一下。
pipeline.addLast方法
addLast具體實現以下:
@Override public final ChannelPipeline addLast(ChannelHandler... handlers) { return addLast(null, handlers); }
經過此方法參數,咱們也能夠得出,init(Channel channel)方法中的addLast其實就是想Pipeline中添加一個處理器。
addLast內部繼續調用另外一個重載方法:
@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; }
最終調用的是下面的重載方法(已省略非相關代碼):
@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; }
通過addLast0,新包裝的context已經添加至pipeline中了,此時的pipeline結果變化過程以下:
從addLast0代碼片斷得知, 每一個新添加的結點,都是從tail結點以前插入
本篇總結:
通過本篇的代碼研究,對於Pipeline得出如下結論:
一、channel初始化時,會同時建立一個與之對應的pipeline;
二、此pipeline本質上是一個handler處理器雙向鏈表, 用於將處理inbound及outbound過程的handler都串聯起來;
三、在netty中,對於I/O處理分爲兩種流向,對於獲取外部數據資源進行處理的,都是對應inbound,好比read等,而對於向外部發送數據資源的,都對於outbound,好比connetct及write等。
關於pipeline中的handler調用過程,後面的章節咱們會作詳細分析。