Netty源码分析----pipeline

(*文章基于Netty4.1.22版本)

介绍

Netty中随着一个Channel的创建,会连带创建一个ChannelPipeline,这个ChannelPipeline就像一个处理各种事件的管道,负责去处理Channel上发生的事件,例如连接事件,读事件,写事件等。
更深入的说,处理的并不是ChannelPipeline,而是ChannelPipeline中一个个的ChannelHandler,其结构如下
image.png
ChannelPipeline中有很多Handler(其实是Context类型,Context封装了Handler),组成了一个双向的链表,同时初始化的时候就会带有一个头结点和尾结点,自定义的ChannelHandler都会添加到Head和Tail之间。
同时Netty定义了两种事件:
- inbound:事件从Head往Tail方向传递,实现ChannelInboundHandler的ChannelHandler为处理inbound事件的ChannelHandler
- outbound:事件从Tail往Head方向传递,实现ChannelOutboundHandler的ChannelHandler为处理inbound事件的ChannelHandler

ChannelPipeline如何与ChannelHandler关联

先看下ChannelPipeline接口的部分定义

public interface ChannelPipeline
        extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {

    ChannelPipeline addFirst(String name, ChannelHandler handler);

    ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);

    ChannelPipeline addLast(String name, ChannelHandler handler);

    ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);

    ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);

    ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);

    ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);

    ChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);

    ChannelPipeline addFirst(ChannelHandler... handlers);

    ChannelPipeline addFirst(EventExecutorGroup group, ChannelHandler... handlers);

    ChannelPipeline addLast(ChannelHandler... handlers);

    ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);
}

通过方法名称,大概可以看出其工作原理以及每个方法是做什么的

再看下Channel启动初始化的时候,默认是DefaultChannelPipeline(从这里可以看出,Channel总是对应一个pipeline)

    protected AbstractChannel(Channel parent) {
        //....
        pipeline = newChannelPipeline();
    }
    protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }

那么看下DefaultChannelPipeline的对方法的实现

    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;
    }

默认初始化了Head和Tail,并且将ChannelPipeline对应的Channel也保存了下来。
接下来,以文章Netty源码分析—-服务启动之Channel初始化中的Netty的demo中的这句代码为例,分析一下其中实现

socketChannel.pipeline().addLast(new NettyServerHandler());

这里将一个自定义的ChannelHandler加入到了ChannelPipeline中

    public final ChannelPipeline addLast(ChannelHandler handler) {
        return addLast(null, handler);
    }
    @Override
    public final ChannelPipeline addLast(String name, ChannelHandler handler) {
        return addLast(null, name, handler);
    }
    @Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);//判断是否重复添加并设置add属性为true
            // 将Handler封装成AbstractChannelHandlerContext
            newCtx = newContext(group, filterName(name, handler), handler);

            addLast0(newCtx);

            // 这就是服务启动那篇文章讲过的
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }
            // executor属性如果为空,则返回EventLoop
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {// 异步执行任务
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

newContext主要创建了一个DefaultChannelHandlerContext对象,看下其构造方法

    DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
        super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
        if (handler == null) {
            throw new NullPointerException("handler");
        }
        this.handler = handler;
    }
    //父类的构造方法
    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;
    }

保存了Handler已经所属的Pipeline,还有这个Handler属于inbound还是outbound等属性,以及触发事件在Pipeline中的传播,后续会分析。
主要看下addLast0方法的实现:

    private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }

逻辑很简单,将Context加入到Tail前面,链表的相关知识,不再分析引用的变化过程。
其他方法也是类似的过程,此处省略

InBound事件在Pipeline中传播

以注册时间为例,在之前讲过,注册调用的是AbstractUnsafe的register0方法,其中有句代码如下:

pipeline.fireChannelRegistered();

在这里就触发了事件的传播,看下其实现

    public final ChannelPipeline fireChannelRegistered() {
        AbstractChannelHandlerContext.invokeChannelRegistered(head);
        return this;
    }

直接调用了静态方法,传入Head,表示从Head开始传播
“` java
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}

next是HeadContext,看下他的invokeChannelRegistered方法(实际在父类AbstractChannelHandlerContext中)
``` java 
    private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }




<div class="se-preview-section-delimiter"></div>

这里又调回了HeadContext的channelRegistered方法

        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            invokeHandlerAddedIfNeeded();
            ctx.fireChannelRegistered();
        }




<div class="se-preview-section-delimiter"></div>

invokeHandlerAddedIfNeeded方法这个在服务启动的文章中分析过,回调在Channel注册前添加的Handler,该方法只会调用一次。
再看下fireChannelRegistered方法,这个实现在父类AbstractChannelHandlerContext中实现,没有传入参数,这个时候会去寻找下一个节点,进行调用

    @Override
    public ChannelHandlerContext fireChannelRegistered() {
        invokeChannelRegistered(findContextInbound());
        return this;
    }
    private AbstractChannelHandlerContext findContextInbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.next;
        } while (!ctx.inbound);
        return ctx;
    }




<div class="se-preview-section-delimiter"></div>

先通过findContextInbound方法,遍历链表,找到当前Context后面第一个inbound类型的Context(在addLast方法中,将Handler封装成Context对象的时候,就已经将inbound赋值,具体看上面),然后再又调用invokeChannelRegistered方法,这时的参数就不是Head了,而是Head后一个节点,这样通过递归的方式往后调用,就形成了事件在Pipeline中的传播。

  • 注意:传播靠的是Handler中再调用一次fireChannelXX方法,这个方法会往后找合适的Handler进行传播

为了说明这个问题,我们看下Nettydemo中自定义的Handler
首先是先使用addLast方法添加一个自定义的Handler到pipeline中

    ch.pipeline().addLast(new StringDecoder()).addLast(new ServerHandler())

    private static class ServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg)
                throws Exception {
            String body = (String)msg;
            System.out.println("receive body:"+body );
        }
    }




<div class="se-preview-section-delimiter"></div>

而Handler的逻辑也很简单,就是打印一下接收到的信息,结果很明显,当Client往Server发送了一条消息,控制台就打印

receive body:XXXX

假设,我们有多个自定义的Handler

ch.pipeline().addLast(new StringDecoder()).addLast(new ServerHandler()).addLast(new ServerHandler())




<div class="se-preview-section-delimiter"></div>

如上,我们添加了两个自定义的Handler,那么我们是想事件依次通过两个Handler进行不同的处理(这里两个Handler同样的功能,只为说明问题),那么结果是Client发送一条消息,而Server打印两次

  • 结果呢?

结果当然肯定打印了一次啦,不然我写那么多结果和预想一样,不就是在凑字数么=_=….

  • 那么怎么样才可以让多个Handler都执行呢?

只需要在Handler最后加一句代码就OK了

ctx.fireChannelRead(msg);




<div class="se-preview-section-delimiter"></div>

这个上面有分析过,会找到下一个对应类型的Context然后调用。
所以我觉得Netty的Pipeline的Inbound传播过程和下图更像
image.png
上面例子中,那个问题就是”往后传播”这个步骤漏了。而一些自带的Handler,都会触发这样的步骤,所以添加多个也是可以一路处理到达你的Hadnler

注意:这里有一个问题,假设Context2处理完后继续往后传播,那么就会到了Tail,这会出现一个问题,以ChannelRead为例,看下Tail的实现

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            onUnhandledInboundMessage(msg);
        }
    protected void onUnhandledInboundMessage(Object msg) {
        try {
            logger.debug(
                    "Discarded inbound message {} that reached at the tail of the pipeline. " +
                            "Please check your pipeline configuration.", msg);
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }




<div class="se-preview-section-delimiter"></div>

Netty任务一个事件到达Tail,表示前面Pipeline中没有正确的处理事件,并让其无奈传播到Tail,所以这里打印了一个日志,大概意思大家也懂

writeAndFlush/write与OutBound事件传播的关系

上面讲了InBound的事件传播,InBound事件是IO线程触发的事件,例如read,active等读事件,而OutBound事件是用户自己触发的事件,例如Netty应用中,最常用的就是

        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            // ....
            ctx.writeAndFlush(writeBuf);
        }




<div class="se-preview-section-delimiter"></div>

或者

        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            // ....
            Channel channel = ctx.channel();
            channel.writeAndFlush(writeBuf);
        }




<div class="se-preview-section-delimiter"></div>

这种write的方式,会触发OutBound事件的传播,下面来说一下是如何传播的,且上面两者write方式触发的事件传播的区别

ctx.writeAndFlush

该方法会调用到AbstractChannelHandlerContext.write(Object, boolean, ChannelPromise)方法,前面的和事件传播无关,暂时不看

    private void write(Object msg, boolean flush, ChannelPromise promise) {
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }




<div class="se-preview-section-delimiter"></div>

在讲Inbound事件传播机制的时候,说过每次传播会遍历Pipeline中的Handler然后找到Inbound类型进行调用,对于Outbound事件也是类似的,通过findContextOutbound去找到Outbound类型的事件

    private AbstractChannelHandlerContext findContextOutbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }




<div class="se-preview-section-delimiter"></div>

注意这里,从当前的节点开始往前找,这个this是我们自定义的Handler,而之前的例子中,prev只有一个Head。
为了更能说明传播流程,我在Demo中多加了一个Outbound类型的Handler

socketChannel.pipeline()
                            .addLast(new StringDecoder())
                            .addLast(new StringEncoder())
                            .addLast(new NettyServerHandler());




<div class="se-preview-section-delimiter"></div>

这时候,findContextOutbound找到的就是StringEncoder这个Outbound类型的Handler,
然后调用无论走哪个分支,调用AbstractChannelHandlerContext.invokeWrite0(Object, ChannelPromise)方法

    private void invokeWrite0(Object msg, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler()).write(this, msg, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }




<div class="se-preview-section-delimiter"></div>

由于第一个找到的Outbound类型的Handler是StringEncoder,那么看下其write方法

    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        //....
                ctx.write(msg, promise);
        //....
    }




<div class="se-preview-section-delimiter"></div>

这个时候,又调用了write方法,然后又回到AbstractChannelHandlerContext.write(Object, boolean, ChannelPromise)方法,然后继续调用findContextOutbound方法,而这时候的this是StringEncoder,所以找到的是HeadContext,然后再调用HeadContext的write方法,这样形成一个递归的调用,Outbound事件就是这样传播的

Channel.writeAndFlush

看下Channel的writeAndFlush方法,其调用的是AbstractChannel方法

    @Override
    public ChannelFuture writeAndFlush(Object msg) {
        return pipeline.writeAndFlush(msg);
    }




<div class="se-preview-section-delimiter"></div>

pipiline的writeAndFlush方法如下:

    @Override 
    public final ChannelFuture write(Object msg) {
        return tail.write(msg);
    }

总结:直接使用Channel.writeAndFlush会从Tail开始传播,而使用ctx.writeAndFlush则是从当前Handler开始往前传播

服务启动中涉及到的Pipeline的相关知识

看到服务启动分析的文章中,会有一些操作pipeline的代码,可能一开始看的时候不太清楚流程,当分析完pipeline后,这部分的内容也可以充分的了解了

猜你喜欢

转载自blog.csdn.net/u013160932/article/details/80474375