前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Netty】ChannelHandler的添加和删除(二)

【Netty】ChannelHandler的添加和删除(二)

作者头像
用户3467126
修改2019-07-03 19:29:39
1.1K0
修改2019-07-03 19:29:39
举报
文章被收录于专栏:爱编码爱编码

主要讲述了ChannelPipeline和ChannelHandler的基本知识以及ChannelPipeline的创建,本文将学习ChannelHandler的添加和删除

ChannelHandler的添加

添加handler, 我们以用户代码为例进行剖析:

代码语言:javascript
复制
.childHandler(new ChannelInitializer<SocketChannel>() {
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
        ch.pipeline().addLast(new StringEncoder());
        ch.pipeline().addLast(new SimpleHandler());
    }
 });

用过netty的小伙伴们肯定对这段代码不会陌生, 通过addLast, 可以添加编解码器和我们自定义的handler, 某一个事件完成之后可以自动调用我们handler预先定义的方法, 具体添加和调用是怎么个执行逻辑, 在我们之后的内容会全部学习到, 以后再使用这类的功能会得心应手

在这里, 我们主要剖析 ch.pipeline().addLast(newSimpleHandler()) 这部分代码的 addLast()方法。跟踪到 DefaultChannelPipeline#ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler)

代码语言:javascript
复制
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;    synchronized (this) {
        //判断handler是否被重复添加(1)
        checkMultiplicity(handler);
        //创建一个HandlerContext并添加到列表(2)
        newCtx = newContext(group, filterName(name, handler), handler);
        //添加HandlerContext(3)
        addLast0(newCtx);
        //是否已注册
        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;
        }
    }
    //回调添加事件(4)
    callHandlerAdded0(newCtx);
    return this;
 }

这个方法代码比较长, 我们拆解为4个步骤:

1.重复添加验证 2.创建一个HandlerContext并添加到列表 3.添加context 4.回调添加事件

1.重复添加验证

我们跟到 checkMultiplicity(handler)中:

代码语言:javascript
复制
private static void checkMultiplicity(ChannelHandler handler) {
    if (handler instanceof ChannelHandlerAdapter) { 
       ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
          if (!h.isSharable() && h.added) {
            throw new ChannelPipelineException(
                    h.getClass().getName() + 
                     " is not a @Sharable handler, so can't be added or removed multiple times.");
         }
         //满足条件设置为true, 代表已添加
         h.added = true;
      }
}

首先判断是不是 ChannelHandlerAdapter类型, 因为我们自定义的handler通常会直接或者间接的继承该接口, 所以这里为true 拿到handler之后转换成 ChannelHandlerAdapter类型, 然后进行条件判断 if(!h.isSharable()&&h.added)代表如果不是共享的handler, 并且是未添加状态, 则抛出异常:

代码语言:javascript
复制
public boolean isSharable() {
     Class<?> clazz = getClass();
    Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
    Boolean sharable = cache.get(clazz);
    if (sharable == null) { 
            //如果这个类注解了Sharable.class, 说明这个类会被多个channel共享 
           sharable = clazz.isAnnotationPresent(Sharable.class);
           cache.put(clazz, sharable);
      }
    return sharable;
}

首先拿到当前 handler的class对象然后再从netty自定义的一个 ThreadLocalMap对象中获取一个盛放 handler的class对象的map, 并获取其value。如果value值为空, 则会判断是否被 Sharable注解, 并将自身 handlerclass对象和判断结果存入map对象中, 最后返回判断结果这说明了被 Sharable注解的 handler是一个共享 handler从这个逻辑我们可以判断, 共享对象是可以重复添加的。

2.创建HandlerContext并添加到列表

如果是共享对象或者没有被添加, 则将ChannelHandlerAdapter的added设置为true, 代表已添加。剖析完了重复添加验证, 回到addLast方法中, 我们看第二步, 创建一个HandlerContext并添加到列表:

代码语言:javascript
复制
newCtx = newContext(group, filterName(name, handler), handler);

首先看 filterName(name,handler)方法, 这个方法是判断添加handler的name是否重复

代码语言:javascript
复制
private String filterName(String name, ChannelHandler handler) {
    if (name == null) {
        //没有名字创建默认名字
        return generateName(handler);
    }
    //检查名字是否重复
    checkDuplicateName(name);
    return name;
}

因为我们添加handler时候, 不一定会会给handler命名, 所以这一步name有可能是null, 如果是null, 则创建一个默认的名字, 这里创建名字的方法我们就不往里跟了, 有兴趣的同学可以自己跟进去看.

然后再检查名字是否重复checkDuplicateName(name)这个方法中:

代码语言:javascript
复制
private void checkDuplicateName(String name) {
    //不为空
    if (context0(name) != null) {
        throw new IllegalArgumentException("Duplicate handler name: " + name);
}
private AbstractChannelHandlerContext context0(String name) {
    //遍历pipeline
    AbstractChannelHandlerContext context = head.next;
    while (context != tail) {
        //发现name相同, 说明存在handler
        if (context.name().equals(name)) {
            //返回
            return context;
        }
        context = context.next;
    }
    return null;
}

这里做的操作非常简单, 就是将 pipeline中, 从 head节点往下遍历 HandlerContext, 一直遍历到tail, 如果发现名字相同则会认为重复并返回 HandlerContext对象,我们回到 addLast()方法中并继续看添加创建相关的逻辑: newCtx=newContext(group,filterName(name,handler),handler) filterName(name,handler)这步如果并没有重复则会返回 handlername

跟到newContext(group, filterName(name, handler), handler)方法中

代码语言:javascript
复制
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);}

这里我们看到创建了一个 DefaultChannelHandlerContext对象, 构造方法的参数中, 第一个this代表当前的pipeline对象, group为null, 所以 childExecutor(group)也会返回null, name为handler的名字, handler为新添加的handler对象。

继续跟到DefaultChannelHandlerContext的构造方法

代码语言:javascript
复制
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赋值为自身handler的成员变量, HandlerConexthandler关系在此也展现了出来, 是一种组合关系。我们首先看父类的构造方法, 有这么两个参数: isInbound(handler), isOutbound(handler), 这两个参数意思是判断需要添加的handler是 inboundHandler还是 outBoundHandler

同样我们看 isOutbound(handler)方法

代码语言:javascript
复制
private static boolean isOutbound(ChannelHandler handler) {
    return handler instanceof ChannelOutboundHandler;
}

通过判断是否实现 ChannelOutboundHandler接口判断是否为 outboundhandler

跟到其父类AbstractChannelHandlerContext的构造方法中:

代码语言:javascript
复制
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;
}

一切都不陌生了, 因为我们tail节点和head节点创建的时候同样走到了这里 这里初始化了name, pipeline, 以及标识添加的handler是inboundhanlder还是outboundhandler

3.添加HandlerContext

回到最初的 addLast()方法中:跟完了创建 HandlerContext的相关逻辑, 我们继续跟第三步, 添加 HandlerContext

代码语言:javascript
复制
private void addLast0(AbstractChannelHandlerContext newCtx) {
    //拿到tail节点的前置节点
    AbstractChannelHandlerContext prev = tail.prev;
    //当前节点的前置节点赋值为tail节点的前置节点
    newCtx.prev = prev;
    //当前节点的下一个节点赋值为tail节点
    newCtx.next = tail;
    //tail前置节点的下一个节点赋值为当前节点
    prev.next = newCtx;
    //tail节点的前一个节点赋值为当前节点
    tail.prev = newCtx;
}

这一部分也非常简单, 做了一个指针的指向操作, 将新添加的handlerConext放在tail节点之前, 之前tail节点的上一个节点之后, 熟悉双向链表的同学对此逻辑应该不会陌生, 如果是第一次添加handler, 那么添加后的结构入下图所示:

添加完handler之后, 这里会判断当前channel是否已经注册, 这部分逻辑我们之后再进行剖析, 我们继续往下走

4.回调添加事件

之后会判断当前线程线程是否为eventLoop线程, 如果不是eventLoop线程, 就将添加回调事件封装成task交给eventLoop线程执行, 否则, 直接执行添加回调事件callHandlerAdded0(newCtx)

代码语言:javascript
复制
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try {
        ctx.handler().handlerAdded(ctx);
        ctx.setAddComplete();
    } catch (Throwable t) {
        //忽略代码
    }
}

我们重点关注这句ctx.handler().handlerAdded(ctx);其中ctx是我们新创建的HandlerContext, 通过handler()方法拿到绑定的handler, 也就是新添加的handler, 然后执行handlerAdded(ctx)方法, 如果我们没有重写这个方法, 则会执行父类的该方法.

在ChannelHandlerAdapter类中定义了该方法的实现:

代码语言:javascript
复制
@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {}

我们看到没做任何操作, 也就是如果我们没有重写该方法时, 如果添加handler之后将不会做任何操作, 这里如果我们需要做一些业务逻辑, 可以通过重写该方法进行实现。 以上就是添加handler的有关的业务逻辑

handler的删除

如果用户在业务逻辑中进行 ctx.pipeline().remove(this)这样的写法, 或者 ch.pipeline().remove(newSimpleHandler())这样的写法, 则就是对 handler进行删除, 我们学习过添加 handler的逻辑, 所以对 handler删除操作理解起来也会比较容易

代码语言:javascript
复制
public final ChannelPipeline remove(ChannelHandler handler) {
    remove(getContextOrDie(handler));
    return this;
}

方法体里有个 remove()方法, 传入一个 getContextOrDie(handler)参数,这个 getContextOrDie(handler) , 其实就是根据handler拿到其包装类 HandlerContext对象。

代码语言:javascript
复制
private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
    AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
    //代码省略
}

这里仍然会通过 context(handler)方法去寻找, 再跟进去

代码语言:javascript
复制
public final ChannelHandlerContext context(ChannelHandler handler) {
    if (handler == null) {
        throw new NullPointerException("handler");
    }
    //从头遍历节点
    AbstractChannelHandlerContext ctx = head.next;
    for (;;) {
        if (ctx == null) {
            return null;
        }
        //找到handler 
       if (ctx.handler() == handler) {
               return ctx;
           }
           ctx = ctx.next;
       }
}

这里我们看到寻找的方法也非常的简单, 就是从头结点开始遍历, 遍历到如果其包装的handler对象是传入的handler对象, 则返回找到的handlerContext

回到remove(handler)方法:

代码语言:javascript
复制
public final ChannelPipeline remove(ChannelHandler handler) {
    remove(getContextOrDie(handler));
        return this;
}
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
    //当前删除的节点不能是head, 也不能是tail
    assert ctx != head && ctx != tail;
    synchronized (this) {
        //执行删除操作
        remove0(ctx);
        if (!registered) {
            callHandlerCallbackLater(ctx, false);
            return ctx;
        }
        //回调删除handler事件
       EventExecutor executor = ctx.executor();
           if (!executor.inEventLoop()) {
               executor.execute(new Runnable() {
                   @Override
                   public void run() {
                       callHandlerRemoved0(ctx);
                   }
               });
               return ctx;
           }
       }
       callHandlerRemoved0(ctx);
       return ctx;
}

首先要断言删除的节点不能是tail和head 然后通过remove0(ctx)进行实际的删除操作, 跟到remove0(ctx)中:

代码语言:javascript
复制
private static void remove0(AbstractChannelHandlerContext ctx) {
    //当前节点的前置节点
    AbstractChannelHandlerContext prev = ctx.prev;
    //当前节点的后置节点
    AbstractChannelHandlerContext next = ctx.next;
    //前置节点的下一个节点设置为后置节点
    prev.next = next;
    //后置节点的上一个节点设置为前置节点
    next.prev = prev;
    }

这里的操作也非常简单, 做了一个指针移动的操作, 熟悉双向链表的小伙伴应该不会陌生, 删除节点逻辑大概如下图所示:

回到 remove(ctx)方法:

代码语言:javascript
复制
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
    //当前删除的节点不能是head, 也不能是tail
    assert ctx != head && ctx != tail;
    synchronized (this) {
        //执行删除操作
        remove0(ctx);
        if (!registered) {
            callHandlerCallbackLater(ctx, false);
            return ctx;
        }
        //回调删除handler事件
        EventExecutor executor = ctx.executor();
        if (!executor.inEventLoop()) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    callHandlerRemoved0(ctx);
                }
            });
            return ctx;
        }
    }
    callHandlerRemoved0(ctx);
    return ctx;
    }

我们继续往下看, 如果当前线程不是eventLoop线程则将回调删除事件封装成task放在taskQueue中让eventLoop线程进行执行, 否则, 则直接执行回调删除事件。

代码语言:javascript
复制
private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
    try {
        try {
            //调用handler的handlerRemoved方法
            ctx.handler().handlerRemoved(ctx);
        } finally {
            //将当前节点状态设置为已移除
            ctx.setRemoved();
        }
    } catch (Throwable t) {
        fireExceptionCaught(new ChannelPipelineException(
                ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
    }
}

与添加handler的逻辑一样, 这里会调用当前handler的handlerRemoved方法, 如果用户没有重写该方法, 则会调用其父类的方法, 方法体在ChannelHandlerAdapter类中有定义, 我们跟进去

代码语言:javascript
复制
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
}

同添加handler一样, 也是一个空实现, 这里用户可以通过重写来添加自己需要的逻辑。 以上就是删除handler的相关操作。

总结

本文主要学习了ChannelHandler的添加和删除。 接下来会学习pipeline的传播机制。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ChannelHandler的添加
    • 1.重复添加验证
    • 2.创建HandlerContext并添加到列表
      • 3.添加HandlerContext
      • handler的删除
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档