【Netty】ChannelHandler和ChannelPipeline

一、前言

  前面学习了Netty的ByteBuf,接着学习ChannelHandler和ChannelPipeline。

二、ChannelHandler和ChannelPipeline

  2.1 ChannelHandler

  在ChannelPipeline中,ChannelHandler可以被链在一起处理用户逻辑。

  1. Channel生命周期

  Channel接口定义了一个简单但是强大的状态模型,该模型与ChannelInboundHandler API紧密联系,Channel有如下四种状态。

  

  Channel的生命周期如下图所示。

  

  当状态发生变化时,就会产生相应的事件。

  2. ChannelHandler的生命周期

  ChannelHandler定义的生命周期如下图所示。

  

  Netty定义了ChannelHandler的两个重要的子类

    · ChannelInboundHandler,处理各种入站的数据和状态的变化。

    · ChannelOutboundHandler,处理出站数据并允许拦截的所有操作。

  3. ChannelInboundHandler接口

  下图展示了ChannelInboundHandler接口生命周期中的方法,当接受到数据或者其对应的Channel的状态发生变化则会调用方法

  

  当ChannelInboundHandler的实现覆盖channelRead()方法时,它负责显式释放与池的ByteBuf实例相关联的内存,可以使用ReferenceCountUtil.release() 方法进行释放。如下代码展示了该方法的使用。

public class DiscardHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ReferenceCountUtil.release(msg);}
}

  上述显式的释放内存空间会显得比较麻烦,而如下代码则无需显式释放内存空间。  

@Sharable
public class SimpleDiscardHandlerextends SimpleChannelInboundHandler<Object> {@Overridepublic void channelRead0(ChannelHandlerContext ctx,Object msg) {// No need to do anything special
    }
}

  上述代码中,SimpleChannelInboundHandler会自动释放资源,因此无需显式释放。

  4. ChannelOutboundHandler接口

  ChannelOutboundHandler处理出站操作和数据,它的方法会被Channel、ChannelPipeline、ChannelHandlerContext触发。ChannelOutboundHandler可按需推迟操作或事件。例如对远程主机的写入被暂停,你可以延迟刷新操作并在稍后重启。

  5. ChannelHandler适配器

  可以使用ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter类作为自己的ChannelHandler程序的起点,这些适配器提供了ChannelInboundHandler和ChannelOutboundHandler的简单实现,它们继承了共同父类接口ChannelHandler的方法,其继承结构如下图所示

  

  ChannelHandlerAdapter还提供了isSharable方法,如果有Sharable注释则会返回true,这也表明它可以被添加至多个ChannelPipiline中。ChannelInboundHandlerAdapter and ChannelOutboundHandlerAdapter的方法体中会调用ChannelHandlerContext对应的方法,因此可以将事件传递到管道的下个ChannelHandler中。

  6. 资源管理

  无论何时调用ChannelInboundHandler.channelRead()和ChannelOutboundHandler.write()方法,都需要保证没有资源泄露,由于Netty使用引用计数来管理ByteBuf,因此当使用完ByteBuf后需要调整引用计数。

  为了诊断可能出现的问题,Netty提供了ResourceLeakDetector,它将抽取应用程序大约1%的缓冲区分配来检查内存泄漏,其额外的开销很小,内存检测有如下四种级别

  

  可以通过java -Dio.netty.leakDetectionLevel=ADVANCED 设置内存检测级别。

  当读取数据时,可以在readChannel方法中调用ReferenceCountUtil.release(msg)方法释放资源,或者实现SimpleChannelInboundHandler(会自动释放资源);而当写入数据时,可以在write方法中调用ReferenceCountUtil.release(msg)释放资源,具体代码如下 

@Sharable
public class DiscardOutboundHandlerextends ChannelOutboundHandlerAdapter {@Overridepublic void write(ChannelHandlerContext ctx,Object msg, ChannelPromise promise) {ReferenceCountUtil.release(msg);promise.setSuccess();}
}

  不仅需要释放资源,并且需要通知ChannelPromise,否则ChannelFutureListener可能收不到事件已经被处理的通知。如果消息到达实际的传输层,就可以在写操作完成或者关闭通道时会自动释放资源。

  2.2 ChannelPipeline接口

  如果将ChannelPipeline视为ChannelHandler实例链,可拦截流经通道的入站和出站事件,即可明白ChannelHandler之间的交互是如何构成应用程序数据和事件处理逻辑的核心的。当创建一个新的Channel时,都会分配了一个新的ChannelPipeline,该关联是永久的,该通道既不能附加另一个ChannelPipeline也不能分离当前的ChannelPipeline。

  一个事件要么被ChannelInboundHander处理,要么被ChannelOutboundHandler处理,随后,它将通过调用ChannelHandlerContext的实现来将事件转发至同一超类型的下一个处理器。ChannelHandlerContext允许ChannelHandler与其ChannelPipeline和其他ChannelHandler进行交互,一个处理器可以通知ChannelPipeline中的下一个处理器,甚至可以修改器隶属于的ChannelPipeline。

  下图展示了ChannelHandlerPipeline、ChannelInboundHandler和ChannelOutboundHandler之间的关系

  

  可以看到ChannelPipeline是由一系列ChannelHandlers组成,其还提供了通过自身传播事件的方法,当进站事件触发时,其从ChannelPipeline的头部传递到尾部,而出站事件会从右边传递到左边。

  当管道传播事件时,其会确定下一个ChannelHandler的类型是否与移动方向匹配,若不匹配,则会跳过并寻找下一个,直至找到相匹配的ChannelHandler(一个处理器可以会同时实现ChannelInboundHandler和ChannelOutboundHandler)。

  1. 修改ChannelPipeline

  ChannelHandler可实时修改ChannelPipeline的布局,如添加、删除、替换其他ChannelHandler(其可从ChannelPipeline中移除自身),如如下图所示的方法。

  

  通常,ChannelPipeline中的每个ChannelHandler通过其EventLoop(I / O线程)处理传递给它的事件,不要阻塞该线程,因为它会对I/O的整体处理产生负面影响。

  2.3 ChannelHandlerContext接口

  ChannelHandlerContext代表了ChannelHandler与ChannelPipeline之间的关联,当ChannelHandler被添加至ChannelPipeline中时其被创建,ChannelHandlerContext的主要功能是管理相关ChannelHandler与同一ChannelPipeline中的其他ChannelHandler的交互。

  ChannelHandlerContext中存在很多方法,其中一些也存在于ChannelHandler和ChannelPipeline中,但是差别很大。如果在ChannelHandler或者ChannelPipeline中调用该方法,它们将在整个管道中传播,而如果在ChannelHandlerContext中调用方法,那么会仅仅传递至下个能处理该事件的ChannelHandler。

  1. 使用ChannelHandlerContext

  ChannelHandlerContext、ChannelHandler、ChannelHandlerContext、Channel之间的关系如下图所示

  

  可以通过ChannelHandlerContext来访问Channel,并且当调用Channel的write方法时,写事件会在管道中传递,代码如下 

ChannelHandlerContext ctx = ..;
Channel channel = ctx.channel();
channel.write(Unpooled.copiedBuffer("Netty in Action",
CharsetUtil.UTF_8));

  除了使用Channel的write方法写入数据外,还可以使用ChannelPipeline的write方法写入数据,代码如下 

ChannelHandlerContext ctx = ..;
ChannelPipeline pipeline = ctx.pipeline();
pipeline.write(Unpooled.copiedBuffer("Netty in Action",
CharsetUtil.UTF_8));

  上述两段代码在管道中产生的效果相同,如下图所示。

  

  其中两种方法的写事件都会通过ChannelHandlerContext在管道中传播。

  若想从指定的ChannelHandler开始传递事件,那么需要引用到指定ChannelHandler之前的一个ChannelHandlerContext,该ChannelHandlerContext将调用其关联的ChannelHandler。

  如下图所示,展示了从指定ChannelHandler开始处理事件。

  

  2. ChannelHandler和ChannelHandlerContext的高级用法

  可以通过调用ChannelHandlerContext的pipeline方法获得其对应的ChannelPipeline引用,这可以在运行中管理ChannelHandler,如添加一个ChannelHandler。

  另一种高级用法是缓存ChannelHandlerContext的引用,以供之后使用。如下代码展示了用法 

public class WriteHandler extends ChannelHandlerAdapter {private ChannelHandlerContext ctx;@Overridepublic void handlerAdded(ChannelHandlerContext ctx) {this.ctx = ctx;}public void send(String msg) {ctx.writeAndFlush(msg);}
}        

  因为ChannelHandler可以属于多个ChannelPipeline,所以它可以绑定到多个ChannelHandlerContext实例,当使用时必须使用@Sharable注释,否则,当将其添加至多个ChannelPipeline时会抛出异常。如下代码所示  

@Sharable
public class SharableHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("Channel read message: " + msg);ctx.fireChannelRead(msg);}
}

  @Sharable注释没有任何状态,而如下代码会出现错误。  

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

  由于上述代码包含了状态,即count计数,将此类的实例添加到ChannelPipeline时,在并发访问通道时很可能会产生错误。可通过在channelRead方法中进行同步来避免错误。

  2.4 异常处理

  在出站和进站时,可能会发生异常,Netty提供了多种方法处理异常。

  1. 处理进站异常

  当处理进站事件时发生异常,它将从ChannelInboundHandler中被触发的位置开始流过ChannelPipeline,为处理异常,需要在实现ChannelInboundHandler接口是重写exceptionCaught方法。如下示例所示。  

public class InboundExceptionHandler extends ChannelInboundHandlerAdapter {@Overridepublic void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {cause.printStackTrace();ctx.close();}
}

  由于异常会随着进站事件在管道中传递,包含异常处理的ChannelHandler通常放在了管道的尾部,因此可以保证无论异常发生在哪个ChannelHandler中,其最终都会被处理。

  2. 处理出站异常

  在出站操作中处理的正常完成和处理异常都基于以下通知机制。

    · 每个出站操作返回一个ChannelFuture,在ChannelFuture注册的ChannelFutureListeners在操作完成时通知成功或错误。

    · ChannelOutboundHandler的几乎所有方法都会传递ChannelPromise实例,ChannelPromise是ChannelFuture的子类,其也可以为异步通知分配监听器,并ChannelPromise还提供可写的方法来提供即时通知。可通过调用ChannelFuture实例的addListener(ChannelFutureListener)方法添加一个ChannelFutureListener,最常用的是调用出站操作所返回的ChannelFuture的addListener方法,如write方法,具体代码如下所示。  

ChannelFuture future = channel.write(someMessage);
future.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture f) {if (!f.isSuccess()) {f.cause().printStackTrace();f.channel().close();}}
});

  第二种方法是将ChannelFutureListener添加到ChannelPromise中,并将其作为参数传递给ChannelOutboundHandler的方法,具体代码如下所示  

public class OutboundExceptionHandler extends ChannelOutboundHandlerAdapter {@Overridepublic void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise) {promise.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture f) {if (!f.isSuccess()) {f.cause().printStackTrace();f.channel().close();}}});}
}

三、总结

  本篇博文讲解了ChannelHandler,以及其与ChannelPipeline、ChannelHandlerContext之间的关系,及其之间的交互,同时还了解了如何处理进站与出站时所抛出的异常。谢谢各位园友的观看~

转载于:https://www.cnblogs.com/leesf456/p/6901189.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/455590.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动驾驶安全驾驶规则_自动驾驶知识科普 自动驾驶汽车的七大核心技术

自动驾驶技术的本质是用机器视角去模拟人类驾驶员的行为&#xff0c;其技术框架可以分为三个环节&#xff1a;感知层、决策层 和执行层&#xff0c;具体涉及传感器、计算平台、算法、高精度地图、OS、HMI等 多个技术模块。目前自动驾驶L3商业化技术已经成熟&#xff0c;L4级/L5…

bupt summer training for 16 #3 ——构造

https://vjudge.net/contest/172464 后来补题发现这场做的可真他妈傻逼 A.签到傻逼题&#xff0c;自己分情况 1 #include <cstdio>2 #include <vector>3 #include <algorithm>4 5 using std::vector;6 using std::sort;7 8 typedef long long ll;9 10 int n…

python常用快捷键、写代码事半功倍_Pycharm常用快捷键总结及配置方法

工欲善其事必先利其器&#xff0c;Python开发利器Pycharm常用快捷键以及配置如下&#xff0c;相信有了这些快捷键&#xff0c;你的开发会事半功倍 一 常用快捷键 编辑类&#xff1a; Ctrl D 复制选定的区域或行 Ctrl Y 删除选定的行 Ctrl Alt L 代码格式化 Ctrl Alt O 优…

使用FFMPEG SDK解码流数据获得YUV数据及其大小

本文以H264视频流为例&#xff0c;讲解解码流数据的步骤。 为突出重点&#xff0c;本文只专注于讨论解码视频流数据&#xff0c;不涉及其它&#xff08;如开发环境的配置等&#xff09;。如果您需要这方面的信息&#xff0c;请和我联系。 准备变量 定义AVCodecContext。如果您…

关于Python3.7和Python3.6中元组类型数据内存存储问题

关于Python3.7和Python3.6中元组类型数据内存存储问题 小编最近发现了一个瑕疵 当定义一个元组类型的变量后,若在程序后面再定义一个元组变量,这两个元组的内容相同,那么在不同的版本中会出现不同的结果 在Python3.6版本中,解释器将在内存中开辟两个内存空间分别存储两个元组的…

shell 删除了hdfs 文件_从零开始学大数据(三) Shell操作HDFS文件系统-中

1、格式化[rootmaster sbin]# hdfs namenode -format2、命令hdfs dfsadmin查看(hdfs dfsadmin -report)[rootmaster ~]# hdfs dfsadmin -report安全模式#获取安全模式状态[rootmaster ~]# hdfs dfsadmin -safemode get#进入安全状态[rootmaster ~]# hdfs dfsadmin -safemode en…

计算机硬件

计算机硬件 一、为什么要学习计算机基础 程序员编程的本质就是让计算机去工作&#xff0c;而编程语言就是程序员与计算机沟通的介质。程序员要想让计算机工作&#xff0c;就要知道计算机能干什么、是怎么样的一个完成过程&#xff0c;这也是我们必须学习计算机基础的原因。 …

铁路售票系统_铁路资讯:复兴号动车、智能京张高铁…中国最高端铁路装备看这里...

今天上午&#xff0c;两年一度的中国国际现代化铁路技术装备展在京开展&#xff0c;会期3天&#xff0c;将集中展示路网建设、客货运输、经营管理、工程建造、技术装备、旅客服务等铁路行业各领域的先进产品及技术。展会现场智能京张&#xff1a;将首次实现时速350公里自动驾驶…

CentOS下安装MySQL报安装文件conflicts错误:

2019独角兽企业重金招聘Python工程师标准>>> 第一&#xff1a;报这个错误&#xff0c;说明已经安装或相关文件已经存在&#xff0c;把已经存在的文件卸载了就可以了&#xff1a; rpm -e --nodeps mysql-libs-5.1.* 转载于:https://my.oschina.net/u/3197158/blog/1…

inc指令是什么意思_西门子PLC一些指令

指令(英文全称意思)∶指令含义1、LD ( Load装载):动合触点2、LDN (Load Not不装载):动断触点3、A(And与动合):用于动合触点串联4、AN (And Not与动断):用于动断触点串联5、o(Or 或动合):用于动合触点并联6、ON(Or Not 或动断):用于动断触点并联7、(Out输出):用于线圈输出8、OLD…

touchesEnded不响应

为什么80%的码农都做不了架构师&#xff1f;>>> http://blog.csdn.net/assholeu/article/details/16363241 touchesEnded不响应主要存在以下几种情况 case 1 : userInteractionEnabled 部分控件如UIImageView&#xff0c;userInteractionEnabled默认为NO&#xff0…

iOS开发人员不容错过的10大工具

内容简介 1、iOS简介 2、iOS开发十大实用工具之开发环境 3、iOS开发十大实用工具之图标设计 4、iOS开发十大实用工具之原型设计 5、iOS开发十大实用工具之演示工具 6、iOS开发十大实用工具之视频制作 7、iOS开发十大实用工具之分析工具 iOS简介 说起iOS&#xff0c;自然不必多介…

算法学习系列(十):用数组模拟链表、双链表、栈、队列、单调栈、单调队列

目录 引言一、数组模拟链表1.模板2.例题3.测试 二、数组模拟双链表1.模板2.例题3.测试 三、数组模拟栈1.模板2.例题3.测试 四、数组模拟队列1.模板2.例题3.测试 五、数组模拟单调栈1.例题模板2.测试 六、数组模拟单调队列1.例题模板2.测试 引言 首先说一下为什么要拿数组来模拟…

为什么你的路由器穿墙能力差?看完秒懂

1、信号弱赖我咯? 不管你承认与否&#xff0c;只要有墙家中就会存有信号死角&#xff0c;不要小看一墙之隔。如何让路由器的信号增强? 网上一搜旁门左道真不少&#xff0c;什么调整天线寻找合理角度&#xff0c;又或是用易拉罐DIY一个信号放大器&#xff0c;然鹅并非简单的将…

fish工具_Python程序员使用哪些开发工具

Python程序员使用哪些开发工具?很多Python学习者想必都会有如下感悟&#xff1a;最开始学习Python的时候&#xff0c;因为没有去探索好用的工具&#xff0c;吃了很多苦头。后来工作中深刻体会到&#xff0c;合理使用开发的工具的便利和高效。今天&#xff0c;北京学佳澳小编总…

[shiro学习笔记]第二节 shiro与web融合实现一个简单的授权认证

本文地址&#xff1a;http://blog.csdn.net/sushengmiyan/article/details/39933993shiro官网: http://shiro.apache.org/shiro中文手冊&#xff1a;http://wenku.baidu.com/link?urlZnnwOHFP20LTyX5ILKpd_P94hICe9Ga154KLj_3cCDXpJWhw5Evxt7sfr0B5QSZYXOKqG_FtHeD-RwQvI5ozyT…

Web安全之Cookie劫持

1.Cookie是什么? 2.窃取的原理是什么? 3.系统如何防Cookie劫持呢? 看完这三个回答&#xff0c;你就明白哪位传奇大侠是如何成功的!!! Cookie: HTTP天然是无状态的协议&#xff0c;为了维持和跟踪用户的状态&#xff0c;引入了Cookie和Session。Cookie包含了浏览器客户端的用…

运动估计简介

运动估计( Motion Estimation) 维基百科链接&#xff1a;http://en.wikipedia.org/wiki/Motion_estimation运动估计的应用有很多&#xff0c;最初的应用的领域是视频的编码。运动估计算法一般分为: 像素递归法pel-recursive algorithm (PRA)和块匹配法 block-matching algorith…

andriod studio 运行 无结果_华为物联网操作系统LiteOS内核教程01——IoT-Studio介绍及安装...

1. 物联网一站式开发工具 —— IoT StudioIoT Studio 是支持 LiteOS 嵌入式系统软件开发的工具&#xff0c;提供了代码编辑、编译、烧录 及调试等一站式开发体验&#xff0c;支持 C、C、汇编等多种开发语言&#xff0c;让您快速&#xff0c;高效地进 行物联网开发。2. IoT Stud…