Netty源码分析----pipeline

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

介绍

Netty中随着一个Channel的创建,会连带创建一个ChannelPipeline,这个ChannelPipeline就像一个处理各种事件的管道,负责去处理Channel上发生的事件,例如连接事件,读事件,写事件等。
更深入的说,处理的并不是ChannelPipeline,而是ChannelPipeline中一个个的ChannelHandler,其结构如下


Netty源码分析----pipeline_第1张图片
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

ChannelHandler和ChannelHandlerContext

ChannelHandler

先看下ChannelHandler接口的定义

public interface ChannelHandler {
    // handler被添加进pipeline的时候的回调方法
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;
    // handler从pipeline中移除的时候的回调方法
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
}

另外两个常用的接口如下,这两个接口定义了Netty的两种事件流:Inbound和Outbound

public interface ChannelOutboundHandler extends ChannelHandler {

    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;

    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    void read(ChannelHandlerContext ctx) throws Exception;

    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    void flush(ChannelHandlerContext ctx) throws Exception;
}

public interface ChannelInboundHandler extends ChannelHandler {

    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    void channelActive(ChannelHandlerContext ctx) throws Exception;

    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
}

接口不做解释,从方法名称可以知道每个方法大概关联的操作,另外有一点比较重要的是,ChannelOutboundHandler和ChannelInboundHandler两个划分两种事件流,而每个方法就代表了每种事件流下的事件,举个例子来说,用户调用的write、flush或者connect等都属于Outbound事件,而且Outbound一般都是用户触发

ChannelHandlerContext

接来下看下ChannelHandlerContext接口的核心方法

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {
    // 返回对应的Channel对象
    Channel channel();
    // 返回对应的EventLoop对象
    EventExecutor executor();
    // 返回对应的ChannelHandler对象
    ChannelHandler handler();
    // 该Context对应的Handler是否从pipeline中移除
    boolean isRemoved();
    /*******************以下的fire方法都是触发对应事件在pipeline中传播*********************/
    ChannelHandlerContext fireChannelRegistered();

    ChannelHandlerContext fireChannelUnregistered();

    ChannelHandlerContext fireChannelActive();

    ChannelHandlerContext fireChannelInactive();

    ChannelHandlerContext fireExceptionCaught(Throwable cause);

    ChannelHandlerContext fireUserEventTriggered(Object evt);

    ChannelHandlerContext fireChannelRead(Object msg);

    ChannelHandlerContext fireChannelReadComplete();

    ChannelHandlerContext fireChannelWritabilityChanged();

    ChannelHandlerContext read();

    ChannelHandlerContext flush();
    // 返回对应的pipeline
    ChannelPipeline pipeline();
}

从方法中可以看出,Context和Pipeline、Handler、Channel、EventLoop是一对一的关系。
方法定义中有很多fire方法,是触发事件传播的入口,那么也可以看出Context是一个触发事件传播的结构

看下Pipeline里使用的DefaultChannelHandlerContext及其父类

final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
    private final ChannelHandler handler;

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

    // 返回封装的Handler
    public ChannelHandler handler() {
        return handler;
    }
    // 通过handler继承的类的类型判断是Inbound还是Outbound
    private static boolean isInbound(ChannelHandler handler) {
        return handler instanceof ChannelInboundHandler;
    }
    // 通过handler继承的类的类型判断是Inbound还是Outbound
    private static boolean isOutbound(ChannelHandler handler) {
        return handler instanceof ChannelOutboundHandler;
    }
}

父类代码如下,只贴了部分代码

abstract class AbstractChannelHandlerContext extends DefaultAttributeMap
        implements ChannelHandlerContext, ResourceLeakHint {
    // Context是pipeline中的一个节点,是双向链表,这里保存了两个指针类型便于向前和向后遍历
    volatile AbstractChannelHandlerContext next;
    volatile AbstractChannelHandlerContext prev;

    private static final AtomicIntegerFieldUpdater HANDLER_STATE_UPDATER =
            AtomicIntegerFieldUpdater.newUpdater(AbstractChannelHandlerContext.class, "handlerState");
    //当handlerAdded方法被调用的方式不是马上调用的时候,会设置为该状态
    // 例如pipeline中调用callHandlerCallbackLater或者在EventLoop的execute方法中执行
    // 这种情况是延迟执行,或者说不是马上执行,需要有个中间状态
    private static final int ADD_PENDING = 1;
    // handlerAdded调用前设置的状态
    private static final int ADD_COMPLETE = 2;
    // 当节点被移除之后设置的状态
    private static final int REMOVE_COMPLETE = 3;
    // 初始状态
    private static final int INIT = 0;
    // handler的类型
    private final boolean inbound;
    private final boolean outbound;
    private final DefaultChannelPipeline pipeline;
    private final String name;
    private final boolean ordered;
    // 当channelReadComplete、read、channelWritableStateChanged、flush4个四个事件发生的时候
    // 如果不在EventLoop的线程中,那么会转换成Runnable对象放到EventLoop线程中处理
    private Runnable invokeChannelReadCompleteTask;
    private Runnable invokeReadTask;
    private Runnable invokeChannelWritableStateChangedTask;
    private Runnable invokeFlushTask;

    private volatile int handlerState = INIT;

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

    @Override
    public Channel channel() {
        return pipeline.channel();
    }

    // 返回pipeline
    public ChannelPipeline pipeline() {
        return pipeline;
    }
    
    final EventExecutor executor;
    // 一般调用pipeline添加context的时候都没传这个参数,所以为空
    // 如果为空,则获取Channel的EventLoop
    // 而Channel和EventLoop绑定是在注册时候,也就是说,在注册完成前是该返回是空
    public EventExecutor executor() {
        if (executor == null) {
            return channel().eventLoop();
        } else {
            return executor;
        }
    }
}

ChannelPipeline如何与ChannelHandler关联

先看下ChannelPipeline接口的部分定义

public interface ChannelPipeline
        extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable> {

    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);
            // 下面会触发handlerAdded方法,根据不同情况判断该以什么方式调用
            // 未注册的话走Callback流程
            if (!registered) {
                // 将状态设置成ADD_PENDING
                // 然后handlerAdded的调用将换成callback
                // 等注册完的时候会调用callback,然后调用handlerAdded方法
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }
            EventExecutor executor = newCtx.executor();
            // 如果和EventLoop不是同个线程
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                // 将handlerAdded的调用放到队列,等待EventLoop线程执行
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        // 如果是在EventLoop线程中,那么直接执行
        callHandlerAdded0(newCtx);
        return this;
    }

newContext主要创建了一个DefaultChannelHandlerContext对象,构造方法之前已经描述过了。
callHandlerAdded0方法主要是将Context的状态设置为ADD_COMPLETE和调用handlerAdded方法

addLast0方法的实现:

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

逻辑很简单,将Context加入到Tail前面,链表的相关知识,不再分析引用的变化过程。

callHandlerCallbackLater方法如下:

    private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
        assert !registered;
        // PendingHandlerAddedTask是调用callHandlerAdded0方法
        // PendingHandlerRemovedTask是调用callHandlerRemoved0方法
        PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
        PendingHandlerCallback pending = pendingHandlerCallbackHead;
        // 方法链表中等待后续遍历调用
        if (pending == null) {
            pendingHandlerCallbackHead = task;
        } else {
            while (pending.next != null) {
                pending = pending.next;
            }
            pending.next = task;
        }
    }

其他方法也是类似的过程,此处省略

InBound事件在Pipeline中传播

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

pipeline.fireChannelRegistered();

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

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

直接调用了静态方法,传入Head,表示从Head开始传播

   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中)

    private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

这里又调回了HeadContext的channelRegistered方法

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

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

先通过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 );
        }
    }

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

receive body:XXXX

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

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

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

  • 结果呢?

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

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

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

ctx.fireChannelRead(msg);

这个上面有分析过,会找到下一个对应类型的Context然后调用。
所以我觉得Netty的Pipeline的Inbound传播过程和下图更像


Netty源码分析----pipeline_第2张图片
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);
        }
    }

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

或者

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

这种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);
        }
    }

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

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

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

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

这时候,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);
        }
    }

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

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

这个时候,又调用了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);
    }

pipiline的writeAndFlush方法如下:

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

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

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

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

总结

一个Channel在创建的时候就会创建一个对应的ChannelPipeline,
通过上面的分析,可以看到ChannelPipeline的设计是线程安全的,有很多地方的操作就是为了这个线程安全做了很多的操作,例如addLast调用handlerAdded会转换成EventLoop队列任务,Netty中很多地方都是类似的,为了避免这种多线程操作的问题都是先转成队列的任务,从而转换成单线程的操作,这种设计需要好好琢磨

你可能感兴趣的:(Netty源码分析----pipeline)