欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除

程序员文章站 2023-11-02 15:58:10
上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析; 一、ChannelHandler的添加 下面是Netty官方的一段demo源码,可以看到在服务端初始化时执行了向Channe ......

上篇文章中,我们对netty中channelpipeline的构造与初始化进行了分析与总结,本篇文章我们将对channelhandler的添加与删除操作进行具体的的代码分析;

一、channelhandler的添加

下面是netty官方的一段demo源码,可以看到在服务端初始化时执行了向channelpipeline中添加自定义channelhandler的操作。

            serverbootstrap b = new serverbootstrap();
            b.group(bossgroup, workergroup).channel(nioserversocketchannel.class).option(channeloption.so_backlog, 100)
                    .handler(new logginghandler(loglevel.info)).childhandler(new channelinitializer<socketchannel>() {
                        @override
                        public void initchannel(socketchannel ch) throws exception {
                            channelpipeline p = ch.pipeline();
                            if (sslctx != null) {
                                p.addlast(sslctx.newhandler(ch.alloc()));
                            }
                            // p.addlast(new logginghandler(loglevel.info));
                            // 向channelpipeline中添加自定义channelhandler
                            p.addlast(serverhandler);
                        }
                    });

我们可以看到上面的代码中调用channelpipeline的addlast方法实现了channelhandler的添加,下面我们就分析下addlast方法的具体源码实现

首先看下addlast方方法的具体源码实现

    public final channelpipeline addlast(eventexecutorgroup group, string name, channelhandler handler) {
        final abstractchannelhandlercontext newctx;
        synchronized (this) {
            //判断handler是否被重复添加
            checkmultiplicity(handler);

            //创建channelhandlercontext节点  filtername检查名称是否重复
            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()) {//判断是否在同一线程中
                callhandleraddedineventloop(newctx, executor);
                return this;
            }
        }
        callhandleradded0(newctx);
        return this;
    }

分析addlast方法代码可以看到,channelhandler的添加基本可以分为四步

1、验证channelhandler是否重复添加

我们看下checkmultiplicity方法的具体实现

    private static void checkmultiplicity(channelhandler handler) {
        if (handler instanceof channelhandleradapter) {
            channelhandleradapter h = (channelhandleradapter) handler;
            if (!h.issharable() && h.added) {//如果该handler非共享且已经被添加
                throw new channelpipelineexception(
                        h.getclass().getname() +
                        " is not a @sharable handler, so can't be added or removed multiple times.");
            }
            h.added = true;//添加过之后,修改标识
        }
    }

2、创建一个handlercontext对象

我们之前说过netty会把一个channelhandler封装成一个channelhandlercontext对象,如下面代码所示
newctx = newcontext(group, filtername(name, handler), handler);
封装对象时,我们可以给要添加的channelhandler起一个名字,filtername方法可以判断该handler的命名是否重复
    private string filtername(string name, channelhandler handler) {
        if (name == null) {
            return generatename(handler);//返回一个默认名称
        }
        checkduplicatename(name);
        return name;
    }
checkduplicatename方法会遍历链表中节点如果查询到有重复的name则会抛出异常
    private void checkduplicatename(string name) {
        if (context0(name) != null) { //遍历节点,查找是否有重复name
            throw new illegalargumentexception("duplicate handler name: " + name);
        }
    }
    private abstractchannelhandlercontext context0(string name) {
        abstractchannelhandlercontext context = head.next;
        while (context != tail) {
            if (context.name().equals(name)) {
                return context;
            }
            context = context.next;
        }
        return null;
    }

3、向链表中添加添加context

前面进行了一系列判断后,通过addlast0方法我们把channelhandlercontext添加到pipeline中的双向链表中

    //相当于在tail节点前面插入一个节点,也就是addlast
    private void addlast0(abstractchannelhandlercontext newctx) {
        abstractchannelhandlercontext prev = tail.prev;//拿到tail节点的前置节点
        newctx.prev = prev;//把当前context的前置节点置为 prev
        newctx.next = tail;//把当前context的后置节点置为tail
        prev.next = newctx;//把prev节点的后置节点置为context
        tail.prev = newctx;//把tail节点的前置节点置为context
    }

addlast0内部实现很简单,就是在tail节点前面插入一个节点,也就是把该channelhandlercontext放在链表的最后。

4、调用回调方法,通知添加成功

这一步就是当channelhandler添加到pipeline中时调用,通过callhandleradded()回调方法通知channelhandler添加成功,执行handleradded()方法;

首先判断当前线程与eventloop线程是否一致,不一致的话封装成task提交给eventloop线程,是同一线程直接执行callhandleradded0方法,我们看下方法具体实现

    private void callhandleradded0(final abstractchannelhandlercontext ctx) {
        try {
            ctx.callhandleradded();//调用callhandleradded回调方法
        } catch (throwable t) {
            boolean removed = false;
            try {
                remove0(ctx);//如果出现异常的话,把该ctx删除
                ctx.callhandlerremoved();//调用callhandlerremoved回调方法
                removed = true;
            } catch (throwable t2) {
                if (logger.iswarnenabled()) {
                    logger.warn("failed to remove a handler: " + ctx.name(), t2);
                }
            }

            if (removed) {
                fireexceptioncaught(new channelpipelineexception(
                        ctx.handler().getclass().getname() +
                        ".handleradded() has thrown an exception; removed.", t));
            } else {
                fireexceptioncaught(new channelpipelineexception(
                        ctx.handler().getclass().getname() +
                        ".handleradded() has thrown an exception; also failed to remove.", t));
            }
        }
    }
    final void callhandleradded() throws exception {
        // we must call setaddcomplete before calling handleradded. otherwise if the handleradded method generates
        // any pipeline events ctx.handler() will miss them because the state will not allow it.
        if (setaddcomplete()) {//在添加handler之前,保证状态为可添加状态
            handler().handleradded(this);
        }
    }

通过上面代码实现,我们就可以通过重写channelhandler的handleradded方法,执行一些当channelhandler添加到pipeline中时需要触发的功能逻辑。

二、channelhandler的删除

channelhandler的删除主要是通过channelpipeline的remove方法来实现的

我们先看下remove方法源码具体实现

    @override
    public final channelpipeline remove(channelhandler handler) {
        remove(getcontextordie(handler));//删除handler
        return this;
    }
private abstractchannelhandlercontext remove(final abstractchannelhandlercontext ctx) {
        //不能删除头节点和尾节点
        assert ctx != head && ctx != tail;

        //加锁,保证线程安全
        synchronized (this) {
            remove0(ctx);//在链表中删除context对象

            // if the registered is false it means that the channel was not registered on an eventloop yet.
            // in this case we remove the context from the pipeline and add a task that will call
            // channelhandler.handlerremoved(...) once the channel is registered.
            //这里主要判断下该pipline对应channel是否已经注册到eventloop上
            if (!registered) {
                callhandlercallbacklater(ctx, false);
                return ctx;
            }

            
            eventexecutor executor = ctx.executor();
            if (!executor.ineventloop()) {//判断是否在同一线程中
                executor.execute(new runnable() {
                    @override
                    public void run() {
                        callhandlerremoved0(ctx);
                    }
                });
                return ctx;
            }
        }
        //调用回调方法,通知handler已删除
        callhandlerremoved0(ctx);
        return ctx;
    }

删除操作整个流程与添加类似

1、获取channelhandlercontext对象

    //根据传入的handler拿到其包装的channelhandlercontext对象
    private abstractchannelhandlercontext getcontextordie(channelhandler handler) {
        //根据context方法从链表中获取该handler的channelhandlercontext对象
        abstractchannelhandlercontext ctx = (abstractchannelhandlercontext) context(handler);
        if (ctx == null) {
            throw new nosuchelementexception(handler.getclass().getname());
        } else {
            return ctx;
        }
    }
    @override
    public final channelhandlercontext context(channelhandler handler) {
        if (handler == null) {
            throw new nullpointerexception("handler");
        }

        abstractchannelhandlercontext ctx = head.next;
        //遍历链表拿到该handler封装的channelhandlercontext对象
        for (;;) {

            if (ctx == null) {
                return null;
            }

            if (ctx.handler() == handler) {
                return ctx;
            }

            ctx = ctx.next;
        }
    }

2、判断是否是首尾节点

首先判断删除的节点既不是头节点也不是尾节点

   //不能删除头节点和尾节点
   assert ctx != head && ctx != tail;

3、执行删除操作

然后通过remove0方法删除指定context节点

    private static void remove0(abstractchannelhandlercontext ctx) {
        abstractchannelhandlercontext prev = ctx.prev;//获取当前节点的前置节点
        abstractchannelhandlercontext next = ctx.next;//获取当前节点的后置节点
        prev.next = next;//把prev后置节点设置为next
        next.prev = prev;//把next前置节点设置为prev
    }

 4、调用回调方法,通知删除成功

同样会判断当前线程与eventloop线程是否一致,不一致的话封装成task提交给eventloop线程

 eventexecutor executor = ctx.executor();
            if (!executor.ineventloop()) {//判断是否在同一线程中
                executor.execute(new runnable() {
                    @override
                    public void run() {
                        callhandlerremoved0(ctx);
                    }
                });
                return ctx;
            }
 //调用回调方法,通知handler已删除
 callhandlerremoved0(ctx);

调用callhandlerremoved()回调方法,通知触发handlerremoved删除成功。

    private void callhandlerremoved0(final abstractchannelhandlercontext ctx) {
        // notify the complete removal.
        try {
            ctx.callhandlerremoved();//调用ctx中的回调方法
        } catch (throwable t) {
            fireexceptioncaught(new channelpipelineexception(
                    ctx.handler().getclass().getname() + ".handlerremoved() has thrown an exception.", t));
        }
    }
    final void callhandlerremoved() throws exception {
        try {
            // only call handlerremoved(...) if we called handleradded(...) before.
            if (handlerstate == add_complete) {
                handler().handlerremoved(this);//通知handler删除成功事件
            }
        } finally {
            // mark the handler as removed in any case.
            setremoved();
        }
    }

三、总结

通过上面的内容,我们梳理了channelhandler被封装成channelhandlercontext对象后,基于channelpipeline的双向链表的添加和删除操作,整个流程还是比较简单清晰的。其中如有不足与不正确的地方还望指出与海涵

 

关注微信公众号,查看更多技术文章。

 

Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除