Netty学习——源码篇8 Outbound/Inbound传播方式

接上篇:Netty学习——源码篇7 Pipeline的事件传播机制

1 Outbound事件传播方式

        Outbound事件都是请求事件(Request Event),即请求某件事情的发生,然后通过Outbound事件进行通知。

        Outbound事件的传播方向是从Tail到customContext再到Head。下面以Connect事件为例,分析一下Outbound事件的传播机制。

        首先,当用户调用了Bootstrap的connect方法时,就会触发一个Connect请求事件,此调用会触发调用链,如下图所示。

fc9bce2ba489418d81e6d6f2fe56b78e.jpeg

        继续跟踪,发现AbstractChannel的connect方法又调用了DefaultChannelPipeline的connect 方法,代码如下:

    public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        return pipeline.connect(remoteAddress, localAddress, promise);
    }

         而pipeline.connect()方法的代码如下:

    public final ChannelFuture connect(
            SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, localAddress, promise);
    }

        可以看到,当Outbound事件(这里是Connect事件)传递到Pipeline后,其实是以Tail为起点开始传播的。

        而tail.connect调用的是AbstractChannelHandlerContext的connect方法。

 public ChannelFuture connect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

        if (remoteAddress == null) {
            throw new NullPointerException("remoteAddress");
        }
        if (!validatePromise(promise, false)) {
            // cancelled
            return promise;
        }

        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeConnect(remoteAddress, localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeConnect(remoteAddress, localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

        顾名思义,findContextOutbound 方法的作用是以当前Context为起点,向Pipeline中Context双向链表的前段寻找第一个Outbound属性为true的Context(即关联ChannelOutboundHandler的Context),然后返回。findContextOutbound()方法实现代码:

    private AbstractChannelHandlerContext findContextOutbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }

        当找到了一个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);
        }
    }

        如果用户没有重写ChannelHandler的connect方法,那么会调用ChannelOutboundHandlerAdapter的connect方法,代码如下:

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

        可以看到,ChannelOutboundHandlerAdapter的connect方法仅调用了ctx.connect方法,而这个调用又回到了Cotext.connect方法调用Connect.findContextOutbound方法,然后调用next.invokeConect方法,其次调用handler.connect方法,最后又调用Context.connect方法,如此循环下去,直到Connect事件传递到DefaultChannelPipeline的双向链表的头节点,即Head中。为什么会传递到Head中呢?回想一下,Head实现了ChannelOutboundHandler,因此它的Outbound属性是true。

        因为Head本身即是一个ChannelHandlerCont,又实现了ChannelOutboundHandler接口,所以当connect消息传递到Head后,会将消息传递到对应的ChannelHandler中处理,而Head的handler方法返回的就是Head本身。因此最终Connect事件是在Head中被处理的。Head的Connect事件处理逻辑的代码如下:

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

        到这里,整个Connect请求事件就结束了。下图描述了整个 Connect请求事件的处理过程。

60d046ff7adf47fda8970cbbcda3ae7f.jpeg

        仅仅以Connect请求事件为例,分析了Outbound事件的传播过程,但是其实所有的Outbound的事件传播都遵循着一样的传播规律。

2 Inbound事件传播方式

        Inbound和Outbound事件的处理过程是类似的,只是传播方向不同。

        Inbound事件是一个通知事件,即某件事已经发生了,然后通过Inbound事件进行通知。Inbound通常发生在Channel的状态改变或I/O事件就绪时。

        Inbound的热点是其传播方向从Head到customContext再到Tail.

        上面分析了connect()方法其实是一个Outbound事件,那么接着分析connect()事件后会发生什么Inbound事件,并最终找到Outbound和Inbound事件之间的联系。当Connect()事件传播到Unsafe后,其实是在AbstractNioUnsafe的connect方法中进行处理的,代码如下:
 

public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            try {
                if (connectPromise != null) {
                    // Already a connect in process.
                    throw new ConnectionPendingException();
                }

                boolean wasActive = isActive();
                if (doConnect(remoteAddress, localAddress)) {
                    fulfillConnectPromise(promise, wasActive);
                } else {
                    connectPromise = promise;
                    requestedRemoteAddress = remoteAddress;

                    // Schedule connect timeout.
                    int connectTimeoutMillis = config().getConnectTimeoutMillis();
                    if (connectTimeoutMillis > 0) {
                        connectTimeoutFuture = eventLoop().schedule(new Runnable() {
                            @Override
                            public void run() {
                                ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                                ConnectTimeoutException cause =
                                        new ConnectTimeoutException("connection timed out: " + remoteAddress);
                                if (connectPromise != null && connectPromise.tryFailure(cause)) {
                                    close(voidPromise());
                                }
                            }
                        }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
                    }

                    promise.addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.isCancelled()) {
                                if (connectTimeoutFuture != null) {
                                    connectTimeoutFuture.cancel(false);
                                }
                                connectPromise = null;
                                close(voidPromise());
                            }
                        }
                    });
                }
            } catch (Throwable t) {
                promise.tryFailure(annotateConnectException(t, remoteAddress));
                closeIfClosed();
            }
        }

        在AbstractNioUnsafe的connect方法中,先调用doConnect方法进行实际的Socket连接,当连接后会调用fulfillConnectPromise方法,代码如下:

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
            if (promise == null) {
                
                return;
            }

            boolean active = isActive();

           
            boolean promiseSet = promise.trySuccess();

            if (!wasActive && active) {
                pipeline().fireChannelActive();
            }

           
            if (!promiseSet) {
                close(voidPromise());
            }
        }

        可以看到,在fulfillConnectPromise方法中,会通过调用pipeline.fireChannelActive方法将通道激活的消息(即Socket连接成功)发送出去。而这里,当调用pipeline.fireXXX后,就是Inbound事件的起点。因此当调用pipeline.fireChannelActive方法时,就产生了一个ChannelActive Inbound事件,接下来看一下Inbound事件是怎么传播的,代码如下:

    @Override
    public final ChannelPipeline fireChannelActive() {
        AbstractChannelHandlerContext.invokeChannelActive(head);
        return this;
    }

        在fireChannelActive方法中调用了invokeChannelActive(head)方法,因此可以证明Inbound事件在pipeline中传输的起点是Head。head.invokeChannelActive(head)方法代码如下:

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

        回想一下在Outbound事件(例如Connect事件)的传输过程中,也有类似的如下操作:

        1、首先调用findContextInbound(),从Pipeline的双向链表中找到第一个Inbound属性为true的Context,然后将其返回。

        2、调用Context的invokeChannelActive()方法,invokeChannelActive方法代码如下:

    private void invokeChannelActive() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelActive(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelActive();
        }
    }

        这个方法和Outbound的对应方法如出一辙。与Outbound一样,如果用户没有重写channelActive方法,就会调用ChannelInboundHandlerAdapter的channelActive方法,代码如下:

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }

        同样的,在ChannelInboundHandlerAdapter的channelActive方法中,仅仅调用了ctx.fireChannelActive方法,因此就调用Context.fireChannelActive方法,其次调用Connect.findContextInbound()方法,然后调用nextContext.invokeChannelActive方法。再然后调用nextHandler.channelActive()方法,最后调用nextContext.fireChannelActive方法,如此循环。下图描述了Inbound事件的传输过程。

3 小结

3.1 Outbound事件传播过程总结如下

        1、Outbound 事件是请求事件(由Context发起一个请求,并最终由Unsafe处理这个请求)。

        2、Outbound事件 发起者是Channel。

        3、Outbound事件的处理者是Unsafe。

        4、Outbound事件在Pipeline中的传输方向是从Tail到Head。

        5、在ChannelHandler中处理事件时,如果这个Handler不是最后一个Handler,则需要调用ctx的方法将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。

        6、Outbound事件传播方向是,从Context.OUT_EVT()方法到Connect.findContextOutbound()方法,再到nextContext.invokeOUT_EVT方法,再到nextHandler.OUT_EVT方法,最后到nextContext.OUT_EVT方法。

3.2 Inbound事件传播过程总结如下

        1、Inbound事件为通知事件,当某件事情已经就绪后,会通知上层。

        2、Inbound事件的发起者是Unsafe。

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

        4、Inbound事件在Pipeline中的传输方法是从Head到Tail。

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

        6、Inbound事件的传播方向是,从Context.fireIN_EVT()方法到Connect.findContextInbound()方法,再到nextContext.invokeIN_EVT()方法,再到nextHandler.IN_EVT方法,最后到nextContext.fireIN_EVT方法。

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geminigoth

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值