Netty中的Channel、ChannelHandler、ChannelHandlerContext以及ChannelPipeline(常见的调用顺序,消息的传递)

本文深入解析Netty中的消息传递流程,包括Channel的生命周期、ChannelHandler的分类与使用,以及ChannelInboundHandler和ChannelOutboundHandler的区别。通过实例展示了如何在不同的Handler中处理入站和出站事件,强调了资源释放的重要性。同时介绍了ChannelHandlerContext和ChannelPipeline的作用,阐述了它们在事件传播和处理器链中的角色。


原博文,点击这里

调用顺序

代码:NettyPro中protocoltcp模块:Git HUb地址,点击这里
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
这里的L:表示Local
R:表示为Remote

消息的传递

编解码

对于编解码器使用的是out,进行消息的传递

Handler

对于Handler使用的是:
1.ChannelHandlerContext#fireChannelActive()传递到下一个处理器
2.ctx.writeAndFlush与ctx.channel().writeAndFlush传递到出站处理器。

1.Channel

定义:一个Channel表示一个通道,跟io中的stream的角色一样,但是有几点不同。 1. stream是单向的,只支持读或者写,channel是双向的,既支持读也支持写。2. stream是阻塞的,channel是并行的。

其中Channel的生命周期状态如下:

状态说明说明
channelUnregisteredchannel创建之后,还未注册到EventLoop
channelRegisteredchannel注册到了对应的EventLoop
channelActivechannel处于活跃状态,活跃状态表示已经连接到了远程服务器,现在可以接收和发送数据
channelInactivechannel未连接到远程服务器

一个Channel正常的生命周期如下:

channelRegistered -> channelActice -> channelInactive -> channelUnregistered

在另外一种特殊情况下,会发生多次channelRegistered和channelUnregistered,这是因为用户可以从EventLoop上取消注册Channel来阻止事件的执行并在之后重新注册。状态变化如下:

2.ChannelHandler

ChannelHandler有2种类型:

1.Inbound Handler: 处理inbound数据(接收到的数据)以及所有类型的channel状态修改事件
2.Outbound Handler: 处理outbound数据(发送出去的数据)并且可以拦截各种操作,比如bind、connect、disconnect、close、write等操作
Inbound和Outbound Handler都属于ChannelHandler,它们都可以被添加到ChannelPipeline中,它们内部也提供了handlerAdded、handlerRemoved这两种方法分别在ChannelHandler添加到ChannelPipeline和ChannelHandler从ChannelPipeline中被删除的时候触发。

2.1ChannelInboundHandler

ChannelInboundHandler方法在两种情况下触发:channel状态的改变和channel接收到数据。

一些方法说明:

方法名描述
channelRegistered(…)Channel注册到EventLoop,并且可以处理IO请求
channelUnregistered(…)Channel从EventLoop中被取消注册,并且不能处理任何IO请求
channelActive(…)Channel已经连接到远程服务器,并准备好了接收数据
channelInactive(…)Channel还没有连接到远程服务器
channelReadComplete(…)Channel的读取操作已经完成
channelRead(…)有数据可以读取的时候触发
userEventTriggered(…)当用户调用Channel.fireUserEventTriggered方法的时候触发,用户可以传递一个自定义的对象当这个方法里

ChannelInboundHandler有一个实现ChannelInboundHandlerAdapter,它实现了所有的方法,我们只需要继承这个类然后复写需要的方法即可。

ChannelInboundHandler中的channelRead方法中有读取的ByteBuf。由于Netty在ByteBuf的使用上使用了池的概念,当不需要这个ByteBuf的时候需要进行资源的释放以减少内存的消耗。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
      // do something
      ReferenceCountUtil.release(msg);
}

Netty内部提供了一个SimpleChannelInboundHandler类,这个类读取数据会自动释放资源。它继承ChannelInboundHandlerAdapter并复写了channelRead方法,在channelRead方法里面finally代码里会自动release资源,并提供了channelRead0方法:

@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) {
      // do something, do not need release
}

2.2选择和使用

所以一般使用ChannelInboundHandler的话,有3种方法。

直接实现ChannelInBoundHandler接口
继承ChannelInboundHandlerAdapter
继承SimpleChannelInboundHandler
第1种基本不用,第3种用来处理接收消息,第2种用来处理事件状态的改变

3.ChannelOutboundHandler

ChannelOutboundHandler方法在两种情况下触发:发送数据和拦截操作。

ChannelOutboundHandler有一个强大的功能,可以按需推迟一个操作,这使得处理请求可以用到更为复杂的策略。比如,如果写数据到远端被暂停,你可以推迟flush操作,稍后再试。

一些方法说明:

方法名描述
bind(…)请求绑定channel到本地地址
connect(…)channel连接到远程地址
disconnect(…)channel从远程服务器上断开
close(…)关闭channel
deregister(…)取消channel在eventloop上的注册
read(…)在channel中读数据
flush(…)flush数据到远程服务器
write(…)写数据到远程服务器

ChannelOutboundHandler有一个实现ChannelOutboundHandlerAdapter,它实现了所有的方法,我们只需要继承这个类然后复写需要的方法即可。

在outboundhandler中有时候也需要释放资源,当消息被消费并且不再需要传递给下一个outbound handler的时候,调用ReferenceCountUtil.release(message)释放消息。

当消息被写回去或者channel关闭的时候,这个消息的资源会被自动释放,所以没有一个类似SimpleChannelInboundHandler的概念。

ChannelHandlerContext

当ChannelHandler被添加到ChannelPipeline中的时候,ChannelHandlerContext会被创建。
所以说ChannelHandlerContext属于ChannelHandler。

可以通过ChannelHandlerContext的channel方法得到Channel和pipeline方法得到ChannelPipeline。

ChannelHandlerContext可以被保留下来并且在其他地方进行调用,比如在其他线程,或者在handler外部进行调用。

可以使用以下方法保留ChannelHandlerContext:

class WriterHandler extends ChannelHandlerAdapter {
  private ChannelHandlerContext ctx;

  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
      this.ctx = ctx;
  }

  public void send(String msg) {
      ctx.write(msg);
  }
}

Netty中提供了一个@Sharable注解用来将一个实例的ChannelHandler添加到多个ChannelPipeline中,如果不加上这个注解,被多个ChannelPipeline使用的话会抛出异常。

ChannelPipeline

多个ChannelHandler可以组成一个ChannelPipeline,里面的每个ChannelHandler可以转发给下一个ChannelHandler。

ChannelPipeline内部的所有ChannelHandler的处理流程图:

ChannelPipeline提供了多种方法用于添加或删除或代替ChannelHandler,比如addLast, addFirst, remove, replace等方法。

### Netty中Pipeline的双向链表调用顺序及Handler进出站原理 Netty中的`ChannelPipeline`是一个双向链表结构,每个`Channel`都有且仅有一个与之对应的`ChannelPipeline`[^1]。这个双向链表由多个`ChannelHandlerContext`组成,每个`ChannelHandlerContext`关联着一个`ChannelHandler`。链表的头节点称为`HeadContext`,尾节点称为`TailContext`。 #### 1. 双向链表的基本结构 `ChannelPipeline`通过维护一个双向链表来管理多个`ChannelHandler`实例。链表中的每个节点(即`ChannelHandlerContext`)都包含以下关键属性: - `next`:指向下一个`ChannelHandlerContext`。 - `prev`:指向前一个`ChannelHandlerContext`。 - `handler`:当前上下文中关联的`ChannelHandler`。 这种结构使得事件可以在链表中以串行化的方式流动,确保每个`ChannelHandler`都能按照预定顺序处理事件。 #### 2. Handler的进出站原理 Netty将事件分为两类:入站事件(Inbound)和出站事件(Outbound)。这两种事件在`ChannelPipeline`中的流动方向不同。 - **入站事件(Inbound)**: 入站事件从链表头部开始流动,依次经过每个`ChannelHandlerContext`,直到到达链表尾部。这些事件通常用于处理接收到的数据,例如解码、验证等操作。入站事件的典型方法包括`channelRead()`、`exceptionCaught()`等[^1]。 - **出站事件(Outbound)**: 出站事件从链表尾部开始流动,依次经过每个`ChannelHandlerContext`,直到到达链表头部。这些事件通常用于处理发送到网络的数据,例如编码、压缩等操作。出站事件的典型方法包括`write()`、`flush()`等[^1]。 #### 3. 调用顺序的具体规则 `ChannelPipeline`中的调用顺序严格遵循链表的方向。对于入站事件,调用顺序是从`HeadContext`到`TailContext`;对于出站事件,调用顺序是从`TailContext`到`HeadContext`。 当一个`ChannelHandler`处理完事件后,可以通过以下两种方式将事件传递给下一个`ChannelHandler`: - **`ctx.fireXXX()`**:触发入站事件,事件会沿着链表向后传递(从`HeadContext`到`TailContext`)。 - **`ctx.writeAndFlush()`**:触发出站事件,事件会沿着链表向前传递(从`TailContext`到`HeadContext`)[^5]。 需要注意的是,`ctx.writeAndFlush()`和`ctx.channel().writeAndFlush()`的行为有所不同。前者会从当前`ChannelHandlerContext`的位置开始向前传递事件,而后者会从链表尾部开始向前传递事件[^3]。 #### 4. 代码示例 以下是一个简单的代码示例,展示了如何在`ChannelPipeline`中添加`ChannelHandler`并实现事件的进出站处理: ```java public class MyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 添加入站处理器 pipeline.addLast(new InboundHandlerA()); pipeline.addLast(new InboundHandlerB()); // 添加出站处理器 pipeline.addLast(new OutboundHandlerA()); pipeline.addLast(new OutboundHandlerB()); } } // 入站处理器示例 public class InboundHandlerA extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("InboundHandlerA: " + msg); super.channelRead(ctx, msg); // 将事件传递给下一个处理器 } } // 出站处理器示例 public class OutboundHandlerA extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { System.out.println("OutboundHandlerA: " + msg); super.write(ctx, msg, promise); // 将事件传递给下一个处理器 } } ``` #### 5. 性能优化点 Netty通过`mask`机制跳过未实现特定方法的`ChannelHandler`,从而减少不必要的调用,提高性能[^4]。例如,如果某个`ChannelHandler`未实现`channelRead()`方法,则该方法不会被调用,事件会直接传递给下一个`ChannelHandler`。 ### 总结 Netty的`ChannelPipeline`通过双向链表结构管理多个`ChannelHandler`,并定义了事件的进出站流动规则。入站事件从链表头部向尾部流动,而出站事件从链表尾部向头部流动。这种设计确保了事件能够按照预定顺序被处理,并支持灵活的编解码和业务逻辑处理。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值