ChannelHandlerContext——ChannelHandler和ChannelPipeline之二

目录

ChannelHandlerContext接口

使用ChannelHandlerContext

ChannelHandler和ChannelHandlerContext的高级用法

异常处理

处理入站异常

处理出站异常


本文继上文《ChannelHandler和ChannelPipeline之一》,接着讲ChannelHandlerContext接口。

ChannelHandlerContext接口

ChannelHandlerContext代表了ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加到ChannelPipeline中时都会创建ChannelHandlerContext。ChannelHandlerContext的主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipeline中的其他ChannelHandler之间的交互。

ChannelHandlerContext有很多的方法,其中一些方法也存在于Channel和ChannelPipeline本身上,但是有一点重要的不同。如果调用Channel或者ChannelPipeline上的这些方法,它们将沿着整个ChannelPipeline进行传播。而调用ChannelHandlerContext上的相同方法,则将从当前关联的ChannelHandler开始,并且只会传播给位于该ChannelPipeline中的下一个能够处理该事件的ChannelHandler。下表对ChannelHandlerContext的API进行了总结。

方法名称描述
alloc返回和这个实例相关联的Channel所配置的ByteBufAllocator
bind绑定到给定的SocketAddress,并返回ChannelFuture
channel返回绑定到这个实例的Channel
close关闭Channel,并返回ChannelFuture
connect连接给定的SocketAddress,并返回ChannelFuture
deregister从之前分配的EventExecutor注销,并返回ChannelFuture
disconnect从远程节点断开,并返回ChannelFuture
executor返回调度事件的EventExecutor
fireChannelActive触发对下一个ChannelOutboundHandler上的channelActive()方法(已连接)的调用
fireChannelInactive触发对下一个ChannelOutboundHandler上的channelInactive()方法(已关闭)的调用
fireChannelRead触发对下一个ChannelOutboundHandler上的channelRead()方法(已接收的消息)的调用
fireChannelReadComplete触发对下一个ChannelOutboundHandler上的channelReadComplete()方法的调用
fireChannelRegistered触发对下一个ChannelOutboundHandler上的channelRegistered()方法的调用
fireChannelUnregistered触发对下一个ChannelOutboundHandler上的channelUnregistered()方法的调用
fireChannelWritabilityChanged触发对下一个ChannelOutboundHandler上的channelWritabilityChanged()方法的调用
fireExceptionCaught触发对下一个ChannelOutboundHandler上的channelExceptionCaught(Throwable)方法的调用
fireUserEventTriggered触发对下一个ChannelOutboundHandler上的userEventTriggered(Object)方法的调用
handler返回绑定到这个实例的ChannelHandler
isRemoved如果所关联的ChannelHandler已经从所关联的ChannelPipeline中移除,则返回true
name返回这个实例的唯一名称
pipeline返回这个实例所关联的ChannelPipeline
read将数据从Channel读取到第一个入站缓冲区;如果读取成功则触发一个channelRead事件,并(在最后一个消息被读取完成后)通知ChannelInboundHandler的channelReadComplete(ChannelHandlerContext)方法
write通过这个实例写入消息并经过ChannelPipeline
writeAndFlush通过这个实例写入消息并冲刷并经过ChannelPipeline

当使用ChannelHandlerContext的API的时候,请牢记以下两点:

  • ChannelHandlerContext和ChannelHandler之间的关联(绑定)是永远不会变的,所以缓存对它的引用是安全的;
  • 相对于其他类的同名方法,ChannelHandlerContext的方法将产生更短的事件流,应该尽可能地利用这个性能获得最大的性能。

使用ChannelHandlerContext

接下来我们讨论ChannelHandlerContext的用法,以及存在于ChannelHandlerContext、Channel和ChannelPipeline上的方法的行为。下图展示了它们之间的关系:

以下代码通过ChannelHandlerContext获得Channel,调用Channel上的write方法将会导致写入事件从尾端到头部地流经ChannelPipeline。

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

以下代码类似,但是这一次是写入ChannelPipeline。我们再次看到,(到ChannelPipeline的)引用是通过ChannelHandlerContext获得的。

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

虽然被调用的Channel或ChannelPipeline上的write()方法将一直传播事件通过整个ChannelPipeline,但是在ChannelHandler的级别上,事件从一个ChannelHandler到下一个ChannelHandler的移动是由ChannelHandlerContext上的调用完成的。

为什么会想要从ChannelPipeline中的某个特定点开始传播事件呢?

  • 为了减少事件传经对它不感兴趣的ChannelHandler所带来的开销。
  • 为了避免将事件传经那些可能会对它产生兴趣的ChannelHandler。

要想调用从某个特定的ChannelHandler开始的处理过程,必须获取到在(ChannelPipeline)该ChannelHandler之前的ChannelHandler所关联的ChannelHandlerContext。这个ChannelHandlerContext将调用和它相关联的ChannelHandler之后的ChannelHandler。

如下图所示,消息将从下一个ChannelHandler开始流经ChannelPipeline,绕过了前面所有的ChannelHandler。

以上描述的用例是常见的,对于调用特定的ChannelHandler实例上的操作尤其有用。

ChannelHandler和ChannelHandlerContext的高级用法

你可以通过调用ChannelHandlerContext的pipeline()方法来获得被封闭的ChannelPipeline的引用。这使得运行时得以操作ChannelPipeline的ChannelHandler,我们可以利用这一点实现一些复杂的设计。例如,你可以通过将ChannelHandler添加到ChannelPipeline中实现动态的协议切换。

另一种高级的用法是缓存到ChannelHandlerContext的引用以供稍后使用,这可能会发生在任何的ChannelHandler之外,甚至来自于不同的线程。

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

因为一个ChannelHandler可以从属于多个ChannelPipeline,所以它也可以绑定到多个ChannelHandlerContext。对于这种用法旨在多个ChannelPipeline中共享同一个ChannelHandler,对应的ChannelHandler必须要使用@Sharable注解标注;否则,试图将它添加到多个ChannelPipeline时将会触发异常。显而易见,为了安全地被用于多个并发的Channel(即连接),这样的ChannelHandler必须是线程安全的。

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

以上代码的时间将会导致问题,因为它拥有状态,即用于跟踪方法调用次数的实例变量count。将这个类的一个实例添加到一个ChannelPipeline将极有可能在它被多个并发的Channel访问时导致问题。(当然,这个简单的问题可以通过使channelRead()方法变位同步方法来修正。)

总之,只应该在你确认了你的ChannelHandler是线程安全后才使用@Sharable注解。

为何要共享同一个ChannelHandler

在多个ChannelPipeline中安装同一个ChannelHandler的一个常见原因是用于收集跨越多个Channel的统计信息。

异常处理

异常处理是任何真实应用程序的重要组成部分,它也可以通过多种方式来实现。因此,Netty提供了几种方式用于处理入站或者出站处理过程中所抛出的异常。

处理入站异常

如果在处理入站事件的过程中有异常被抛出,那么它将从它在ChannelInboundHandler里被触发的那一点开始流经ChannelPipeline。要想处理这种类型的入站异常,你需要在你的ChannelInboundHandler实现中重写下面的方法。

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception

以下代码展示了一个简单的示例,其关闭了Channel并打印了异常的栈跟踪信息。

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

因为异常将会继续按照入站方向流动(就像所有的入站事件一样),所以实现了前面所示逻辑的ChannelInboundHandler通常位于ChannelPipeline的最后。这确保了所有的入站异常总是会被处理,无论它们可能发生在ChannelPipeline中的什么位置。

你应该如何响应异常,可能很大程度上取决于你的应用程序。你可能想要关闭Channel(和连接),也可能会尝试进行恢复。如果你不实现任何处理入站异常的逻辑(或者没有消费该异常),那么Netty将会记录该异常没有被处理的事实。

  • ChannelHandler.exceptionCaught()的默认实现是简单地将当前异常转发给ChannelPipeline中的下一个ChannelHandler;
  • 如果异常到达了ChannelPipeline的尾端,它将会被记录为未被处理;
  • 要想定义自定义的处理逻辑,你需要重写exceptionCaught()方法。然后你需要决定是否需要将该异常传播出去。

处理出站异常

用于处理出站操作中的正常完成以及异常的选项,都基于以下的通知机制。

  • 每个出站操作都将返回一个ChannelFuture。注册到该ChannelFuture的ChannelFutureListener将在操作完成时被通知该操作是成功了还是出错了。
  • 几乎所有的ChannelOutboundHandler上的方法都会传入一个ChannelPromise的实例。作为ChannelFuture的子类,ChannelPromse也可以被分配用于异步通知的监听器。但是,ChannelPromise还具有提供立即通知的可写方法。
ChannelPromise seSuccess();
ChannelPromise setFailure(Throwable cause);

添加ChannelFutureListener只需要调用ChannelFuture实例上的addListener(ChannelFutureListener)方法,并且有两种不同的方式可以做到这一点。其中最常用的方式是调用出站操作(如write方法)所返回的ChannelFuture上的addListener()方法。

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

第二方式是將ChannelFutureListener添加到即將作为参数传递给ChannelOutboundHandler的方法的ChannelPromise。

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

ChannelPromise的可写方法

通过调用ChannelPromise上的setSuccess和setFailure方法,可以使一个操作的状态在ChannelHandler的方法返回给其调用者时便即刻被感知到。

为什么选用一种方式而不是另一种呢? 对于细致的异常处理,你可能会发现,在调用出站操作时添加ChannelFutureListener更加合适。而对于一般的异常处理,你可能会发现,第一种实现方式更加简单。

如果你的ChannelOutboundHandler本身抛出异常会发生什么呢?在这种情况下,Netty本身会通知任何已经注册到对应ChannelPromise的监听器。

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

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

相关文章

LeetCode hot100-51-G

200. 岛屿数量 给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网…

Python 全栈体系【四阶】(五十)

第五章 深度学习 十一、扩散模型 4. 附录:Diffusion的数学推导过程 4.2 Diffusion正向扩散过程推导 设初始数据 x 0 x_0 x0​符合分布 q ( x 0 ) q(x_0) q(x0​),即训练集分布,然后不断向其中添加高斯噪声,高斯噪声本身是不可…

需求响应+配网重构!含高比例新能源和用户需求响应的配电网重构程序代码!

前言 配电网重构作为配电网优化运行的手段之一,通过改变配电网的拓扑结构,以达到降低网损、改善电压分布、提升系统的可靠性与经济性等目的。近年来,随着全球能源消耗快速增长以及环境的日趋恶化,清洁能源飞速发展,分…

超简单白话文机器学习 - 逐步回归Lasso,Ridge正则化(含算法讲解,公式全解,手写代码实现,调包实现)

1. 提高泛化能力 1.1 概念 正则化是一种在机器学习和统计建模中用于防止过拟合的技术。过拟合是指模型在训练数据上表现很好,但在未见过的测试数据或新数据上表现不佳。正则化通过在损失函数(如最小二乘误差)中添加一个惩罚项,限…

【Spring】Spring事务管理

1. 事务的基本概念 1.1简介 数据库事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。Spring的事务管理主要基于AOP(面向切面编程)技术,通过声明式或编程式的方式来实现。Spring框架内…

仓库管理WMS软件(Warehouse Management Software)百科解析

一、什么是仓库管理软件(WMS)? 仓库管理软件(WMS)全称Warehouse Management System,是一种专门用于仓库作业流程优化和库存控制的软件系统。它通过先进的自动识别与数据采集技术,实现对仓库货物…

Leecode热题100---114:二叉树展开为链表

题目: 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历 顺序相同…

SCSS基本使用

SCSS(Sassy CSS)是一种 CSS 预处理器,它是 CSS 语言的一个扩展,增加了变量、嵌套规则、混合(mixins)、函数等功能,使得编写 CSS 更加高效和易于维护。SCSS 代码最终会被编译成标准的 CSS 代码。…

Lambda表达式使用及详解

Java 8引入的Lambda表达式是一种重要的功能,它允许你以更简洁、更直接的方式传递方法。Lambda可以被用来代替只有单个抽象方法的接口的匿名实现类。这里有一些Lambda表达式的实际应用场景及其示例: 1. 集合操作 在处理集合时,如List或Set&a…

Oracle数据库中的PCTFREE解析

PCTFREE是Oracle数据库中用于表或索引段空间管理的一个重要存储参数。这个参数定义了一个数据块中保留的最小空闲空间百分比,旨在为现有行的未来更新预留空间。具体来说: 当设置一个数据块的PCTFREE值时,你实际上是告诉Oracle在这个数据块填满…

Spring Boot | Spring Boot 实现 “记住我“ 功能

目录: 一、SpringBoot 中 自定义 "用户授权管理" ( 总体内容介绍 )二、实现 "记住我" 功能 ( 通过 "HttpSecurity类" 的 rememberMe( )方法来实现 "记住我" 功能 ) :2.1 基于 "简单加密 Token" 的方式 ( 实现 "记住我&…

av_dump_format经验分析,FFmpeg获取媒体文件总时长(FLV获取总时长的误区)

播放器有个功能,当用户打开视频时,需要读取媒体文件的总时长等信息,不巧的时,获取FLV时总失败,下面来具体分析下FLV和MP4获取总时长的原因和区别: 播放器有个获取MediaInfo的接口,功能如下&am…

python web自动化(关键字驱动与POM)

1.关键字驱动解析 所谓的关键字驱动,本质就是函数封装的过程。 ⾃动化当中的封装⽬的是:拆分重复的⾏为代码和测试数据,增加可维护性和复⽤性 我们想要定义一个工具类 # 定义工具类(基于基础的方法,进行的关键…

echarts全局设置饼图的颜色

🌷第一步 在js文件中写入你需要的颜色 这里的颜色也可以写渐变的 🌷下一步 在main.is中引用全局挂载 🌷最后一步 在初始化的时候加一个macarons即可 🌷第一步 在js文件中写入你需要的颜色 这里的颜色也可以写渐变的 (functi…

探索k8s集群中kubectl的陈述式资源管理

一、k8s集群资源管理方式分类 1.1陈述式资源管理方式:增删查比较方便,但是改非常不方便 使用一条kubectl命令和参数选项来实现资源对象管理操作 即通过命令的方式来实 1.2声明式资源管理方式:yaml文件管理 使用yaml配置文件或者json配置文…

【研发日记】嵌入式处理器技能解锁(一)——多任务异步执行调度的三种方法

文章目录 前言 Timer中断调度 Event中断调度 StateFlow调度 分析和应用 总结 参考资料 前言 近期在一些嵌入式系统开发项目中,在使用嵌入式处理器时,遇到了挺多费时费力的事情。所以利用晚上和周末时间,在这些方面深入研究了一下&…

全文索引-FullText

一、数据结构 倒排索引:存储单词与文档的映射 1、正向索引 正向索引结构如下: 文档1的ID→单词1的信息;单词2的信息;单词3的信息… 文档2的ID→单词3的信息;单词2的信息;单词4的信息… 2、反向索引 反向索引结构如…

AWS安全性身份和合规性之Inspector

Amazon Inspector 是一项漏洞管理服务,可持续扫描您的 AWS 工作负载,以查找软件漏洞和意外网络泄露。Amazon Inspector 会自动发现和扫描正在运行的亚马逊 EC2 实例、亚马逊弹性容器注册表 (Amazon ECR) Container Registry 中的容器映像,以及…

Joomla 3.7.0 (CVE-2017-8917) SQL注入漏洞环境

1 漏洞概述 Joomla是一个基于PHP的内容管理系统(CMS),广泛应用于各类网站。2017年,Joomla 3.7.0版本被发现存在SQL注入漏洞(CVE-2017-8917),攻击者可以利用该漏洞对数据库进行未授权查询或操作…

前端常用的状态码

常用状态码 常用状态码大家可以记一下,所有的也记不住,简洁几个字概括一下,方便大家进行记忆。 200 OK:请求成功 401 Unauthorized:未授权,未登录 403 Forbidde:已登录,但是对于…