Pipeline 的事件传播机制

Pipeline 的事件传播机制

前面章节中,我们已经知道 AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量,分别用于标识 Context 所对应的 handler 的类型,即:

1、inbound 为 true 是,表示其对应的 ChannelHandler 是 ChannelInboundHandler 的子类。

2、outbound 为 true 时,表示对应的 ChannelHandler 是 ChannelOutboundHandler 的子类。

这里大家肯定还有很多疑惑,不知道这两个字段到底有什么作用? 这还要从 ChannelPipeline 的事件传播类型说起。

Netty 中的传播事件可以分为两种:Inbound 事件和 Outbound 事件。如下是从 Netty 官网针对这两个事件的说明:

img

从上图可以看出,inbound 事件和 outbound 事件的流向是不一样的,inbound 事件的流行是从下至上,而 outbound刚好相反,是从上到下。并且 inbound 的传递方式是通过调用相应的 ChannelHandlerContext.fireIN_EVT()方法,而outbound 方法的的传递方式是通过调用 ChannelHandlerContext.OUT_EVT()方法。例如:ChannelHandlerContext 的 fireChannelRegistered()调用会发送一个 ChannelRegistered 的 inbound 给下一个 ChannelHandlerContext,而 ChannelHandlerContext 的 bind()方法调用时会发送一个 bind 的 outbound 事件给下一个 ChannelHandlerContext。

Inbound 事件传播方法有:

public interface ChannelInboundHandler extends ChannelHandler { 
    void channelRegistered(ChannelHandlerContext ctx) throws Exception; 
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception; 
    void channelActive(ChannelHandlerContext ctx) throws Exception; 
    void channelInactive(ChannelHandlerContext ctx) throws Exception; 
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; 
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception; 
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception; 
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception; 
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; 
}

Outbound 事件传播方法有:

public interface ChannelOutboundHandler extends ChannelHandler { 

	void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception; 
	void connect( 
        ChannelHandlerContext ctx, SocketAddress remoteAddress, 
        SocketAddress localAddress, ChannelPromise promise) throws Exception; 
	void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; 
	void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; 
	void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; 
	void read(ChannelHandlerContext ctx) throws Exception; 
	void flush(ChannelHandlerContext ctx) throws Exception; 
}

大家应该发现了规律:inbound 类似于是事件回调(响应请求的事件),而 outbound 类似于主动触发(发起请求的 事件)。注意,如果我们捕获了一个事件,并且想让这个事件继续传递下去,那么需要调用 Context 对应的传播方法 fireXXX,例如:

public class MyInboundHandler extends ChannelInboundHandlerAdapter { 
@Override 
	public void channelActive(ChannelHandlerContext ctx) throws Exception { 
        System.out.println("连接成功"); 
        ctx.fireChannelActive(); 
	} 
}

public class MyOutboundHandler extends ChannelOutboundHandlerAdapter { 
@Override 
	public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception 		{ 
		System.out.println("客户端关闭"); 
		ctx.close(promise); 
	} 
}

如上面的示例代码:MyInboundHandler 收到了一个 channelActive 事件,它在处理后,如果希望将事件继续传播下去,那么需要接着调用 ctx.fireChannelActive()方法。

Outbound 事件传播方式

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

Outbound 事件的传播方向是 tail -> customContext -> head。

我们接下来以 connect 事件为例,分析一下 Outbound 事件的传播机制。

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

img

继续跟踪,我们就发现 AbstractChannel 的 connect()其实由调用了 DefaultChannelPipeline 的 connect()方法:

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

而 pipeline.connect()方法的实现如下:

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

可以看到,当 outbound 事件(这里是 connect 事件)传递到 Pipeline 后,它其实是以 tail 为起点开始传播的。

而 tail.connect()其实调用的是 AbstractChannelHandlerContext 的 connect()方法:

public ChannelFuture connect( 
    final SocketAddress remoteAddress, 
    final SocketAddress localAddress, final ChannelPromise promise) { 
    //此处省略 N 句 
    final AbstractChannelHandlerContext next = findContextOutbound(); 
    EventExecutor executor = next.executor(); 
    next.invokeConnect(remoteAddress, localAddress, promise); 
    //此处省略 N 句 
    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() 实现:

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

我们看到,ChannelOutboundHandlerAdapter 的 connect()仅仅调用了 ctx.connect(),而这个调用又回到了:

Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect

这样的循环中,直到 connect 事件传递到 DefaultChannelPipeline 的双向链表的头节点,即 head 中。为什么会传递 到 head 中呢?回想一下,head 实现了 ChannelOutboundHandler,因此它的 outbound 属性是 true。

因为 head 本身既是一个 ChannelHandlerContext,又实现了 ChannelOutboundHandler 接口,因此当 connect()消息 传递到 head 后,会将消息转递到对应的 ChannelHandler 中处理,而 head 的 handler()方法返回的就是 head 本身:

public ChannelHandler handler() { 
	return this; 
} 

因此最终 connect()事件是在 head 中被处理。head 的 connect()事件处理逻辑如下:

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

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

img

我们仅仅以 connect()请求事件为例,分析了 outbound 事件的传播过程,但是其实所有的 outbound 的事件传播都遵循着一样的传播规律,小伙伴们可以试着分析一下其他的 outbound 事件,体会一下它们的传播过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值