Netty之ChannelHandler以及状态模型介绍

   接受连接或创建连接只是IO应用程序的一部分,虽然这些很重要,但是一个网络应用程序往往是更复杂的需要更多的代码编写的地方是处理传入和传出的数据。

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。
    Netty之ChannelHandler以及状态模型介绍_第1张图片
    如上图所示,ChannelPipeline 是 ChannelHandler 的一个列表;如果一个入站 I/O 事件被触发,这个事件会从第一个开始依次通过 ChannelPipeline 中的 ChannelHandler;若是一个出站 I/O 事件,则会从最后一个开始依次通过 ChannelPipeline 中的 ChannelHandler。 ChannelHandler可以处理事件并检查类型,如果某个 ChannelHandler 不能处理则会跳过,并将事件传递到下一个 ChannelHandler。ChannelPipeline 可以动态添加、删除、替换其中的 ChannelHandler,这样的机制可以提高灵活性。
     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 的关系:
    Netty之ChannelHandler以及状态模型介绍_第2张图片
    如果你想有一些事件流全部通过 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", CharsetU til.UTF_8));
     }
   });
 } 

下图表示通过 Channel 或 ChannelPipeline 的通知:
Netty之ChannelHandler以及状态模型介绍_第3张图片
可能你想从 ChannelPipeline 的指定位置开始,不想流经整个ChannelPipeline,如下情况:
 为了节省开销,不感兴趣的 ChannelHandler 不让通过
 排除一些 ChannelHandler 在这种情况下,你可以使用 ChannelHandlerContext 的 ChannelHandler 通知起点。它使用ChannelHandlerContext 执行下一个 ChannelHandler。
下面代码显示了直接使用ChannelHandlerContext 操作:

   // Get reference of ChannelHandlerContext
   ChannelHandlerContext ctx =  ..; 
 // Write buffer via ChannelHandlerContext
 ctx.write(Unpooled.copiedBuffer("Netty in Action",
      CharsetUtil.UTF_8)); 

该消息流经 ChannelPipeline 到下一个ChannelHandler,在这种情况下使用ChannelHandlerContext 开始下一个ChannelHandler。
下图显示了事件流:
Netty之ChannelHandler以及状态模型介绍_第4张图片
如上图显示的, 从指定的 ChannelHandlerContext 开始, 跳过前面所有的 ChannelHandler,使用ChannelHandlerContext操作是常见的模式,
最常用的是从ChannelHanlder调用操作,也可以在外部使用ChannelHandlerContext,因为这是线程安全的。
修改 ChannelPipeline 调用 ChannelHandlerContext 的 pipeline()方法能访问 ChannelPipeline, 能在运行时动态的增加、删除、替换 ChannelPipeline 中的 ChannelHandler。可以保持hannelHandlerContext 供以后使用,如外部 Handler 方法触发一个事件,甚至从一个不同的线程。 下面代码显示了保存 ChannelHandlerContext 供之后使用或其他线程使用:

 public class WriteHandler extends ChannelHandlerAdapter {
    private ChannelHandlerContext ctx;
    @Override 
    public void handlerAdded(ChannelHandlerContext ctx) throws
   Exception {
              this.ctx = ctx;
             }

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

请注意,ChannelHandler 实例如果带有@Sharable 注解则可以被添加到多个ChannelPipeline。 也就是说单个 ChannelHandler 实例可以有多个 ChannelHandlerContext,因此可以调用不同 ChannelHandlerContext 获取同一个 ChannelHandler。如果添加不带@Sharable 注解的 ChannelHandler 实例到多个 ChannelPipeline 则会抛出异常;使用@Sharable 注解后的 ChannelHandler 必须在不同的线程和不同的通道上安全使用。 怎么是不安全的使用?看下面代码:

@Sharable 
public class NotSharableHandler extends ChannelInboundHandlerAdapter {
private int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
 count++;
System.out.println("channelRead(...)called the " + count + "time“") ;
ctx.fireChannelRead(msg);
          }
}

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

  • channelUnregistered
  • channelRegistered
  • channelActive
  • channelInactive

Channel 的状态在其生命周期中变化,因为状态变化需要触发,下图显示了 Channel 状态变化:

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

你可能感兴趣的:(netty,传出,channelhan,状态模型,传入)