Netty进阶:Pilpeline源码分析


在上篇文章中提到每个SocketCahnnel或者ServerSocketChannel的父类AbstractChannel的构造函数中会实例化DefaultChannelPipeline。在本文中会详细的介绍ChannelPiple实例化过程中的细节、以及ChannelPiple的工作原理。

1. ChannelPiple

每当 ServerSocketChannel 创建一个新的连接,就会创建一个 SocketChannel,对应的就是目标客户端。而每一个 SocketChannel(包括ServerSocketChannel)都将会分配一个全新的 ChannelPipeline,他们的关系是永久不变的;而每一个 ChannelPipeline 内部都含有多个 ChannelHandlerContext,他们一起组成了双向链表,这些 Context 用于包装我们调用 addLast 方法时添加的 ChannelHandler。关系图如下:
Netty
再来来看看DefaultChannelPipeline类的继承关系图:
netty

看到DefaultChannelPipeline实现了 ChannelInboundInvoker及ChannelOutboundInvoker两个接口。顾名思义,一个是处理通道的inbound事件调用器,另一个是处理通道的outbound事件调用器。

  • inbound: 本质上就是执行I/O线程将从外部read到的数据传递给业务线程的一个过程。
  • outbound: 本质上就是业务线程将数据传递给I/O线程, 直至发送给外部的一个过程。

再回顾一下上篇文章中已经提到过的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;
}

在此构造函数中绑定了当前channel实例,然后初始化双向列表的头尾节点。其中head是HeadContext的实例,tail是TailContext的实例,HeadContext与TailContext都是DefaultChannelPipeline的内部类。下面看看它们的类的继承结构。

HeadContext类:
这里写图片描述
TailContext类:
这里写图片描述
从类继承图我们可以看出:

  1. HeadContext与TailContext都是通道的handler(中文一般叫做处理器)
  2. HeadContext既可以用于outbound过程的handler,也可以用于inbound过程的handler
  3. TailContext只可以用于inbound过程的handler
  4. HeadContext 与 TailContext 同时也是一个处理器上下文对象

下面继续进入到HeadContext的构造方法

HeadContext(DefaultChannelPipeline pipeline) {
    super(pipeline, null, HEAD_NAME, false, true);
	//获取channel中的unsafe对象
    unsafe = pipeline.channel().unsafe();
    setAddComplete();
}

AbstractChannelHandlerContext的构造方法:

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

此构造方法,只是设置了当前context对象对应的Pipeline以及此context是作用于outbound。

最后一行代码setAddComplete设置当前节点的状态,通过sun.misc.Unsafe的CAS操作来完成的。

小结:

  1. 每个channel初始化时,都会创建一个与之对应的pipeline
  2. 此pipeline内部就是一个双向链表
  3. 双向链表的头结点是处理outbound过程的handler,尾节点是处理inbound过程的handler;
  4. 双向链表的结点同时还是handler上下文对象;
2. 添加初始化Handller

通过上一节 知道了ChannelPipeline 中含有两个 ChannelHandlerContext(同时也是 ChannelHandler), 我们知道ServerSocketChannel和SocketChannel都关联着PipeLine,当然后者是被前者创建的。在Netty中,ServerSocketChannel有两个特殊的Handler,一个是初始化Handler,另一个是处理Accept事件的Handler。

初始化Handler的在服务端启动过程中的创建了继承了ChannelInitializer的匿名类。源码后面看,先说个大概的作用,它的作用是添加用户自定义Handler和处理Accept事件的Handler。

这里说一下处理Accept事件的Handler,Netty中的实现叫做ServerBootStrapAcceptor,它的作用是就是处理Accept事件详细过程在后续Accept事件的文章中介绍。

但是这个 Pipeline是如何添加我们指定的handller呢?一般在我们服务端会有如下的代码:

 //设置ServerSocketChannel关联的handler
 .handler(new LoggingHandler(LogLevel.INFO))
 //设置SocketChannel关联的handler
 .childHandler(new ChannelInitializer<SocketChannel>() {
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
         p.addLast(new Someonehandller());
  }

如果看过ServerBootStrap源码分析Netty进阶篇:ServerBootStrap源码分析就知道这里只是设置了ServerBootStrap中的childHandller和AbstractBootStart中的handller属性,当然handler()不是必须的。那么这里设置的handler和何时添加到PipleLine中呢?

AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister->init(channel)

ServerBootStrap重写了init方法,比BootStartp实现稍微复杂一些。

@Override
void init(Channel channel) throws Exception {
    //省略部分代码
    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
  	//...
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = handler();
            if (handler != null) {
            	//将ServerSocketChannel的Handler添加到pipeline
                pipeline.addLast(handler);
            }
            ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                    	//将ServerBootstrapAcceptor添加到ServerSocketChannel的pipeline
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
    		});
		}
	}
}

这个方法此处传入的参数channel是NioServerSocketChannel,channel.pipeline()获取此channel关联的PipleLine,接下来是调用关联的pipeline的addLast()方法,是new了一个ChannelInitializer的匿名对象,下面看看ChannelInitializer的继承关系
netty
通过类继承图,我们得知ChannelInitializer的匿名对象其实就是一个处理inbound过程的处理器。

addLast最终具体实现如下:

 public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);
            newCtx = newContext(group, filterName(name, handler), handler);
            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;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

newContext方法的作用就是对传入的handler进行包装,最后返回一个绑定了handler的context对象,也就是DefaultChannelHandlerContext实例,形成和PipeLine中head和tail相似的格式,如何对这个Handler设置呢?看到源码后真的不得不叹服Netty开发者博大精深!
newContext方法中调用了下面构造方法:

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

内部调用了父类AbstractChannelHandlerContext构造方法,isInbound方法的返回值是布尔类型。

接下里看addlast0方法:

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

看到这里就很舒服了,这就是在尾节点前面插入一个节点的操作。

init方法就是给NioServerSocketChannel的pipeLine中添加一个匿名ChannelInitializer类,在该类的initChannel方法中添加自定义Handler和ServerBootStrapAcceptor。现在pipeLine中有三个Handler:Head+初始化Handler+Tail。

3. ServerSocketChannel添加Acceptor Handler和自定义Handler

那么有一个疑问,该匿名类的initChannel方法什么时候执行?也就是自定义Handler和ServerBootstrapAcceptor什么时候被添加到pipleLine中?

在ServerBootStarp源码分析中提到过,当channel注册到selector后的调用链为:

Bootstrap.initAndRegister -> 
    AbstractBootstrap.initAndRegister -> 
        MultithreadEventLoopGroup.register -> 
            SingleThreadEventLoop.register -> 
                AbstractUnsafe.register -> //交给EventLoop执行
                	AbstractUnsafe.register0 -> 
------------------------------------------

 private void register0(ChannelPromise promise) {

    boolean firstRegistration = neverRegistered;
    doRegister();
    neverRegistered = false;
    registered = true;
    pipeline.invokeHandlerAddedIfNeeded();
    safeSetSuccess(promise);
    pipeline.fireChannelRegistered();
   //注册ServerSocketChannel是交给EventLoop执行的,所以还没有绑定端口
   //isActive()为false,所以不会执行下面内容。当Channel激活才会调用fireChannelActive。fireChannelActive的调用过程后面分析。
    if (isActive()) {
        if (firstRegistration) {
            pipeline.fireChannelActive();
        } else if (config().isAutoRead()) {
            beginRead();
        }
    }

}

doRegister方法最后调用了AbstractAbstractNioChannel的doRegister,其内部是JDK NIO channel的register。我们主要看一下fireChannelRegistered,也就是注册时间在pipeline的传递逻辑

@Override
public final ChannelPipeline fireChannelRegistered() {
    AbstractChannelHandlerContext.invokeChannelRegistered(head);
    return this;
}

static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRegistered();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

参数head 是一个 AbstractChannelHandlerContext 实例, 并且它没有重写 invokeChannelRegistered方法,因此调用的AbstractChannelHandlerContext中实现的方法。

@Override
private void invokeChannelRegistered() {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRegistered(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRegistered();
    }
}

handler() 返回的是和Context关联的handler,关联的操作是在addLast方法中的newContext,前面有讲到。Head实现了channelRegistered方法,但是他的内部没有真正的逻辑,只是继续调用ctx.fireChannelRegistered(),这里是AbstractChannelHandlerContext的实现,pipeline.fireChannelRegistered()是调用链的入口,但是这两个方法的内部差不多,只不过invokeChannelRegistered()方法的参数不一样,前者是findContextInbound(),显然这个参数是下一个InBoundhandler。

当遍历到初始化Handler对应的Context时, 并接着调用了 ChannelInitializer.channelRegistered 方法. 继续进入此方法

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        // Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
        // the handler.
        if (initChannel(ctx)) {
            // we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
            // miss an event.
            ctx.pipeline().fireChannelRegistered();
        } else {
            // Called initChannel(...) before which is the expected behavior, so just forward the event.
            ctx.fireChannelRegistered();
        }
    }


private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
        try {
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
            // We do so to prevent multiple calls to initChannel(...).
            exceptionCaught(ctx, cause);
        } finally {
            remove(ctx);
        }
        return true;
    }
    return false;
}

initChannel(C ch) 这个方法就是我们重写的方法,在此处调用, 当添加了自定义的 ServerBootstrapAcceptor 后, 会删除初始化Handler这个ChannelHandler, 即 remove(ctx),与此同时又会发出一个事件,又回到了第三节的开头。至于为什么要再次发出这个事件?官方的注释是:我们调用initChannel(…)所以我们需要现在调用pipeline.fireChannelRegistered()以确保我们不会错过任何事件。

因此最后的 Pipeline就只有:head+自定义handlller+ServerBootstrapAcceptor+tail 。ServerBootstrapAcceptord和我们在handler(xxxhandler)中设置的是同一个级别的,都是Inboundhandler,更多内容在Accept事件处理的文章中详细介绍。

最后以一张图总结添加ServerSocketChannel的handler过程。
Netty

4 . Connect事件传输机制

1.在客户端启动分析中当调用了 Bootstrap.connect 方法时,就会触发一个 Connect 请求事件,回顾一下调用过程,pipeline 的 connect 代码如下:

public final ChannelFuture connect(
            SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, localAddress, promise);
}
  1. connect 事件传递到 Pipeline 后, 它其实是以 tail 为起点开始传播的.而 tail.connect 其实调用的是 AbstractChannelHandlerContext.connect 方法:
public ChannelFuture connect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        ext.invokeConnect(remoteAddress, localAddress, promise);
        //省略干扰代码
    }
  1. findContextOutbound() 顾名思义, 它的作用是以当前 Context 为起点, 向 Pipeline 中的 Context 双向链表的前端寻找第一个 outbound 属性为真的 Context(即关联着 ChannelOutboundHandler 的 Context), 然后返回。找到了一个 outbound 的 Context 后, 就调用它的 invokeConnect 方法, 这个方法中会调用 Context 所关联着的 ChannelHandler 的 connect 方法:
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
                ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            connect(remoteAddress, localAddress, promise);
        }
    }
  1. 如果用户没有重写 ChannelHandler 的 connect 方法, 那么会调用 ChannelOutboundHandlerAdapter 所实现的方法:
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
        SocketAddress localAddress, ChannelPromise promise) throws Exception {
    ctx.connect(remoteAddress, localAddress, promise);
}

ChannelOutboundHandlerAdapter.connect 仅仅调用了 ctx.connect,因此又回到了(2),继续寻找outboundhandler,直到 connect 事件传递到DefaultChannelPipeline 的双向链表的头节点, 即 head 中,在head节点中调用unsafe.connect方法:

 @Override
public void connect(
    ChannelHandlerContext ctx,
    SocketAddress remoteAddress, SocketAddress localAddress,
    ChannelPromise promise) throws Exception {
	unsafe.connect(remoteAddress, localAddress, promise);
}

下面以一幅图来描述一个整个 Connect 请求事件的处理过程:
netty

5. 总结

Inbound 事件传播方法有:

ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()

如果我们捕获了一个事件, 并且想让这个事件继续传递下去, 那么需要调用 Context 相应的传播方法.
和connect类似的Outbound事件还有:

ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)

注意handler和context的的区别,比如context中connect方法是事件传输的介质,handler中connect是真正的处理事件。

Inbound 事件:

  • Inbound 事件是通知事件, 当某件事情已经就绪后, 通知上层.

  • Inbound 事件发起者是 unsafe

  • Inbound 事件的处理者是 Channel, 如果用户没有实现自定义的处理方法, 那么Inbound 事件默认的处理者是 TailContext, 并且其处理方法是空实现.

  • Inbound 事件在 Pipeline 中传输方向是 head -> tail

  • 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.fireIN_EVT (例如 ctx.fireChannelActive) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.

  • Outbound 事件流: Context.fireIN_EVT -> Connect.findContextInbound -> nextContext.invokeIN_EVT -> nextHandler.IN_EVT -> nextContext.fireIN_EVT

Outbound事件:

  • Outbound 事件是请求事件(由 Connect 发起一个请求, 并最终由 unsafe 处理这个请求)

  • Outbound 事件的发起者是 Channel

  • Outbound 事件的处理者是 unsafe

  • Outbound 事件在 Pipeline 中的传输方向是 tail -> head.

  • 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.xxx (例如 ctx.connect) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.

  • Outbound 事件流: Context.OUT_EVT -> Connect.findContextOutbound -> nextContext.invokeOUT_EVT -> nextHandler.OUT_EVT -> nextContext.OUT_EVT

下篇介绍Netty中最最核心的EventLoop

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值