Netty源码之ChannelPipeline

这几天一直在看netty,感觉跟之前看过的tomcat原理多多少少有点类似,看了些书,觉得还是看源码比较实在,有感觉。


这个图是 netty实战上面扣来的我觉得很实在(它是经过抽象看起来两条链,实际上就一条双向链表,无非类型不符合则不处理),在ChannelPipeLine中,维护了具体处理相关通知的成员handler,组成的双向链表,链表成员为ChannelHandlerContext。左边是链表头,右边是链表尾。

根据请求的方向分为Outboundhandler跟InboundhandlerfireIN_EVT()即从链表头向尾传递,仅仅类型是Inboundhandler才处理,数据的出站运动则从链表尾部向头部传递,仅被Outboundhandler处理。出站数据将会达到网络层,Socket传出。

每一个Channel都会有一个对应的ChannelPipLine。

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        unsafe = newUnsafe();
        pipeline = new DefaultChannelPipeline(this);
    }

下面是ChannelPipLine的构造函数

    public DefaultChannelPipeline(AbstractChannel channel) {
        if (channel == null) {
            throw new NullPointerException("channel");
        }
        this.channel = channel;

        //TailHandler是DefaultChannelPipeline的一个默认实现ChannelInboundHandler的一个内部类
        TailHandler tailHandler = new TailHandler();
        tail = new DefaultChannelHandlerContext(this, null, generateName(tailHandler), tailHandler);


        //HeadHandler是DefaultChannelPipeline的一个默认实现ChannelOutboundHandler的一个内部类
        HeadHandler headHandler = new HeadHandler(channel.unsafe());
        head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler);

        head.next = tail;
        tail.prev = head;
    }

逻辑很简单,无非构造两个头尾handler跟ChannelHandlerContext,并将headContext跟tailContext构造成双向链表。之后的addLast,addFirst无非向这两之间的添加context,双向链表的头尾依旧是以上两个。

下面看addFirst()

@Override
    public ChannelPipeline addFirst(String name, ChannelHandler handler) {
        return addFirst(null, name, handler);
    }


    @Override
    public ChannelPipeline addFirst(EventExecutorGroup group, final String name, ChannelHandler handler) {
        synchronized (this) {
            //检查是否已添加同名的handler
            checkDuplicateName(name);
            //根据pipline,group,name,handler构建出一个新的DefaultChannelHandlerContext
            DefaultChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
            //添加这个newCtx到pipline:通过操作链表,将该handlerContext添加到head的后面
            addFirst0(name, newCtx);
        }

        return this;
    }

一口气贴太多可能看不过来,以上逻辑无非先检查是否有同名headler,再根据headler,pipline封装数据得到绑定的Context(其中设置了inbound/outbound属性),最后调用addFirst0,将context加入双向链表中。

    private void checkDuplicateName(String name) {
        if (name2ctx.containsKey(name)) {
            throw new IllegalArgumentException("Duplicate handler name: " + name);
        }
    }

name2ctx是一个以handler名字为key,ChannelHandlerContext为值的map;

 private void addFirst0(String name, DefaultChannelHandlerContext newCtx) {
        /**
         * 检查newCtx中的handler是否被重复添加:
         * 对于一个没有加Shareble的hanlder类,如果每次都是new出来不同的对象,是可以重复添加到
         * 同一个pipline的,但是如果是同一个对象实例,是不允许重复添加到同一个pipline的
         */
        checkMultiplicity(newCtx);
        DefaultChannelHandlerContext nextCtx = head.next;
        newCtx.prev = head;
        newCtx.next = nextCtx;
        head.next = newCtx;
        nextCtx.prev = newCtx;

        name2ctx.put(name, newCtx);

        callHandlerAdded(newCtx);
    }

可以看到,先checkMultiplicity 检查ctx中的handler是否多次添加的合法性,将新的ctx插入到head节点后。顺便将新的存入name2ctx中。并调用handlerAdded触发的事件。

    private static void checkMultiplicity(ChannelHandlerContext ctx) {
        ChannelHandler handler = ctx.handler();
        if (handler instanceof ChannelHandlerAdapter) {
            ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
            /**
             * 1.判断当前添加的ChannelHandlerContext的handler是否加了注解@Sharable
             * 2.判断handler是否已经被添加过
             * 如果没有添加@Sharable注解,又被添加过,那么将会抛出异常
             * 如果加了@Sharable注解,那么这个handler可以被多次添加
             */
            if (!h.isSharable() && h.added) {
                throw new ChannelPipelineException(
                        h.getClass().getName() +
                                " is not a @Sharable handler, so can't be added or removed mul
   @Override
    public ChannelHandlerContext fireChannelActive() {
        //先找到当前ChannelHandlerContext的下一个ChannelHandlerContext
        final DefaultChannelHandlerContext next = findContextInbound();
        //得到执行器
        EventExecutor executor = next.executor();
        /**
         * 1.首先executor.inEventLoop()方法判断当前线程是不是netty创建的
         */
        //调用上面得到的next的真正的invokeChannelActive
        if (executor.inEventLoop()) {
            next.invokeChannelActive();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelActive();
                }
            });
        }
        return this;
    }

基本上,到这里,整个链表的构造理清了~下面看看双向链表中的事件的传递。

当ServerSocket刚刚监听端口bind的时候,这里会触发一次fireChannelActive事件;那我们从这开始跟踪~

    @Override
    public ChannelPipeline fireChannelActive() {
        //从pipline的head开始触发ChannelActive事件
        head.fireChannelActive();
        //判断isAutoRead的值,如果为true(默认值为true),则自动调用read
        if (channel.config().isAutoRead()) {
            channel.read();
        }
        return this;
    }
我们可以看到,pipline的fireChannelActive是调用head的fireChannel,从head开始,并顺着链表往后调用,继续看下面代码
   @Override
    public ChannelHandlerContext fireChannelActive() {
        //先找到当前ChannelHandlerContext的下一个ChannelHandlerContext
        final DefaultChannelHandlerContext next = findContextInbound();
        //得到执行器
        EventExecutor executor = next.executor();
        /**
         * 1.首先executor.inEventLoop()方法判断当前线程是不是netty创建的
         */
        //调用上面得到的next的真正的invokeChannelActive
        if (executor.inEventLoop()) {
            next.invokeChannelActive();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelActive();
                }
            });
        }
        return this;
    }

这里很有意思,从这里也可以看出netty线程模型的优点~慢慢来,先得到链表下一个handlercontext

    private DefaultChannelHandlerContext findContextInbound() {
        /**
         * inbound时候,从head往tail遍历handle,并找出这些handle里面所有inbound类型的handle,
         * 一直遍历到TailHandle,TailHandle是netty默认实现的一个inbound类型的handle,这个TailHandle默认实现的
         * ChannelInboundHandler接口都是空方法,所以当调用到tail对应的方法的时候,调用链就会终止
         */
        DefaultChannelHandlerContext ctx = this;
        do {
            ctx = ctx.next;
        } while (!ctx.inbound);
        return ctx;
    }

先得到链表上下一个inbound属性为true的ctx并返回,通过这个其实就已经把双向链表给用户抽象成两条链表的感觉,如最上面的那张图。

再继续回到上一个函数,得到执行器,然后判断当前线程是否是该执行器绑定Eventloop的指定线程,如果是的话则直接调用事件next.invokeChannelActive();否则将该事件分装成任务加入该执行器的任务队列中。(这里涉及到netty的线程模型和任务调度,在后面将eventLoop时会仔细分析)

    private void invokeChannelActive() {
        try {
            ((ChannelInboundHandler) handler).channelActive(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    }

上面的this为下一个ctx,如果inbound为ture,那么它一定是ChannelInboundHandler的子类,netty这里采用了适配器模型,我们直接看到对应的adapter的方法

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }

实现很简单,调用下一个inbound ctx的firchannelActive,保证了事件在链表中传递。

总结

看完netty实战后,书总体上还是不错,个人觉得仅仅算本源码指导书,光看书收获不大,看完书后再看源码真的舒服得多,pipline模型算是用的恰到好处,了解tomcat内部的pipline后再看有那么点亲切感。


猜你喜欢

转载自blog.csdn.net/panxj856856/article/details/80314448
今日推荐