Netty in Action (十五) 第六章节 第一部分 ChannelHandler和ChannelPipeline

本章内容包括:

1)ChannelHandler和ChannelPipeline的APIs

2)检测内存泄漏

3)异常处理

在之前的一个章节中,我们学习了ByteBuf,Netty的数据容器,在这个章节中,我们将讲解Netty的数据流和对应的处理组件,然后我们将我们已经学过的所有组件整合在一起

你已经知道多个ChannelHandler可以被链式的放入ChannelPipeline来将所有的处理逻辑组织在一起,我们将学习包涵这些有关类的很多用户案例和他们之间的对应关系------ChannelHandlerContext

了解这些组件之间是如何交互的对构建多模块可重复利用的Netty项目是至关重要的

6.1 The ChannelHandler family

准备详细地学习ChannelHandler之前,我们将花一些时间理解一下这个Netty组件模型的基础知识

6.1.1 The Channel lifecycle

接口Channel定义了一些简单但是有用的状态模型,它与ChannelInboundHandler的API紧密关联,下表6.1展示了Channel的四种状态

状态 描述
ChannelUnregistered Channel已经创建,但是还没有注册到EcentLoop上
ChannelRegistered Channel已经注册到EventLoop
ChannelActive Channel已经激活了(已经连接到远程端),现在它已经准备好接受和发送信息
ChannelInactive Channel没有连接到远程端

Channel的一个正常的生命周期是如图6.1展示的,一旦一个状态发生了改变,相对应的事件将会产生,然后会转向到ChannelPipeline中的ChannelHandler中,然后Channel会对事件进行处理

6.1.2 The ChannelHandler lifecycle

接口ChannelHandler定义了操作的生命周期,如下表6.2展示,这些操作将会在ChannelHandler加入或者移除ChannelPipeline的时候被调用,每一个方法接收一个ChannelHandlerContext的入参

类型 描述
handlerAdded 当一个ChannelHandler被载入ChannelPipeline的时候触发
handlerRemoved 当一个ChannelHandler从ChannelPipeline中移除的时候触发
exceptionCaught 在处理过程中ChannelPipeline发生了异常

Netty定义了ChannelHandler两个比较重要的子接口

1)ChannelInboundHandler------处理输入的数据且处理一切状态的改变

2)ChannelOutboundHandler--------处理输出数据,运行拦截一切的操作

在下一个小节中,我们将进行详细的讲解

6.1.3 Interface ChannelInboundHandler

表6.3展示了在整个ChannelInboundHandler的生命周期里的所有方法,当接收到数据或者相关联的Channel状态改变的时候会调用这些方法,与我们之前提及的一样,这些方法与Channel的生命周期是一一映射对应的

类型 描述
channelRegistered 当一个Channel被注册到EventLoop上的时候并且能够处理IO的时候调用执行
channelUnregistered 当一个Channel从EventLoop中注销的时候且不能再处理I/O的时候调用执行
channelActive 当一个Channel被激活是调用执行
channelInactive 当一个Channel已经处于非激活的状态且不再连接到远程端的时候被调用执行
channelReadComplete 当一个Channel的读操作已经准备好的时候被调用执行
channelRead 当数据已经从Channel读取的时候执行
channelWritabilityChanged 当一个Channel的可写的状态发生改变的时候执行,用户可以保证写的操作不要太快,这样可以防止OOM,写的太快容易发生OOM,如果当发现Channel变得再次可写之后重新恢复写入的操作,Channel中的isWritable方法可以监控该channel的可写状态,可写状态的阀门直接通过Channel.config().setWriterHighWaterMark()和Channel.config().setWriteLowWaterMark()配置
userEventTriggered 当ChannelInboundHandler的fireUserEventTriggered被调用的时候执行,因为一个POJO对象传输通过了ChannelPipeline

当一个ChannelInboundHandler具体实现重写了channelRead方法的时候,那么它需要负责显式的去释放池对象的ByteBuf实例相关联的内存空间,Netty提供了一个特别的方法来达到这个目的,ReferenceCountUtil.release(),如下面的代码清单所示:

Netty用WARN级别记录没有释放的资源,这样可以很方便的找出代码中有问题的实例,但是这样管控资源显得有些麻烦,一个简单的备选方案是使用SimpleChannelInboundHandler,下面的代码清单向你展示了这种改变

因为SimpleChannelInboundHandler这个对象可以自动的释放资源,那么你不能够获取任何信息的引用供以后使用,这将会是无效不合法的

小节6.1.6提供了引用处理的详细讲解

6.1.4 Interface
ChannelOutboundHandler

输出操作和数据处理是由ChannelOutboundHandler管控的,它的方法将由Channel,ChannelPipeline,ChannelHandlerContext调用执行

一个ChannelOutboundHandler强大有用的功能之一是可以推迟一个操作和事件的执行,它允许复杂的事件来请求处理,如果一个写数据到远程端的过程被挂起,你可以推迟flush的操作然后在之后的时间重新进行断点续传

ChannelOutboundHandler自己的一些本地的方法如6.4所示

类型 描述
bind(ChannelHandlerContext,

SocketAddress,ChannelPromise)

绑定到本地的地址的channel的请求被执行
connect(ChannelHandlerContext,

SocketAddress,SocketAddress,ChannelPromise)

连接到远程端的channel的请求被执行
disconnect(ChannelHandlerContext,

ChannelPromise)

当从远程端停止连接的时候执行
close(ChannelHandlerContext,ChannelPromise) 请求关闭channel的时候执行
deregister(ChannelHandlerContext,

ChannelPromise)

请求当channel从EventLoop上注销的时候执行
read(ChannelHandlerContext) 请求从channel中读取更多信息的时候执行
flush(ChannelHandlerContext) 当从channel刷入队列信息到远程端的时候执行
write(ChannelHandlerContext,Object,

ChannelPromise)

当从channel中写数据到远程端的时候执行

CHANNELPROMISE VS. CHANNELFUTURE ChannelOutboundHandler的很多方法用ChannelPromise作为参数来通知操作的完成,ChannelPromise作为ChannelFuture的子类来定义了一些写的方法,例如setSuccess和setFailure方法,这样可以使ChannelFuture看起来不变

接下来,我们看看一些类,这些类可以简化ChannelHandler的写入操作

6.1.5 ChannelHandler adapters

你可以使用ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter作为你写你自己的ChannelHandler的起点,这些适配的类分别为ChannelInboundHandler和ChannelOutboundHandler类提供了一些基本的实现,通过继承抽象类ChannelHandlerAdapter,我们可以获取它的父接口ChannelHandler一些常用的方法的具体实现,继承图如图6.2所示:

ChannelHandlerAdapter还提供一个特殊的方法isSharable的方法,如果该接口的具体实现以“Sharable”注解的话,那么将会返回true,这就表示这个实现可以被加入到多个不同的ChannelPipeline中去

ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter提供的方法体与ChannelHandlerContext中的方法是对等的,因此都会将事件转发至管道中的下一个ChannelHandler中

在你自己的处理器中使用这些适配类,可以很方便的扩展他们并且可以自定义重写父类方法

6.1.6 Resource management

当你通过调用ChannelInboundHandler.channelRead()或者ChannelOutboundHandler.write()这些方法来操作数据的时候,你都需要保证没有资源泄漏,应该还记得上一个章节讲解的知识,Netty使用引用计数来处理池化的ByteBuf,所以在你已经完成对ByteBuf使用的时候,修改引用计数的值是很重要的

为了帮助你诊断这些潜在的问题,Netty提供了一个叫做ResourceLeakDetector的类,这个类将会对你的引用中1%的buffer分配进行取样来分析你的内存泄漏,当然这样做的内存损耗也是很小的,可以接受的

如果诊断出游内存损耗的话,那么记录的日记信息应该和下面打印的信息有些类似

目前Netty定义了泄漏级别,在表6.5中展示

等级 描述
DISABLED 禁止使用内存检测,只有经过全面的测试才能使用
SIMPLE 使用默认的1%的测试样例来记录任何的使用泄漏的情况,使用这种默认的级别对于大多数的案例来说是一个不错的选择
ADVANCED 报告发现泄漏的情况且找出该信息所在的位置,依旧使用默认的测试样例比例
PARANOID 与ADVANCED一样,除了它将所有的buffer作为测试样例,这对性能有很大的负担,这应该用在测试debug阶段

泄漏检查等级可以在下面的java系统的变量中设置,设置的值就是上面表的任意属性

如果设置好相关的JVM参数后重新启动你的应用的话,你将会检测到你应用最近buffer内存泄漏的地方,一下是一个通过单元测试后经典的泄漏情景

当你实现ChannelInboundHandler.channelRead() 和ChannelOutboundHandler.write()的时候,你如何使用这个诊断工具来防止你内存泄漏,让我们举一个案例,当你使用channelRead来消费输入的信息的时候,通过调用ChannelHandlerContext类的fireChannelRead使其信息不通过下一个ChannelInboundHandler方法,代码清单向你展示了如何释放信息

CONSUMING
INBOUND MESSAGES THE EASY WAY 因为消费一个输入信息然后释放这个信息所占用的内存是一个很常见的任务,Netty提供了一个实现ChannelInboundHandler的特殊类被叫做SimpleChannelInboundHandler,这个可以自动释放一个信息一旦这个信息被channelRead0消费

释放资源重要但是同样通知ChannelPromise也同样重要,否则会出现一种场景,当该信息已经被释放了,但是ChannelFutureListener却并没有被通知到

总的来说,如果一个信息被消费然后被丢弃并没有通过ChannelPipeline中的下一个ChannelOutboundHandler的时候,用户应该调用ReferenceCountUtil.release(),如果一个信息到达真正的传输层的时候,当它被写入的时候或者channel关闭的时候,它能够自动释放

6.2
Interface ChannelPipeline

如果你将ChannelPipeline想象成ChannelHandler链式的形态用来与通过Channel的输入输出的数据交互的话,那么你将很容易的轻易理解将这些ChannelHandler联合在一起组成了应用程序数据业务逻辑的核心块

每一个新的Channel被创建的时候都会被分配给一个新的ChannelPipeline,这种关系绑定是稳定的,不易变更的,被绑定的channel不能再与其他的ChannelPipeline绑定也不能与当前的ChannelPipeline解除绑定,这对于Netty的来说,在这个组件的生命周期里都是一个固定的操作,是Netty自动完成的,不需要开发者对这部分的动作进行任何的处理

TIPS:ChannelHandlerContext

一个ChannelHandlerContext使ChannelHandler与它对应的ChannelInboundHandler和相关的ChannelHandler做交互,一个处理器可以通知ChannelPipeline中的下一个ChannelHandler,甚至可以动态地修改它所属的ChannelPipeline

ChannelHandlerContext提供了非常丰富的API来处理事件,或者进行I/O操作,章节6.3将对ChannelHandlerContext进行更加详细的讲解

根据事件的来源,一个事件要么被ChannelInboundHandler处理要么被ChannelOutboundHandler处理,随后通过调用一个ChannelHandlerContext的具体实现,被转发到下一个同样类型的处理器处理

图6.3向我们展示了一个包含输入输出的ChannelHandler的经典的ChannelPipeline,并说明了我们之前说过的那个模型,channelPipeline就是一个一系列ChannelHandler组成的,channelPipeline为通过它自己的事件传播也提供了一些方法,如果一个输入事件被触发了,那么它将从头到尾的通过channelPipeline,在图6.3所示,一个输出的I/O操作将从channelPipeline的右边末端开始一直处理到左端结束

TIPS:channelPipeline的相对说

如果从事件在ChannelPipeline传播的方向的视角来区分事件的话,开始端取决于事件是输入还是输出,但是Netty默认使用输入到ChannelPipeline作为开始端,输出entry作为结束端

当我们已经把输入输出的处理器混合的通过ChannelPipeline的add方法加入到ChannelPipeline中去后,每一个channelHandler的序号(也就是它的位置顺序)就是我们将其加入ChannelPipeline时候的定义的顺序

我们再来从左往右的数一下图6.3中的处理器,第一个输入的处理器记为1,左边第一个输出的处理器记为5

管道里传输事件的时候,需要确认管道中下一个ChannelHandler是否匹配事件传播的方向,如果不匹配,那么将会跳过这个ChannelHandler,匹配下一个Handler,直到找到一个匹配方向的handler(当然一个handler可以同时实现ChannelInboundHandler和ChannelOutboundHandler两个接口)

6.2.1 Modifying
a ChannelPipeline

一个ChannelHandler可以实时的通过移除,增加或者替换其他的channelHandler来修改一个ChannelPipeline的布局,这是ChannelHandler众多出众的能力中的一种,所以我们需要仔细研究一下它是如何工作的,相关的方法在表6.6中展示:

下面的代码清单向你展示了如何使用这些方法

待会你会看见这种重新组织ChannelHandler的能力可以使它自己能够很轻易地实现极其灵活的业务逻辑处理

TIPS:ChannelHandler的执行和阻塞

正常情况下,每一个在ChannelPipeline中的ChannelHandler都是通过它的EventLoop线程来处理通过它的事件的,这是非常重要的,因为它不会阻塞当前线程,但是对于整体的I/O处理来说,这会有些负面的影响

有时候,对于一些比较陈旧的代码而言,使用阻塞的API也许更为的适合,在这种情况下,1ChannelPipeline通过add方法来接收一个EventExcutorGroup,如果一个事件通过定制的EventExcutorGroup的话,它将会包含在EventExcutorGroup中的一个EventExcutor处理,并且会从Channel它自己的EventLoop中移除,在这种场景下,Netty提供了一个叫做DefaultEventExcutorGroup的实现

除了这些操作,还有一些方法获取ChannelHandler要不通过类型要么通过名字,表6.7列出了这些方法

名字 描述
get 根据类型或者名称返回ChannelHandler
context 返回绑定在ChannelHandler上的ChannelHandlerContext
names 返回ChannelPipeline中的所有ChannelHandler名字

6.2.2 Firing events

ChannelPipeline的API额外提供了一些方法来调用输入输出数据的操作,表6.8列出了输入操作的一些常用方法,它用来负责通知ChannelInboundHandler发生在ChannelPipeline中的事件

对于输出数据方面,处理数据会引起一些底层的操作,表6.9向你展示了输出数据ChannelPipeline的一些API:

总而言之

1)一个ChannelPipeline持有关联一个Channel的所有ChannelHandler

2)一个ChannelPipeline可以根据需要动态地增加和移除ChannelHandler

3)ChannelPipeline有很丰富的API可以对输入输出事件作出响应

时间: 2024-10-08 00:39:44

Netty in Action (十五) 第六章节 第一部分 ChannelHandler和ChannelPipeline的相关文章

十五天精通WCF——第一天 三种Binding让你KO80%的业务

原文:十五天精通WCF--第一天 三种Binding让你KO80%的业务 转眼wcf技术已经出现很多年了,也在.net界混的风生水起,同时.net也是一个高度封装的框架,作为在wcf食物链最顶端的我们所能做的任务已经简单的不能再简单了, 再简单的话马路上的大妈也能写wcf了,好了,wcf最基本的概念我们放在后面慢慢分析,下面我们来看看神奇的3个binding如何KO我们实际场景中的80%的业务场景. 一:basicHttpBinding 作为入门第一篇,也就不深入谈谈basic中的信道栈中那些啥

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

本章介绍 获得Netty4最新的版本号 设置执行环境,以构建和执行netty程序 创建一个基于Netty的server和client 拦截和处理异常 编制和执行Nettyserver和client 本章将简介Netty的核心概念,这个狠心概念就是学习Netty是怎样拦截和处理异常.对于刚開始学习netty的读者.利用netty的异常拦截机制来调试程序问题非常有帮助.本章还会介绍其它一些核心概念.如server和client的启动以及分离通道的处理程序.本章学习一些基础以便后面章节的深入学习. 本

第三百八十五、六、七天 how can I 坚持

周五比较烦躁的一天,弄的很不愉快,晚上回济南了,在弟弟那住了一晚上,感觉压力好大啊,觉都没睡多好. 周六,去看了看房,交了个认筹金,好纠结的一天,晚上去刘松那一起撸了撸串,喝的很happy.晚上也没睡多好. 周日,今天,和lcj见了面,感觉好吧,就是眉毛不多呢,应该对我印象不是多好,都不去看电影了.也是昨晚都没睡觉. 啊..时间过得真快啊,下周就又五一放假了.就只知道发个感慨. 洗澡,睡觉了.

thinkphp URL规则、URL伪静态、URL路由、URL重写、URL生成(十五)

原文:thinkphp URL规则.URL伪静态.URL路由.URL重写.URL生成(十五) 本章节:详细介绍thinkphp URL规则.URL伪静态.URL路由.URL重写.URL生成 一.URL规则 1.默认是区分大小写的 2.如果我们不想区分大小写可以改配置文件 'URL_CASE_INSENSITIVE'=>true,//url不区分大小写 *模块命名太长的情况: A.如果模块名为 UserGroupAction,复杂模块(一般是IndexAction) 那么url找模块就必要要写成

张季跃 201771010139《面向对象程序设计(java)》第十五周学习总结

张季跃 201771010139<面向对象程序设计(java)>第十五周学习总结 第一部分:理论知识学习部分 第13章 部署应用程序: JAR文件: Java程序的打包:程序编译完成后,程序员 将.class文件压缩打包为.jar文件后,GUI界面 程序就可以直接双击图标运行. .jar文件(Java归档)既可以包含类文件,也可 以包含诸如图像和声音这些其它类型的文件. JAR文件是压缩的,它使用ZIP压缩格式. 清单文件 (1) 每个JAR文件中包含一个用于描述归档特征的清单文 件(mani

.top域名总量15强:商务中国第十五 持续负增长

IDC评述网(idcps.com)12月29日报道:据ntldstats.com最新数据显示,截止至2015年12月28日16时,国内外.top域名注册总量十五强排名情况,与上期12月15日对比,发生变化.华夏名网以.top域名总量5,732个位居第12,环比下滑1位.另外,西部数码.易名中国.中国数据坚守三甲,市场份额均达到10%,地位稳定.下面,请看IDC评述网整理的数据分析. (图)国内外域名服务商.top域名注册总量排行榜 观察上图数据,可知在国内外.top域名总量十五强排行榜上,中国共

Netty in Action (十二) 第五章节 第一部分 简介ByteBuf

第五章 ByteBuf(分四部分翻译) 本章节包括: 1)ByteBuf------Netty的数据容器 2)API介绍 3)使用案例 4)内存分配 我们之前提到过很多次,网络传输数据的最基本的数据单元是byte,Java的NIO提供了ByteBuffer作为字节的容器,但是这个类的使用有些过于复杂和麻烦 Netty对ByteBuffer提供了一个可选方案ByteBuf,一个很好的解决方案,解决了JDK原生的ByteBuffer的API使用不易的问题,同时ByteBuf为应用程序开发者提供了一系

Netty in Action (十四) 第五章节 第三部分 ByteBufHolder,ByteBuf分配,计数引用

5.4 Interface ByteBufHolder 我们经常在ByteBuf中存储一些正常数据之外,我们有时候还需要增加一些各式各样的属性值,一个Http响应体就是一个很好的例子,除了按照字节传输过来的主体内容,还有状态码,cookie等信息 Netty提供了ByteBufHolder来处理这些常用的用户案例,ByteBufHolder还提供了Netty一些其他的先进特性,例如缓存池,缓存池可以是ByteBuf中直接"借出"获取,如果有需要,"借出"的ByteB

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理

6.3 Interface ChannelHandlerContext 一个ChannelHandlerContext代表了一个ChannelHandler和ChannelPipeline之间的关系,ChannelHandlerContext创建于ChannelHandler被载入到ChannelPipeline的时候,ChannelHandlerContext主要功能是管理在同一ChannelPipeline中各个ChannelHandler的交互 ChannelHandlerContext有