ChannelHandler

本文详细介绍了Netty中ChannelPipeline和ChannelHandler的使用方式,包括如何动态添加、删除、替换ChannelHandler,以及它们在I/O处理、事件通知、状态模型等方面的应用。此外,文章还讨论了ChannelHandlerContext的作用、不同类型的ChannelHandler(ChannelInboundHandler和ChannelOutboundHandler)及其方法,以及如何使用ChannelInitializer初始化ChannelHandler。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://blog.youkuaiyun.com/abc_key/article/details/37928441

     Netty提供了一个强大的处理这些事情的功能,允许用户自定义ChannelHandler的实现来处理数据。使得ChannelHandler更强大的是可以连接每个ChannelHandler来实现任务,这有助于代码的整洁和重用。但是处理数据只是ChannelHandler所做的事情之一,也可以处理I/O操作,例如写请求。所有这些都可以动态实现。

ChannelPipeline

       ChannelPipeline是ChannelHandler实例的列表,用于处理或截获通道的接收和发送数据。ChannelPipeline提供了一种高级的截取过滤器模式,让用户可以在ChannelPipeline中完全控制一个事件及如何处理ChannelHandler与ChannelPipeline的交互。
       对于每个新的通道,会创建一个新的ChannelPipeline并附加至该通道。一旦连接,Channel和ChannelPipeline之间的耦合是永久性的。Channel不能附加到其他的ChannelPipeline或从ChannelPipeline分离。
       下图描述了ChannelHandler在ChannelPipeline中的I/O处理,一个I/O操作可以由一个 ChannelInboundHandler或ChannelOutboundHandler进行处理,并通过调用 ChannelInboundHandler处理入站IO或通过ChannelOutboundHandler处理出站IO。

         
       如上图所示,ChannelPipeline是ChannelHandler的一个列表;如果一个入站I/O事件被触发,这个事件会从第一个开始依次通过ChannelPipeline中的ChannelInboundHandler;若是一个入站I/O事件,则会从最后一个开始依次通过ChannelPipeline中的ChannelOutboundHandler。ChannelHandler可以处理事件并检查类型,如果某个 ChannelHandler不能处理则会跳过,并将事件传递到下一个ChannelHandler。ChannelPipeline可以动态添加、删 除、替换其中的ChannelHandler,这样的机制可以提高灵活性。
       修改ChannelPipeline的方法:
       • addFirst(...),添加ChannelHandler在ChannelPipeline的第一个位置
       • addBefore(...),在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler
       • addAfter(...),在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler
       • addLast(ChannelHandler...),在ChannelPipeline的末尾添加ChannelHandler
       • remove(...),删除ChannelPipeline中指定的ChannelHandler
       • replace(...),替换ChannelPipeline中指定的ChannelHandler

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());
       被添加到ChannelPipeline的ChannelHandler将通过IO-Thread处理事件,这意味了必须不能有其他的IO- Thread阻塞来影响IO的整体处理;有时候可能需要阻塞,例如JDBC。因此,Netty允许通过添加一个EventExecutorGroup到每一个ChannelPipeline.add*方法,自定义的事件会被包含在EventExecutorGroup中的EventExecutor来处理,默认的实现是DefaultEventExecutorGroup。
       ChannelPipeline除了一些修改的方法,还有很多其他的方法,具体是方法及使用可以看API文档或源码。

ChannelHandlerContext

       每个ChannelHandler被添加到ChannelPipeline后,都会创建一个ChannelHandlerContext并与之创建的ChannelHandler关联绑定。ChannelHandlerContext允许ChannelHandler与其他的ChannelHandler实现进行交互,这是相同ChannelPipeline的一部分。ChannelHandlerContext不会改变添加到其中的ChannelHandler,因此它是安全的。
       在相同的ChannelPipeline中通过调用ChannelInboundHandler和ChannelOutboundHandler中各个方法中的一个方法来通知最近的handler,通知开始的地方取决如何设置。下图显示了ChannelHandlerContext、 ChannelHandler、ChannelPipeline的关系:

         
       如果你想有一些事件流全部通过ChannelPipeline,有两个不同的方法可以做到:
       • 调用Channel的方法
       • 调用ChannelPipeline的方法
       这两个方法都可以让事件流全部通过ChannelPipeline。无论从头部还是尾部开始,因为它主要依赖于事件的性质。如果是一个“入站”事件,它开始于头部;若是一个“出站”事件,则开始于尾部。
       下面的代码显示了一个写事件如何通过ChannelPipeline从尾部开始:

@Override
protected void initChannel(SocketChannel ch) throws Exception {
	ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
		@Override
		public void channelActive(ChannelHandlerContext ctx) throws Exception {
			//Event via Channel
			Channel channel = ctx.channel();
			channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
			//Event via ChannelPipeline
			ChannelPipeline pipeline = ctx.pipeline();
			pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
		}
	});
}
       下图表示通过Channel或ChannelPipeline的通知:
         

状态模型

       Netty有一个简单但强大的状态模型,并完美映射到ChannelInboundHandler的各个方法。下面是Channel生命周期四个不同的状态:
       • channelUnregistered
       • channelRegistered
       • channelActive
       • channelInactive

         
       还可以看到额外的状态变化,因为用户允许从EventLoop中注销Channel暂停事件执行,然后再重新注册。在这种情况下,你会看到多个 channelRegistered和channelUnregistered状态的变化,而永远只有一个channelActive和 channelInactive的状态,因为一个通道在其生命周期内只能连接一次,之后就会被回收;重新连接,则是创建一个新的通道。
       下图显示了从EventLoop中注销Channel后再重新注册的状态变化:

         

ChannelHandler和其子类

       Netty中有3个实现了ChannelHandler接口的类,其中2个是接口,一个是抽象类。如下图:
         

ChannelHandler中的方法

       Netty定义了良好的类型层次结构来表示不同的处理程序类型,所有的类型的父类是ChannelHandler。ChannelHandler提供了在其生命周期内添加或从ChannelPipeline中删除的方法。
       • handlerAdded,ChannelHandler添加到实际上下文中准备处理事件
       • handlerRemoved,将ChannelHandler从实际上下文中删除,不再处理事件
       • exceptionCaught,处理抛出的异常

      上面三个方法都需要传递ChannelHandlerContext参数,每个ChannelHandler被添加到ChannelPipeline时会自动创建ChannelHandlerContext。ChannelHandlerContext允许在本地通道安全的存储和检索值。Netty还提供了一个实现了ChannelHandler的抽象类:ChannelHandlerAdapter。ChannelHandlerAdapter实现了父类的所有方法,基本上就是传递事件到ChannelPipeline中的下一个ChannelHandler直到结束。


ChannelInboundHandler
      
ChannelInboundHandler提供了一些方法再接收数据或Channel状态改变时被调用。下面是ChannelInboundHandler的一些方法:
       • channelRegistered,ChannelHandlerContext的Channel被注册到EventLoop;
       • channelUnregistered,ChannelHandlerContext的Channel从EventLoop中注销
       • channelActive,ChannelHandlerContext的Channel已激活
       • channelInactive,ChannelHanderContxt的Channel结束生命周期
       • channelRead,从当前Channel的对端读取消息
       • channelReadComplete,消息读取完成后执行
       • userEventTriggered,一个用户事件被处罚
       • channelWritabilityChanged,改变通道的可写状态,可以使用Channel.isWritable()检查
       • exceptionCaught,重写父类ChannelHandler的方法,处理异常
       Netty提供了一个实现了ChannelInboundHandler接口并继承ChannelHandlerAdapter的 类:ChannelInboundHandlerAdapter。ChannelInboundHandlerAdapter实现了 ChannelInboundHandler的所有方法,作用就是处理消息并将消息转发到ChannelPipeline中的下一个 ChannelHandler。ChannelInboundHandlerAdapter的channelRead方法处理完消息后不会自动释放消息,若想自动释放收到的消息,可以使用SimpleChannelInboundHandler<I>。

/**
 * 实现ChannelInboundHandlerAdapter的Handler,不会自动释放接收的消息对象
 */
public class DiscardHandler extends ChannelInboundHandlerAdapter {
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		//手动释放消息
		ReferenceCountUtil.release(msg);
	}
}
/**
 * 继承SimpleChannelInboundHandler,会自动释放消息对象
 */
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
		//不需要手动释放
	}
}
       如果需要其他状态改变的通知,可以重写Handler的其他方法。通常自定义消息类型来解码字节,可以实现 ChannelInboundHandler或ChannelInboundHandlerAdapter。有一个更好的解决方法,使用编解码器的框架可以很容的实现。使用ChannelInboundHandler、ChannelInboundHandlerAdapter、 SimpleChannelInboundhandler这三个中的一个来处理接收消息,使用哪一个取决于需求;大多数时候使用 SimpleChannelInboundHandler处理消息,使用ChannelInboundHandlerAdapter处理其他的“入站”事件或状态改变。
       ChannelInitializer用来初始化ChannelHandler,将自定义的各种ChannelHandler添加到ChannelPipeline中。


ChannelOutboundHandler
       ChannelOutboundHandler用来处理“出站”的数据消息。ChannelOutboundHandler提供了下面一些方法:
       • bind,Channel绑定本地地址
       • connect,Channel连接操作
       • disconnect,Channel断开连接
       • close,关闭Channel
       • deregister,注销Channel
       • read,读取消息,实际是截获ChannelHandlerContext.read()
       • write,写操作,实际是通过ChannelPipeline写消息,Channel.flush()属性到实际通道
       • flush,刷新消息到通道
       ChannelOutboundHandler是ChannelHandler的子类,实现了ChannelHandler的所有方法。所有最重要的方法采取ChannelPromise,因此一旦请求停止从ChannelPipeline转发参数则必须得到通知。Netty提供了ChannelOutboundHandler的实现:ChannelOutboundHandlerAdapter。 ChannelOutboundHandlerAdapter实现了父类的所有方法,并且可以根据需要重写感兴趣的方法。所有这些方法的实现,在默认情况下,都是通过调用ChannelHandlerContext的方法将事件转发到ChannelPipeline中下一个ChannelHandler。

public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {
	@Override
	public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
		ReferenceCountUtil.release(msg);
		promise.setSuccess();
	}
}
       记得释放资源并通知ChannelPromise,若ChannelPromise没有被通知可能会导致其中一个ChannelFutureListener不被通知去处理一个消息。
       如果消息被消费并且没有被传递到ChannelPipeline中的下一个ChannelOutboundHandler,那么就需要调用ReferenceCountUtil.release(message)来释放消息资源。一旦消息被传递到实际的通道,它会自动写入消息或在通道关闭是释放。

<think>我们正在讨论Netty中的ChannelHandler。根据引用[1],ChannelHandler是Netty中处理事件的核心组件,负责处理入站和出站数据流。引用[5]明确指出,ChannelHandler有两个子接口:ChannelInboundHandler和ChannelOutboundHandler。 因此,直接回答用户的问题:ChannelHandler的两个子接口是**ChannelInboundHandler**(用于处理入站事件)和**ChannelOutboundHandler**(用于处理出站事件)。 为了更清晰地展示,我们可以用以下格式: ### ChannelHandler的两个子接口 1. **ChannelInboundHandler** 负责处理**入站(Inbound)**事件,例如: - 通道激活(channelActive) - 通道读取(channelRead) - 异常捕获(exceptionCaught) 2. **ChannelOutboundHandler** 负责处理**出站(Outbound)**事件,例如: - 连接操作(connect) - 写入操作(write) - 刷新操作(flush) 引用[5]还提到,这两个子接口分别通过`ChannelInboundHandlerAdapter`和`ChannelOutboundHandlerAdapter`提供默认实现,以便事件能自动在管道(Pipeline)中传播[^5]。 ### 相关问题 1. 在实际开发中,如何正确使用`ChannelInboundHandler`和`ChannelOutboundHandler`? 2. `ChannelPipeline`中如何协调多个入站和出站处理器? 3. 为什么Netty推荐使用适配器类(如`ChannelInboundHandlerAdapter`)而不是直接实现接口? 4. 处理入站和出站事件时有哪些常见的资源管理陷阱?</think>### ChannelHandler 的两个核心子接口 在 Netty 的网络编程框架中,`ChannelHandler` 作为事件处理的核心组件,其两个关键子接口是: 1. **ChannelInboundHandler** 📥 负责处理**入站(Inbound)事件**,如数据接收、连接建立等。主要方法包括: ```java void channelRead(ChannelHandlerContext ctx, Object msg); // 读取数据 void channelActive(ChannelHandlerContext ctx); // 连接激活 void exceptionCaught(ChannelHandlerContext ctx, Throwable cause); // 异常处理 ``` **典型应用场景**: - 解码网络字节流为业务对象 - 响应客户端连接请求 - 处理接收到的业务数据[^1][^5] 2. **ChannelOutboundHandler** 📤 负责处理**出站(Outbound)事件**,如数据发送、连接关闭等。核心方法: ```java void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise); // 发送数据 void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, ChannelPromise promise); // 建立连接 void close(ChannelHandlerContext ctx, ChannelPromise promise); // 关闭连接 ``` **关键特性**: - 所有方法都包含 `ChannelPromise` 参数,用于异步回调通知操作结果[^2] - 需手动管理资源释放(如调用 `ReferenceCountUtil.release()`)[^1] --- ### 设计关系图解 ```mermaid classDiagram ChannelHandler <|-- ChannelInboundHandler ChannelHandler <|-- ChannelOutboundHandler ChannelInboundHandler <|.. ChannelInboundHandlerAdapter ChannelOutboundHandler <|.. ChannelOutboundHandlerAdapter class ChannelHandler { <<interface>> } class ChannelInboundHandler { <<interface>> +channelRead() +channelActive() } class ChannelOutboundHandler { <<interface>> +write(ChannelPromise) +connect(ChannelPromise) } ``` --- ### 实际开发建议 1. **避免直接实现接口** 推荐继承适配器类(如 `ChannelInboundHandlerAdapter`),它已实现事件的默认传播逻辑: ```java // 入站事件自动传播(ChannelInboundHandlerAdapter源码示例) public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.fireChannelRead(msg); // 自动传递到下一个Handler } ``` 这样无需手动编写事件传递代码[^3][^5]。 2. **资源管理原则** - 入站路径:使用 `SimpleChannelInboundHandler` 可自动释放资源 - 出站路径:必须在 `write()` 方法中手动释放原始对象[^1] --- ### 相关问题 1. 为什么 `ChannelOutboundHandler` 的方法都需要 `ChannelPromise` 参数?它和 `ChannelFuture` 有何区别?[^2] 2. 如何在同一个 `ChannelPipeline` 中协调多个入站和出站处理器?[^4] 3. 使用 `ChannelInboundHandlerAdapter` 时,如果不想将事件传递给下一个处理器,应该如何中断传播? 4. 在出站操作中,`ChannelPromise` 的 `setSuccess()` 和 `setFailure()` 方法分别应该在什么场景调用? 5. 为什么 Netty 推荐使用适配器类而非直接实现接口?这种设计有什么优势?[^3][^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值