死磕Netty源码之ChannelPipeline源码解析(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/RobertoHuang/article/details/81008055

前言

ChannelPipeline数据管道是ChannelHandler数据处理器的容器,负责ChannelHandler的管理和事件的拦截与调度

关于我:http://huangth.com

GitHub地址:https://github.com/RobertoHuang

免责声明:本系列博客并非原创,主要借鉴和抄袭闪电侠占小狼等知名博主博客。如有侵权请及时联系

源码分析

初始化

在Channel初始化时会调用到AbstractChannel的构造方法,Pipeline的初始化动作是在AbstractChannel构造方法中完成的

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

接下来我们继续跟进newChannelPipeline(),探究一下DefaultChannelPipeline底层实现细节

protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");

    tail = new TailContext(this);
    head = new HeadContext(this);

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

在newChannelPipeline()中调用了DefaultChannelPipeline的构造方法,DefaultChannelPipeline构造方法主要完成了三件事。首先将与之关联的Channel保存在属性Channel中,然后实例化了两个对象(一个是TailContext实例tail,一个是HeadContext实例head),然后将head和tail相互指向构成了一个双向链表。数据结构如下图
ChannelPipeline数据结构
DefaultChannelPipeline中的每个节点是一个ChannelHandlerContext对象(一个ChannelPipeline中可以有多个ChannelHandler实例,而每一个ChannelHandler实例与ChannelPipeline之间的桥梁就是ChannelHandlerContext实例)

添加节点

我们在初始化bootstrap的时候应该对下面这段代码不陌生,它是将ChannelHandler添加到pipeline的双向链表中去的

bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast(new NettyMessageDecoder(serializer));
        socketChannel.pipeline().addLast(new NettyMessageEncoder(serializer));
    }
})

下面我们来跟下addLast方法

public final ChannelPipeline addLast(ChannelHandler... handlers) {
    return addLast(null, handlers);
}

public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    if (handlers == null) {
        throw new NullPointerException("handlers");
    }
    for (ChannelHandler h: handlers) {
        if (h == null) {
            break;
        }
        addLast(executor, null, h);
    }
    return this;
}

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        // 1.禁止非Sharable的handler重复添加到不同的pipeline中
        checkMultiplicity(handler);

        // 2.创建节点 => DefaultChannelHandlerContext
        newCtx = newContext(group, filterName(name, handler), handler);

        // 3.添加节点
        addLast0(newCtx);

        // 如果channel没有与eventloop绑定
        // 则创建一个任务 这个任务会在channel被register的时候调用
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        // 4.回调用户方法
        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;
}

这里简单地用synchronized方法是为了防止多线程并发操作pipeline底层的双向链表

检查是否有重复Handler

检查是否有重复Handler的实现在checkMultiplicity()方法中

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.");
        }
        h.added = true;
    }
}

ChannelHandlerAdapter使用一个成员变量added标识一个Channel是否已经添加过,如果当前要添加的Handler是非共享并且已经添加过那就抛出异常,否则标识该Handler已经添加。由此可见一个Handler如果是Sharable的就可以无限次被添加到Pipeline中,我们客户端代码如果要让一个Handler被共用,只需要加一个@Sharable标注即。而如果Handler是Sharable的一般就通过Spring的注入的方式使用,不需要每次都新建对象

创建节点DefaultChannelHandlerContext

创建节点调用了newContext()方法,代码如下

newCtx = newContext(group, filterName(name, handler), handler);

这里我们需要先分析filterName(name, handler)这段代码,这个方法用于给handler创建一个唯一性的名字

private String filterName(String name, ChannelHandler handler) {
    if (name == null) {
        // 1.如果传入的name为空 则生成
        // Netty生成的name默认为=> 简单类名#0
        // 如果简单类名#0已存在则将基数+1 生成name为简单类名#1 以此递增
        return generateName(handler);
    }
    // 2.检查是否有重名 检查通过则返回
    checkDuplicateName(name);
    return name;
}

检查是否存在重名的Context,如果已存在重名的Context对象则抛出异常

private void checkDuplicateName(String name) {
    // 判断是否已有重名的Context
    if (context0(name) != null) {
        // 如果已存在重名的Context则抛出异常
        throw new IllegalArgumentException("Duplicate handler name: " + name);
    }
}

private AbstractChannelHandlerContext context0(String name) {
    // 从头结点开始遍历所有的Context
    // 只要发现某个Context的名字与待添加的name相同 就返回该Context
    AbstractChannelHandlerContext context = head.next;
    while (context != tail) {
        if (context.name().equals(name)) {
            return context;
        }
        context = context.next;
    }
    return null;
}

在处理完name之后就进入到创建context的过程,代码如下

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}

private EventExecutor childExecutor(EventExecutorGroup group) {
    if (group == null) {
        return null;
    }
    ...
}

由前面的调用链得知group为null,因此此处的childExecutor(group)也返回null

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

在DefaultChannelHandlerContext构造方法中标记了Handler是否是Inboud类型或OutBound类型并调用了父类的构造方法,最后保存了Handler的引用,判断Handler是Inbound还是OutBound的代码如下

private static boolean isInbound(ChannelHandler handler) {
    return handler instanceof ChannelInboundHandler;MessageToMessageCodec
}

private static boolean isOutbound(ChannelHandler handler) {
    return handler instanceof ChannelOutboundHandler;
}

调用父类构造函数,初始化成员变量

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, boolean inbound, boolean outbound) {
    this.name = ObjectUtil.checkNotNull(name, "name");
    this.pipeline = pipeline;
    // 此处exeutor为null
    this.executor = executor;
    this.inbound = inbound;
    this.outbound = outbound;
    // 初次ordered为true
    ordered = executor == null || executor instanceof OrderedEventExecutor;
}

如果一个Handler实现了两类接口,那么它既是一个inBound类型的Handler,又是一个outBound类型的Handler,如下图
ChannelDuplexHandler类图
常用的将decode操作和encode操作合并到一起的codec,一般会继承MessageToMessageCodec,而MessageToMessageCodec就是继承ChannelDuplexHandler

添加Context节点到Pipeline的双向链表中

在创建完Context之后调用addLast0()将Context添加至Pipeline的双向链表中

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

添加节点其实就是个双向链表的插入操作,具体步骤如下图所示
Pipeline添加节点过程

操作完毕,该Context就加入到Pipeline中
Pipeline添加节点完成

至此Pipeline添加节点的操作就完成了,其他的addXXX方法原理类似

Context节点添加完毕回调Handler方法

回调用户方法逻辑在callHandlerAdded0()方法中完成,代码如下

private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try {
        ctx.handler().handlerAdded(ctx);
        ctx.setAddComplete();
    } catch (Throwable t) {
        ...
    }
}

Handler可以重写handlerAdded方法在Handler被添加成功后 执行部分操作

public class DemoHandler extends SimpleChannelInboundHandler<...> {
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 节点被添加完毕之后回调到此
    }
}

接下来,设置该节点的状态

final void setAddComplete() {
    for (;;) {
        int oldState = handlerState;
        if (oldState == REMOVE_COMPLETE || HANDLER_STATE_UPDATER.compareAndSet(this, oldState, ADD_COMPLETE)) {
            return;
        }
    }
}

用cas修改节点的状态至REMOVE_COMPLETE(说明该节点已经被移除)或者ADD_COMPLETE(说明节点已被添加)

删除节点

Netty有个最大的特性之一就是Handler可插拔做到动态编织Pipeline,比如在首次建立连接的时候需要通过进行权限认证,在认证通过之后就可以将此Context移除,下次Pipeline在传播事件的时候就就不会调用到权限认证处理器

下面是权限认证Handler最简单的实现,第一个数据包传来的是认证信息,如果校验通过就删除此Handler,否则直接关闭连接

public class AuthHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf data) throws Exception {
        if (verify(authDataPacket)) {
            ctx.pipeline().remove(this);
        } else {
            ctx.close();
        }
    }

    private boolean verify(ByteBuf byteBuf) {
        // ...
    }
}

重点就在ctx.pipeline().remove(this)这段代码

public final ChannelPipeline remove(ChannelHandler handler) {
    remove(getContextOrDie(handler));
    return this;
}

remove操作可以分为如下三个步骤

1.找到待删除的节点
2.调整双向链表指针删除节点
3.Context节点删除完毕回调Handler方法

找到待删除的节点

private AbstractChannelHandlerContext getContextOrDie(String name) {
    AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
    if (ctx == null) {
        throw new NoSuchElementException(name);
    } else {
        return ctx;
    }
}

public final ChannelHandlerContext context(ChannelHandler handler) {
    if (handler == null) {
        throw new NullPointerException("handler");
    }

    AbstractChannelHandlerContext ctx = head.next;
    for (;;) {

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

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

        ctx = ctx.next;
    }
}

通过遍历链表方式根据Handler找到对应的Context节点(判断依据 => Context的Handler和当前Handler相同)

调整双向链表指针删除节点

private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
    // 头结点和为节点不能删除
    assert ctx != head && ctx != tail;

    synchronized (this) {
        // 调整双向链表指针删除节点
        remove0(ctx);

        // 如果channel没有与eventloop绑定
        // 则创建一个任务 这个任务会在channel被register的时候调用
        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;
        }
    }
    callHandlerRemoved0(ctx);
    return ctx;
}

删除节点是通过remove0方法实现的,代码如下

private static void remove0(AbstractChannelHandlerContext ctx) {
    AbstractChannelHandlerContext prev = ctx.prev;
    AbstractChannelHandlerContext next = ctx.next;
    prev.next = next;
    next.prev = prev;
}

删除节点其实就是个双向链表的删除操作,具体步骤如下图所示
Pipeline删除节点过程

操作完毕,该Context就从Pipeline中移除
Pipeline删除节点完成

Context节点删除完毕回调Handler方法

回调用户方法逻辑在callHandlerRemoved0()方法中完成,代码如下

private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
    try {
        try {
            ctx.handler().handlerRemoved(ctx);
        } finally {
            ctx.setRemoved();
        }
    } catch (Throwable t) {
        fireExceptionCaught(new ChannelPipelineException(ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
    }
}

Handler可以重写handlerRemoved方法在Handler被删除成功后 执行部分操作

public class DemoHandler extends SimpleChannelInboundHandler<...> {
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 节点被删除完毕之后回调到此
    }
}

最后将该节点的状态设置为removed

final void setRemoved() {
    handlerState = REMOVE_COMPLETE;
}

至此Pipeline添加节点的删除就完成了,其他的removeXXX方法原理类似

猜你喜欢

转载自blog.csdn.net/RobertoHuang/article/details/81008055
今日推荐