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 事件,体会一下它们的传播过程。

<think>嗯,用户这次的问题聚焦在Netty的设计模式上,特别点名了Builder模式、责任链模式、工厂模式和适配器模式。看来用户已经对Netty有一定基础认知,现在想深入理解这些模式的具体实现。结合用户引用的三段资料,我需要把理论模式和代码实例对应起来讲透。 用户提到ServerBootstrap的Builder模式,正好引用[1]给出了完美示例。这里要强调链式调用的设计美学:group()、channel()、childHandler()这些方法都返回ServerBootstrap自身,使得配置过程像拼积木一样流畅。这种设计在创建复杂对象时尤其有用,比如需要组合线程模型、通道类型、处理器链的场景。 关于责任链模式,引用[2][3]提供了关键线索。用户可能疑惑为什么ctx.fireChannelRead(msg)这么重要——这正是责任链传递事件的精髓。需要说明DefaultChannelPipeline内部的双向链表结构,以及head/tail这两个默认handler的哨兵作用。可以举InBoundHandlerC的例子,说明消息如何像接力棒一样在处理器间传递。 工厂模式那块用户提到channel创建,但引用资料没直接对应。不过根据知识储备,NioServerSocketChannel.class的传参其实触发了ReflectiveChannelFactory的创建,这块需要补充反射工厂的运作机制。用户可能想知道为什么用工厂而不是直接new,这里要强调配置解耦的好处。 适配器模式是Handler设计的精华。用户可能注意到很多handler都继承ChannelInboundHandlerAdapter而非直接实现接口,这就是适配器模式的典型应用——用空实现屏蔽不关心的事件方法。要特别说明这种设计如何简化开发,避免强制重写所有接口方法。 最后考虑设计相关问题时要扣住实践:如何自定义责任链?工厂模式怎么扩展?这些都能帮用户深化理解。不过要注意引用标记的准确性,比如ServerBootstrap的示例必须标注[^1]。</think>Netty 中巧妙运用多种设计模式实现高扩展性和高性能,以下是核心模式及其作用分析(结合引用内容): --- ### **1. 建造者模式(Builder Pattern)** - **作用**:**封装复杂对象的创建过程**,通过链式调用逐步配置组件。 - **在 Netty 中的应用**: `ServerBootstrap` 和 `Bootstrap` 类通过建造者模式初始化服务器/客户端。通过链式调用配置线程模型、通道类型、处理器链等,**避免冗长的构造函数**,提升可读性与灵活性[^1]。 ```java ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) // 配置线程池 .channel(NioServerSocketChannel.class) // 设置通道类型(工厂模式创建) .childHandler(new ChannelInitializer<SocketChannel>() { // 责任链初始化 @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new MyServerHandler()); // 添加处理器 } }); ChannelFuture f = b.bind(8080).sync(); // 绑定端口 ``` - **关键点**:`.channel(NioServerSocketChannel.class)` 隐式使用**工厂模式**创建 Channel 实例[^1]。 --- ### **2. 责任链模式(Chain of Responsibility)** - **作用**:**将事件处理逻辑拆分为多个独立处理器**,按链式顺序传递事件。 - **在 Netty 中的应用**: `ChannelPipeline` 是责任链的核心载体,处理器 (`ChannelHandler`) 以双向链表结构存储(引用[3])。事件(如数据读取)从 `head` 节点开始,依次传递至 `tail` 节点[^2][^3]。 **示例处理器**: ```java public class InBoundHandlerC extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("InBoundHandlerC: " + msg); ctx.fireChannelRead(msg); // 将事件传递到下一个处理器 } } ``` - **优势**: - 动态增删处理器(如加密、日志、业务逻辑解耦); - 事件通过 `fireChannelRead()` 自动传播(引用[2]),避免硬编码调用链[^2][^3]。 --- ### **3. 工厂模式(Factory Pattern)** - **作用**:**封装对象创建逻辑**,客户端无需关心具体实现类。 - **在 Netty 中的应用**: - **Channel 创建**:`.channel(NioServerSocketChannel.class)` 内部通过 `ReflectiveChannelFactory` 反射实例化 Channel 对象[^1]。 - **EventLoopGroup 创建**:`NioEventLoopGroup` 工厂生成事件循环线程组。 **自定义工厂示例**: ```java public class CustomChannelFactory implements ChannelFactory<Channel> { @Override public Channel newChannel() { return new CustomChannel(); // 返回自定义 Channel 实例 } } // 使用自定义工厂 b.channelFactory(new CustomChannelFactory()); ``` --- ### **4. 适配器模式(Adapter Pattern)** - **作用**:**简化接口实现**,为空方法提供默认实现,避免强制重写所有接口方法。 - **在 Netty 中的应用**: `ChannelInboundHandlerAdapter` 和 `ChannelOutboundHandlerAdapter` 是适配器类的典型代表。例如: ```java public class SimpleHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // 只需重写关注的方法,其他方法(如channelActive())使用默认空实现 } } ``` - **优势**:开发者可选择性重写事件方法(如 `channelRead()`),无需实现全部 `ChannelInboundHandler` 接口的数十个方法。 --- ### **模式协作关系** 1. **建造者模式**配置整体框架(线程池、通道类型); 2. **工厂模式**创建 Channel 实例; 3. **责任链模式**通过 `ChannelPipeline` 组织处理器; 4. **适配器模式**简化处理器开发。 ```mermaid graph LR A[ServerBootstrap] --建造者模式--> B[配置线程组/通道/处理器链] B --工厂模式--> C[创建NioServerSocketChannel] C --初始化--> D[ChannelPipeline] D --责任链模式--> E[添加Handler] E --适配器模式--> F[ChannelInboundHandlerAdapter] ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值