本章介绍

  • ChannelPipeline
  • ChannelHandlerContext
  • ChannelHandler
  • Inbound vs outbound(入站和出站)

接受连接或创建他们仅仅是你的应用程序的一部分,尽管这些不论什么非常重要,可是一个网络应用程序旺旺是更复杂的,须要很多其它的代码编写,如处理传入和传出的数据。Netty提供了一个强大的处理这些事情的功能,同意用户自己定义ChannelHandler的实现来处理数据。使得ChannelHandler更强大的是能够连接每一个ChannelHandler来实现任务,这有助于代码的整洁和重用。可是处理数据仅仅是ChannelHandler所做的事情之中的一个,也能够压制I/O操作,比如写请求。全部这些都能够动态实现。

6.1 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中的ChannelHandler;若是一个入站I/O事件,则会从最后一个開始依次通过ChannelPipeline中的ChannelHandler。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文档或源代码。

6.2 ChannelHandlerContext

        每一个ChannelHandler被加入到ChannelPipeline后,都会创建一个ChannelHandlerContext并与之创建的ChannelHandler关联绑定。ChannelHandlerContext同意ChannelHandler与其它的ChannelHandler实现进行交互,这是同样ChannelPipeline的一部分。ChannelHandlerContext不会改变加入到当中的ChannelHandler,因此它是安全的。

6.2.1 通知下一个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的通知:



        可能你想从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。下图显示了事件流:



如上图显示的,从指定的ChannelHandlerContext開始,跳过前面全部的ChannelHandler,使用ChannelHandlerContext操作是常见的模式,最经常使用的是从ChannelHanlder调用操作,也能够在外部使用ChannelHandlerContext,由于这是线程安全的。

6.2.2 改动ChannelPipeline

        调用ChannelHandlerContext的pipeline()方法能訪问ChannelPipeline,能在执行时动态的添加、删除、替换ChannelPipeline中的ChannelHandler。能够保持ChannelHandlerContext供以后使用,如外部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);
} }

上面是一个带@Sharable注解的Handler,它被多个线程使用时,里面count是不安全的,会导致count值错误。

        为什么要共享ChannelHandler?使用@Sharable注解共享一个ChannelHandler在一些需求中还是有非常好的作用的,如使用一个ChannelHandler来统计连接数或来处理一些全局数据等等。

6.3 状态模型

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

Channel的状态在其生命周期中变化,由于状态变化须要触发,下图显示了Channel状态变化:


        还能够看到额外的状态变化,由于用户同意从EventLoop中注销Channel暂停事件运行,然后再又一次注冊。在这样的情况下,你会看到多个channelRegistered和channelUnregistered状态的变化,而永远仅仅有一个channelActive和channelInactive的状态,由于一个通道在其生命周期内仅仅能连接一次,之后就会被回收;又一次连接,则是创建一个新的通道。
        下图显示了从EventLoop中注销Channel后再又一次注冊的状态变化:

6.4 ChannelHandler和其子类

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

6.4.1 ChannelHandler中的方法

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

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

6.4.2 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,不会自己主动释放接收的消息对象
* @author c.k
*
*/
public class DiscardHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//手动释放消息
ReferenceCountUtil.release(msg);
}
}
/**
* 继承SimpleChannelInboundHandler,会自己主动释放消息对象
* @author c.k
*
*/
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中。

6.4.3 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)来释放消息资源。一旦消息被传递到实际的通道,它会自己主动写入消息或在通道关闭是释放。


Netty In Action中文版 - 第六章:ChannelHandler的更多相关文章

  1. Netty In Action中文版 - 第四章:Transports(传输)

    本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) U ...

  2. Netty In Action中文版 - 第七章:编解码器Codec

    http://blog.csdn.net/abc_key/article/details/38041143 本章介绍 Codec,编解码器 Decoder,解码器 Encoder,编码器 Netty提 ...

  3. Netty In Action中文版 - 第五章:Buffers(缓冲)

    本章介绍 ByteBuf ByteBufHolder ByteBufAllocator 使用这些接口分配缓冲和运行操作 每当你须要数据传输时,它必须包括一个缓冲区.Java NIO API自带的缓冲区 ...

  4. Netty In Action中文版 - 第三章:Netty核心概念

            在这一章我们将讨论Netty的10个核心类.清楚了解他们的结构对使用Netty非常实用.可能有一些不会再工作中用到.可是也有一些非经常常使用也非常核心,你会遇到. Bootstrap ...

  5. Netty In Action中文版 - 第一章:Netty介绍

    本章介绍 Netty介绍 为什么要使用non-blocking IO(NIO) 堵塞IO(blocking IO)和非堵塞IO(non-blocking IO)对照 Java NIO的问题和在Nett ...

  6. Netty In Action中国版 - 第二章:第一Netty程序

    本章介绍 获得Netty4最新的版本号 设置执行环境,以构建和执行netty程序 创建一个基于Netty的server和client 拦截和处理异常 编制和执行Nettyserver和client 本 ...

  7. Netty In Action中文版 - 第十五章:选择正确的线程模型

    http://blog.csdn.net/abc_key/article/details/38419469 本章介绍 线程模型(thread-model) 事件循环(EventLoop) 并发(Con ...

  8. Learning Spark中文版--第六章--Spark高级编程(2)

    Working on a Per-Partition Basis(基于分区的操作) 以每个分区为基础处理数据使我们可以避免为每个数据项重做配置工作.如打开数据库连接或者创建随机数生成器这样的操作,我们 ...

  9. Learning Spark中文版--第六章--Spark高级编程(1)

    Introduction(介绍) 本章介绍了之前章节没有涵盖的高级Spark编程特性.我们介绍两种类型的共享变量:用来聚合信息的累加器和能有效分配较大值的广播变量.基于对RDD现有的transform ...

随机推荐

  1. 初识Visual Studio Code 一.使用Visual Studio Code 开发C# 控制台程序

    原文:初识Visual Studio Code 一.使用Visual Studio Code 开发C# 控制台程序 1. 安装.NET Core 安装包下载地址:https://www.microso ...

  2. [AngularJS] Using an AngularJS directive to hide the keyboard on submit

    Pleasea refer to Link <form ng-submit="foo()" handle-phone-submit> <input type=&q ...

  3. thinkphp3.2.3 excel导出,下载文件,包含图片

    关于导出后出错的问题 https://segmentfault.com/q/1010000005330214 https://blog.csdn.net/ohmygirl/article/detail ...

  4. dll = MinGW gcc 生成动态链接库 dll 的一些问题汇总

    MinGW gcc 生成动态链接库 dll 的一些问题汇总 https://blog.csdn.net/liyuanbhu/article/details/42612365 网络上关于用 MinGW  ...

  5. 解决gvim 8.1中zip插件打开zip文件内容时,而文件路径带有空格的问题。

    解决gvim 8.1中zip插件打开zip文件内容时,而文件路径带有空格的问题. 现象是只能打开一次,第二次打开就显示为空了. 通过 lcd切换工作目录.使得命令行操作中不再有带空格的路径 vim81 ...

  6. Spring web 工具类 WebApplicationContextUtils

    概述 Spring web 的工具类 WebApplicationContextUtils 位于包 org.springframework.web.context.support 是访问一个Servl ...

  7. 【solr基础教程之二】索引 分类: H4_SOLR/LUCENCE 2014-07-18 21:06 3331人阅读 评论(0) 收藏

    一.向Solr提交索引的方式 1.使用post.jar进行索引 (1)创建文档xml文件 <add> <doc> <field name="id"&g ...

  8. 【PHP】php 递归、效率和分析(转)

    递归的定义 递归(http:/en.wikipedia.org/wiki/Recursive)是一种函数调用自身(直接或间接)的一种机制,这种强大的思想可以把某些复杂的概念变得极为简单.在计算机科学之 ...

  9. php-post模拟登录,同步登录(摘自网络)

    这也是个老生常谈的话题了,上午花了点时间把这个问题整理了一下. 一般来说用PHP来模拟post提交数据有三种方法,file_get_contents.curl和socket. 写了个公用函数,专门用来 ...

  10. 数据序列化之protobuf

    数据序列化之protobuf 很多时候需要将一些数据打包,就是把这些数据搞在一起,方便处理.最常见的情况就是把需要传输的数据,当然数据不止一条,打包成一个消息,然后发送出去,接收端再以一定的规则接收并 ...