Netty源码分析——ChannelPipeline

基于Netty源代码版本:netty-all-4.1.33.Final

前言

netty在服务端端口绑定和新连接建立的过程中会建立相应的channel,而与channel的动作密切相关的是pipeline这个概念,pipeline像是可以看作是一条流水线,原始的原料(字节流)进来,经过加工,最后输出。

ChannelPipeline不是单独存在,它肯定会和Channel、ChannelHandler、ChannelHandlerContext关联在一起,所以有关概念这里一起讲。

一、ChannelHandler

1、概念,先看图


ChannelHandler下主要是两个子接口:

  • ChannelInboundHandler(入站):处理输入数据和Channel状态类型改变。
    • 适配器: ChannelInboundHandlerAdapter(适配器设计模式)
    • 常用的: SimpleChannelInboundHandler
  • ChannelOutboundHandler(出站):处理输出数据
    • 适配器: ChannelOutboundHandlerAdapter

每一个Handler都一定会处理出站或者入站(可能两者都处理数据),例如对于入站的Handler可能会继承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter,而SimpleChannelInboundHandler又是继承于ChannelInboundHandlerAdapter,最大的区别在于SimpleChannelInboundHandler会对没有外界引用的资源进行一定的清理,并且入站的消息可以通过泛型来规定。

这里为什么有设配器模式呢?
我们在写自定义Handel时候,很少会直接实现上面两个接口,因为接口中有很多默认方法需要实现,所以这里就采用了设配器模式ChannelInboundHandlerAdapter和ChannelInboundHandlerAdapter就是设配器模式的产物,让它去实现上面接口,实现它所有方法。那么你自己写自定义Handel时,只要继承它,就无须重写上面接口的所有方法了。

2、Channel 生命周期(执行顺序也是从上倒下)

  • (1)channelRegistered: channel注册到一个EventLoop。
  • (2)channelActive: 变为活跃状态(连接到了远程主机),可以接受和发送数据
  • (3)channelInactive: channel处于非活跃状态,没有连接到远程主机
  • (4)channelUnregistered: channel已经创建,但是未注册到一个EventLoop里面,也就是没有和Selector绑定

3、ChannelHandler 生命周期

handlerAdded:当 ChannelHandler 添加到 ChannelPipeline 调用
handlerRemoved:当 ChannelHandler 从 ChannelPipeline 移除时调用
exceptionCaught:当 ChannelPipeline 执行抛出异常时调用

二、ChannelPipeline

1、概念,先看图


ChannelPipeline类是ChannelHandler实例对象的链表,用于处理或截获通道的接收和发送数据。它提供了一种高级的截取过滤模式(类似serverlet中的filter功能),让用户可以在ChannelPipeline中完全控制一个事件以及如何处理ChannelHandler与ChannelPipeline的交互。
对于每个新的通道Channel,都会创建一个新的ChannelPipeline,并将器pipeline附加到channel中。
下图描述ChannelHandler与pipeline中的关系,一个io操作可以由一个ChannelInboundHandler或ChannelOutboundHandle进行处理,并通过调用ChannelInboundHandler处理入站io或通过ChannelOutboundHandler处理出站IO。


2、ChannelPipeline的初始化

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {

    private final DefaultChannelPipeline pipeline;

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

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

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

NioServerSocketChannel继承了AbstractChannel,所以NioServerSocketChannel中保存了pipeline的引用

DefaultChannelPipeline

public class DefaultChannelPipeline implements ChannelPipeline {

    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;

    private final Channel channel;

    protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

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

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

pipeline中保存了channel的引用,创建完pipeline之后,整个pipeline是这个样子的


pipeline中的每个节点是一个ChannelHandlerContext对象,每个context节点保存了它包裹的执行器 ChannelHandler 执行操作所需要的上下文,其实就是pipeline,因为pipeline包含了channel的引用,可以拿到所有的context信息。

3、常用方法

addFirst(...)   //添加ChannelHandler在ChannelPipeline的第一个位置
addBefore(...)  //在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler
addAfter(...)   //在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler
addLast(...)    //在ChannelPipeline的末尾添加ChannelHandler
remove(...)     //删除ChannelPipeline中指定的ChannelHandler
replace(...)    //替换ChannelPipeline中指定的ChannelHandler

ChannelPipeline可以动态添加、删除、替换其中的ChannelHandler,因为ChannelPipeline本身是线程安全的,这样的机制可以提高灵活性。示例:

ChannelPipeline pipeline = ch.pipeline(); 
FirstHandler firstHandler = new FirstHandler(); 
pipeline.addLast("handler1", firstHandler); 
pipeline.addFirst("handler2", new SecondHandler()); 
pipeline.addLast("handler3", new ThirdHandler()); 
pipeline.remove("“handler3“"); 
pipeline.remove(firstHandler); 
pipeline.replace("handler2", "handler4", new FourthHandler());

4、入站出站Handler执行顺序

一般的项目中,inboundHandler和outboundHandler有多个,在Pipeline中的执行顺序?
重点记住: InboundHandler顺序执行,OutboundHandler逆序执行。
问题:下面的handel的执行顺序?

ch.pipeline().addLast(new InboundHandler1());
ch.pipeline().addLast(new OutboundHandler1());
ch.pipeline().addLast(new OutboundHandler2());
ch.pipeline().addLast(new InboundHandler2());
或者:
ch.pipeline().addLast(new OutboundHandler1());
ch.pipeline().addLast(new OutboundHandler2());
ch.pipeline().addLast(new InboundHandler1());
ch.pipeline().addLast(new InboundHandler2());

其实上面的执行顺序都是一样的:

InboundHandler1--> InboundHandler2 -->OutboundHandler2 -->OutboundHandler1

结论:

  • 1)InboundHandler顺序执行,OutboundHandler逆序执行
  • 2)InboundHandler之间传递数据,通过ctx.fireChannelRead(msg)
  • 3)InboundHandler通过ctx.write(msg),则会传递到outboundHandler
    1. 使用ctx.write(msg)传递消息,Inbound需要放在结尾,在Outbound之后,不然outboundhandler会不执行;
      但是使用channel.write(msg)、pipline.write(msg)情况会不一致,都会执行,那是因为channel和pipline会贯穿整个流。
    1. outBound和Inbound谁先执行,针对客户端和服务端而言,客户端是发起请求再接受数据,先outbound再inbound,服务端则相反。

三、ChannelHandlerContext

ChannelPipeline并不是直接管理ChannelHandler,而是通过ChannelHandlerContext来间接管理,这一点通过ChannelPipeline的默认实现DefaultChannelPipeline可以看出来。
DefaultChannelHandlerContext和DefaultChannelPipeline是ChannelHandlerContext和ChannelPipeline的默认实现在DefaultPipeline内部

DefaultChannelHandlerContext组成了一个双向链表。 我们看下DefaultChannelPipeline的构造函数:

protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);

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

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

所以对于DefaultChinnelPipeline它的Handel头部和尾部的Handel是固定的,我们所添加的Handel是添加在这个头和尾之前的Handel。(下面这个图更加清晰)


四、几者关系

先大致说下什么是Channel
通常来说, 所有的 NIO 的 I/O 操作都是从 Channel 开始的. 一个 channel 类似于一个 stream。在Netty中,Channel是客户端和服务端建立的一个连接通道。
虽然java Stream 和 NIO Channel都是负责I/O操作,但他们还是有许多区别的:

  • 1)我们可以在同一个 Channel 中执行读和写操作, 然而同一个 Stream 仅仅支持读或写。
  • 2)Channel 可以异步地读写, 而 Stream 是阻塞的同步读写。
  • 3)Channel 总是从 Buffer 中读取数据, 或将数据写入到 Buffer 中。

几者的关系图如下:


总结:
一个Channel包含一个ChannelPipeline,创建Channel时会自动创建一个ChannelPipeline,每个Channel都有一个管理它的pipeline,这关联是永久性的。

这点从源码中就可以看出,我之前写的博客里有说到:【Netty】5 源码 Bootstrap。每一个ChannelPipeline中可以包含多个ChannelHandler。所有ChannelHandler

都会顺序加入到ChannelPipeline中,ChannelHandler实例与ChannelPipeline之间的桥梁是ChannelHandlerContext实例。
可见,Pipeline的事件传播,是靠Pipeline,Context和Handler共同协作完成的。

五、详细分析下ChannelPipeline的addLast()和remove()

1、ChannelPipeline.addLast()

这里关注一下ChannelPipeline接口中的这个重载方法

ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);

我们常常遇到这样的需求:在一个业务逻辑处理器中,需要写数据库、进行网络连接等耗时业务。Netty的原则是不阻塞I/O线程,所以需指定Handler执行的线程池。

如果MyBusinessLogicHandler是一个耗时的处理逻辑,应该制定group,避免I/O线程被阻塞,如果业务逻辑是异步处理或处理时间很快那么可以不用指定group。

#指定group的用法
static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);

// 复杂耗时业务,使用新的线程池
pipline.addLast(group, "handler", new MyBusinessLogicHandler());

ChannelPipeline.addLast()内部实现

public class DefaultChannelPipeline implements ChannelPipeline {
    @Override
    public final ChannelPipeline addLast(String name, ChannelHandler handler) {
        return addLast(null, name, handler);
    }

    @Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            // 1.检查是否有重复handler
            checkMultiplicity(handler);
            // 2.创建节点
            newCtx = newContext(group, filterName(name, handler), handler);
            // 3.添加节点
            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;
            }
        }
        // 4.回调用户方法
        callHandlerAdded0(newCtx);
        return this;
    }
}

这里简单地用synchronized方法是为了防止多线程并发操作pipeline底层的双向链表
我们还是逐步分析上面这段代码

  • 检查是否有重复handler
    在用户代码添加一条handler的时候,首先会查看该handler有没有添加过
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;
    }
}

netty使用一个成员变量added标识一个channel是否已经添加,上面这段代码很简单,如果当前要添加的Handler是非共享的,并且已经添加过,那就抛出异常,否则,标识该handler已经添加

由此可见,一个Handler如果是sharable的,就可以无限次被添加到pipeline中,我们客户端代码如果要让一个Handler被共用,只需要加一个@Sharable标注即可,如下:

@Sharable
public class BusinessHandler {
    
}

而如果Handler是sharable的,一般就通过spring的注入的方式使用,不需要每次都new 一个
isSharable() 方法正是通过该Handler对应的类是否标注@Sharable来实现的

public abstract class ChannelHandlerAdapter implements ChannelHandler {
    public boolean isSharable() {
        
        Class clazz = getClass();
        Map, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
        Boolean sharable = cache.get(clazz);
        if (sharable == null) {
            sharable = clazz.isAnnotationPresent(Sharable.class);
            cache.put(clazz, sharable);
        }
        return sharable;
    }
}

通过反射判断是否有Sharable.class注解

  • 创建节点
    回到主流程,看创建上下文这段代码
newCtx = newContext(group, filterName(name, handler), handler);

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

private String filterName(String name, ChannelHandler handler) {
    if (name == null) {
        return generateName(handler);
    }
    checkDuplicateName(name);
    return name;
}

显然,我们传入的name为null,netty就给我们生成一个默认的name,否则,检查是否有重名,检查通过的话就返回

netty创建默认name的规则为 简单类名#0,下面我们来看些具体是怎么实现的

private static final FastThreadLocal, String>> nameCaches =
            new FastThreadLocal, String>>() {
        @Override
        protected Map, String> initialValue() {
            return new WeakHashMap, String>();
        }
};

private String generateName(ChannelHandler handler) {
    // 先查看缓存中是否有生成过默认name
    Map, String> cache = nameCaches.get();
    Class handlerType = handler.getClass();
    String name = cache.get(handlerType);
    if (name == null) {
        // 没有生成过,就生成一个默认name,加入缓存 
        name = generateName0(handlerType);
        cache.put(handlerType, name);
    }

    // 生成完了,还要看默认name有没有冲突
    if (context0(name) != null) {
        String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'.
        for (int i = 1;; i ++) {
            String newName = baseName + i;
            if (context0(newName) == null) {
                name = newName;
                break;
            }
        }
    }
    return name;
}

netty使用一个 FastThreadLocal变量来缓存Handler的类和默认名称的映射关系,在生成name的时候,首先查看缓存中有没有生成过默认name(简单类名#0),如果没有生成,就调用generateName0()生成默认name,然后加入缓存

接下来还需要检查name是否和已有的name有冲突,调用context0(),查找pipeline里面有没有对应的context

private AbstractChannelHandlerContext context0(String name) {
    AbstractChannelHandlerContext context = head.next;
    while (context != tail) {
        if (context.name().equals(name)) {
            return context;
        }
        context = context.next;
    }
    return null;
}

context0()方法链表遍历每一个 ChannelHandlerContext,只要发现某个context的名字与待添加的name相同,就返回该context,最后抛出异常,可以看到,这个其实是一个线性搜索的过程

如果context0(name) != null 成立,说明现有的context里面已经有了一个默认name,那么就从 简单类名#1 往上一直找,直到找到一个唯一的name,比如简单类名#3

如果用户代码在添加Handler的时候指定了一个name,那么要做到事仅仅为检查一下是否有重复

private void checkDuplicateName(String name) {
    if (context0(name) != null) {
        throw new IllegalArgumentException("Duplicate handler name: " + name);
    }
}

处理完name之后,就进入到创建context的过程

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

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的引用,进入到其父类

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;
    // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
    ordered = executor == null || executor instanceof OrderedEventExecutor;
}

netty中用两个字段来表示这个channelHandlerContext属于inBound还是outBound,或者两者都是,两个boolean是通过下面两个小函数来判断(见上面一段代码)

 final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
    private static boolean isInbound(ChannelHandler handler) {
        return handler instanceof ChannelInboundHandler;
    }

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

通过instanceof关键字根据接口类型来判断,因此,如果一个Handler实现了两类接口,那么他既是一个inBound类型的Handler,又是一个outBound类型的Handler,比如下面这个类



常用的,将decode操作和encode操作合并到一起的codec,一般会继承 MessageToMessageCodec,而MessageToMessageCodec就是继承ChannelDuplexHandler

public abstract class MessageToMessageCodec extends ChannelDuplexHandler {

    protected abstract void encode(ChannelHandlerContext ctx, OUTBOUND_IN msg, List out)
            throws Exception;

    protected abstract void decode(ChannelHandlerContext ctx, INBOUND_IN msg, List out)
            throws Exception;
}
 
 

添加节点
用下面这幅图可见简单的表示这段过程,说白了,其实就是一个双向链表的插入操作


操作完毕,该context就加入到pipeline中

到这里,pipeline添加节点的操作就完成了,你可以根据此思路掌握所有的addxxx()系列方法

回调用户方法

abstract class AbstractChannelHandlerContext extends DefaultAttributeMap
        implements ChannelHandlerContext, ResourceLeakHint {
    final void callHandlerAdded() throws Exception {
        //查看该节点的状态
        if (setAddComplete()) {
            handler().handlerAdded(this);
        }
    }

    //设置该节点的状态
    final boolean setAddComplete() {
        for (;;) {
            int oldState = handlerState;
            if (oldState == REMOVE_COMPLETE) {
                return false;
            }
            // Ensure we never update when the handlerState is REMOVE_COMPLETE already.
            // oldState is usually ADD_PENDING but can also be REMOVE_COMPLETE when an EventExecutor is used that is not
            // exposing ordering guarantees.
            if (HANDLER_STATE_UPDATER.compareAndSet(this, oldState, ADD_COMPLETE)) {
                return true;
            }
        }
    }
}

2、ChannelPipeline.remove()

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

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

public class AuthHandler extends SimpleChannelInboundHandler {
    @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) 这段代码

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

找到待删除的节点

public class DefaultChannelPipeline implements ChannelPipeline {    
    private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
        AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
        if (ctx == null) {
            throw new NoSuchElementException(handler.getClass().getName());
        } else {
            return ctx;
        }
    }
    @Override
    public final ChannelHandlerContext context(Class handlerType) {
        if (handlerType == null) {
            throw new NullPointerException("handlerType");
        }

        AbstractChannelHandlerContext ctx = head.next;
        for (;;) {
            if (ctx == null) {
                return null;
            }
            if (handlerType.isAssignableFrom(ctx.handler().getClass())) {
                return ctx;
            }
            ctx = ctx.next;
        }
    }
}

这里为了找到Handler对应的context,照样是通过依次遍历双向链表的方式,直到某一个context的Handler和当前Handler相同,便找到了该节点

调整双向链表指针删除

public class DefaultChannelPipeline implements ChannelPipeline {    
    private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
        assert ctx != head && ctx != tail;

        synchronized (this) {
            remove0(ctx);

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

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

经历的过程要比添加节点要简单,可以用下面一幅图来表示



删除后的结果为:



结合这两幅图,可以很清晰地了解权限验证Handler的工作原理,另外,被删除的节点因为没有对象引用到,过段时间就会被gc自动回收
public class DefaultChannelPipeline implements ChannelPipeline {    
    private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
        // Notify the complete removal.
        try {
            ctx.callHandlerRemoved();
        } catch (Throwable t) {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
        }
    }
}

pipeline中的节点删除完成,于是便开始回调用户代码 ctx.handler().handlerRemoved(ctx);,常见的代码如下:

public class DemoHandler extends SimpleChannelInboundHandler<...> {
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 节点被删除完毕之后回调到此,可做一些资源清理
        // do something
    }
}

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

final void setRemoved() {
    handlerState = REMOVE_COMPLETE;
}

总结:

  • 1、在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应。
  • 2、ChannelPipeline是一个维护了一个以 AbstractChannelHandlerContext 为节点的双向链表,其中此链表是 以head(HeadContext)作为头,以tail(TailContext)作为尾的双向链表.
  • 3、pipeline中的每个节点包着具体的处理器ChannelHandler,节点根据ChannelHandler的类型是ChannelInboundHandler还是ChannelOutboundHandler来判断该节点属于in还是out或者两者都是

参考:
https://www.cnblogs.com/qdhxhz/p/10234908.html

https://www.cnblogs.com/java-chen-hao/p/11466291.html

你可能感兴趣的:(Netty源码分析——ChannelPipeline)