第四章 Netty深入剖析之pipeline及事件传播机制

通过前面的章节我们可能注意到,我们有些细节没有将上下文调用关系,就直接看核心代码了,原因就是没有讲解netty pipeline的事件传播机制导致的,等学完本讲,我相信小伙伴们已经有能力自己试着理解之前缺失的内容了。

目录

pipeline的初始化过程

添加channelHandler 和删除 channelHandler

inBound事件的传播

outBound事件的传播

异常的传播

总结


在讲解pipeline的事件传播机制之前我们先来看一下pipeline的初始化过程

 

pipeline的初始化过程

pipeline的创建是在channel初始化是创建的,从这里我们就能看出端倪,pipeline必然是维护了一个默认有两个元素的双向链表队列,但我们看到这里面默认加入了TailContext和HeadContext的实例化对象,那这两个对象又是什么呢,有什么作用呢?其实他们都是ChannelHandlerContext的实现类,该实现类的集成关系如下图。我们按照接口即功能的原则理解即可。那我们在来看看TailContext和HeadContext又有什么特殊,我们看到他们分别继承了AbstractChannelHandlerContext。

 protected DefaultChannelPipeline(Channel channel) {
        
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);
        //分别为头处理器和尾处理器,并将处理请填充到TailContext中
        tail = new TailContext(this);
        head = new HeadContext(this);
//从这里我们就能看出端倪,pipeline必然是维护了一个默认有两个元素的双向链表队列
        head.next = tail;
        tail.prev = head;
   }

添加channelHandler 和删除 channelHandler

添加和算出也没什么好说的就是简单的设置前一个元素的下一个指向和后一个元素的前一个指向。不过这里有一个重要的知识点事我们用户添加的ChannelInitializer类型的 childHandler是如何将我们自定义的handler添加到pipeLine中呢?是因为新的客户端连接接入是在进行注册的上面讲childHandler添加到其pipeline中,通过addLast方法,他会回调添加的这个channelhandlerContext的handler的.handlerAdded(ctx)方法,而ChannelInitializer的handlerAdded实现逻辑是回调用户自定义的代码将用户自定义的channelhandlercontext添加到pipeline中,之后从pipeline中删除该handler,代码如下

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);
            //此处常见了一个DefaultChannelHandlerContext,填充了我们添加的handler
            newCtx = newContext(group, filterName(name, handler), handler);
            //这里今天元素的添加,这是一个ChannelHandlerContext类型的双向列表
            addLast0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            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;
    }
//我们看到就是简单的移动指针
private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }

//最终事件到ServerBootstraphandler中进行处理

public void channelRead(ChannelHandlerContext ctx, Object msg) {

            final Channel child = (Channel) msg;
            /*******************************************************
*********************************************************/
            //将我们用户代码的childHandler放入socketchannel的pipeline中

            child.pipeline().addLast(childHandler);

          
            //childGroup.register(child)这里就是我们本节要分析的重点了,我们看到会调用childGroup.register(child)将客户端channel进行注册

          
        }
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);
        //他会回调添加的这个channelhandlerContext的handler的.handlerAdded(ctx)方法
        callHandlerAdded0(newCtx);
        return this;
    }

//而ChannelInitializer的handlerAdded实现
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
         
            initChannel(ctx);
        }
    }


private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
            try {
                //这就是我们用户代码中实现的方法了
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
               
                exceptionCaught(ctx, cause);
            } finally {
                //在添加完成后我们在pipeline中将这个channelhandlerContext删除出队列
                remove(ctx);
            }
            return true;
        }
        return false;
    }

 

 

inBound事件的传播

channel的继承类图如下:

 ChannelHandler接口的具体功能参见代码,详细赘述读者可以参考代码注释理解,我们这里通过一个样例来进行讲解

 

public final class Server {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new InBoundHandlerA());
                            ch.pipeline().addLast(new InBoundHandlerB());
                            ch.pipeline().addLast(new InBoundHandlerC());
                        }
                    });

            ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

 

package com.imooc.netty.ch6;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @author
 */
public class InBoundHandlerA extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerA: " + msg);
        ctx.fireChannelRead(msg);
    }
}
package com.imooc.netty.ch6;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @author
 */
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerB: " + msg);
        ctx.fireChannelRead(msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.channel().pipeline().fireChannelRead("hello world");
    }
}
package com.imooc.netty.ch6;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @author
 */
public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerC: " + msg);
        ctx.fireChannelRead(msg);
    }
}

 当我们一个请求连接上来是消息的打印顺序为:

InBoundHandlerA: hello world
InBoundHandlerB: hello world
InBoundHandlerC: hello world

 下面正式进入我们本小节内容的讲解我们从InBoundHandlerB的channelActive()可以看到通过pipeline触发了一个fireChannelRead事件,我们跟进去到headContext的事件处理方法,我们看到该方法将事件向下传递, ctx.fireChannelRead(msg),我们注意到这和通过pipeline进行事件的传播不同使用的headContext的事件传播方法,这里有什么不同呢,我们点进去查看,我们简单的概括就是该方法查询当前context节点的下一个inbound类型的context节点,并调用其该事件的处理方法。我们自然也就清楚了pipeline的事件触发机制和ChannelHandlerContext的事件触发机制的不同了,pipeline会调用指定的某一个节点进行传播,一般是头结点或者尾结点,而ChannelHandlerContext的事件触发机制是当前节点的上一个或者下一个节点传播的。

public final ChannelPipeline fireChannelRead(Object msg) {
        //会调用AbstractChannelHandlerContext的静态方法函数,并传递我们双向列表ChannelHandlerContext的head头结点
        AbstractChannelHandlerContext.invokeChannelRead(head, msg);
        return this;
    }

//从head context 开始调用其invokeChannelRead方法
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }
    }

private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                //我们看到会回调用headContext的channelRead方法进行处理
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

@Override
//headContext 的channelRead什么都不做只是进行消息的向下传递
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ctx.fireChannelRead(msg);
        }

 

public ChannelHandlerContext fireChannelRead(final Object msg) {
        invokeChannelRead(findContextInbound(), msg);
        return this;
    }

//该方法从当前节点开始向下寻找寻找到最近的一个为inbound类型的Context,如何确定是inbound类型的呢,是通过我们构造函数有一个标志位true或false确定的
 private AbstractChannelHandlerContext findContextInbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.next;
        } while (!ctx.inbound);
        return ctx;
    }

//这段逻辑和上面的相同知道事件传播完成
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }
    }

 

outBound事件的传播

我们围绕分析的案例如下:

public final class Server {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new OutBoundHandlerA());
                            ch.pipeline().addLast(new OutBoundHandlerB());
                            ch.pipeline().addLast(new OutBoundHandlerC());
                        }
                    });

            ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
package com.imooc.netty.ch6;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * @author
 */
public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerA: " + msg);
        ctx.write(msg, promise);
    }
}
package com.imooc.netty.ch6;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

import java.util.concurrent.TimeUnit;

/**
 * @author
 */
public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerB: " + msg);
        ctx.write(msg, promise);
    }


    @Override
    public void handlerAdded(final ChannelHandlerContext ctx) {
        ctx.executor().schedule(() -> {
            ctx.channel().write("hello world");
          
        }, 3, TimeUnit.SECONDS);
    }
}

 

package com.imooc.netty.ch6;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * @author
 */
public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerC: " + msg);
        ctx.write(msg, promise);
       
    }
}

 我们围绕上面的这个案例展开我们的具体分析,关于何时调用的OutBoundHandlerB的handlerAdded()处理方法上文已经讨论过了,我们直接跟进write()方法,

 

 public ChannelFuture write(Object msg) {
//调用pipeline的write事件
        return pipeline.write(msg);
    }
//这里调用末尾context的write处理方法
public final ChannelFuture write(Object msg) {
        return tail.write(msg);
    }
private void write(Object msg, boolean flush, ChannelPromise promise) {
//与inbound类似,但是会向上查找与之最近的切类型为Outbound的context并调用,知道事件停止传播或到headContext
        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);
        }
    }

 

异常的传播

分析样例:

public class InBoundHandlerA extends ChannelInboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InBoundHandlerA.exceptionCaught()");

        ctx.fireExceptionCaught(cause);
    }
}
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        throw new BusinessException("from InBoundHandlerB");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InBoundHandlerB.exceptionCaught()");

        ctx.fireExceptionCaught(cause);
    }
}
public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InBoundHandlerC.exceptionCaught()");

        ctx.fireExceptionCaught(cause);
    }
}

 

InBoundHandlerB.exceptionCaught()
InBoundHandlerC.exceptionCaught()

 我们查看结果其只会调用当前发生异常的context级下面的context,查看调用channelRead()方法的上下文,在调用read处理事件的上下文中有一个trycatch ,如果抛出异常会调用该方法 notifyHandlerException,该方法首先调用自己的handler的exceptionCaught进行事件的处理,但是我们知道我们的处理方式是继续向下传播该事件,也就是回到这里,继续向下传播该事件
ctx.fireExceptionCaught(cause),极其简单粗暴的直接使用next指向的节点调用其ExceptionCaught方法,知道末尾。结合其特性,我们给出异常处理器的最佳实践就是将异常处理器定义到队列的最未出,集中进行处理。

private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
//如果抛出异常会调用该方法
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }
private void invokeExceptionCaught(final Throwable cause) {
        if (invokeHandler()) {
            try {
//首先调用自己的handler的exceptionCaught进行事件的处理,但是我们知道我们的处理方式是继续向下传播该事件
                handler().exceptionCaught(this, cause);
            } catch (Throwable error) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                        "An exception {}" +
                        "was thrown by a user handler's exceptionCaught() " +
                        "method while handling the following exception:",
                        ThrowableUtil.stackTraceToString(error), cause);
                } else if (logger.isWarnEnabled()) {
                    logger.warn(
                        "An exception '{}' [enable DEBUG level for full stacktrace] " +
                        "was thrown by a user handler's exceptionCaught() " +
                        "method while handling the following exception:", error, cause);
                }
            }
        } else {
            fireExceptionCaught(cause);
        }
    }

 

 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InBoundHandlerB.exceptionCaught()");
        //也就是回到这里,继续向下传播该事件
        ctx.fireExceptionCaught(cause);
    }
 public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
        invokeExceptionCaught(next, cause);
        return this;
    }

public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
//我们看到其直接调用下一个节点的ExceptionCaught方法
        invokeExceptionCaught(next, cause);
        return this;
    }

//该逻辑我们上文讲解过了
static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) {
        ObjectUtil.checkNotNull(cause, "cause");
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeExceptionCaught(cause);
        } else {
            try {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        next.invokeExceptionCaught(cause);
                    }
                });
            } catch (Throwable t) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Failed to submit an exceptionCaught() event.", t);
                    logger.warn("The exceptionCaught() event that was failed to submit was:", cause);
                }
            }
        }
    }

总结

到此为止netty的事件处理机制就算是讲解完毕了,并且简单的拿到几个案例进行消息传播的原理分析,这里简单总结一下使用pipeline产生事件的话inbound 从队头开始,outbound从队未开始,如果是使用channelhandlercontext产生事件的话从当前节点开始,方向与上文一致。而异常传播从当前节点一直向下传播。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值