SimpleChannelInboundHandler
继承于ChannelInboundHandlerAdapter
,
ChannelInboundHandlerAdapter
中包含channelRead()
方法。SimpleChannelInboundHandler
重写了channelRead()
,并且新增了channelRead0()
方法,(在Netty5中channelRead0
已被重命名为messageReceived
)。对于SimpleChannelInboundHandler
这里只提供了一个模板,作用是把处理逻辑不变的内容写好在 channelRead(ctx,msg) 中,并且在里面调用 channelRead0 ,这样变化的内容通过抽象方法实现传递到子类中去了。
@Override// 继承了 ChannelInboundHandlerAdapter#channelRead
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);// 模板方法,抽出可变的部分,具体实现在子类中
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);// 释放资源(保存消息的ByteBuf)
}
}
}
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
因此我们继承SimpleChannelInboundHandler后,处理入站的数据我们只需要重新实现channelRead0方法,当channelRead真正被调用的时候我们的逻辑才会被处理。这里使用的是模板模式,让主要的处理逻辑保持不变,让变化的步骤通过接口实现来完成
值得注意的是对于SimpleChannelInboundHandler入站的数据,当被读取之后可能会执行ReferenceCountUtil.release(msg)释放资源。底层是实现ReferenceCounted,当新的对象初始化的时候计数为1,retain()方法实现其他地方的引用计数加1,release()方法实现应用减一,当计数减少到0的时候会被显示清除,再次访问被清除的对象会出现访问冲突。因此,当我们实现自己的Handler的时候如果希望将客户端发送过来的数据发送到客户端,可能在上述finally中已经释放了资源(writeAndFlush是异步处理),所以会出现异常情况。
但是当我们实现的是ChannelInboundHandler类的时候,重写channelRead方法时,需要释放ByteBuf相关的内存,可以使用Netty提供了一个工具方法,ReferenceCountUtil.release()
如果在channelRead中写了ctx.write(接收到的内容),由于write是异步的,可能在channelRead返回之后,仍然没有完成,为此,扩展了ChannelInboundHandlerAdapter,其在这个时间点上不会释放消息,消息在channelReadComplete(),当writeAndFlush方法被调用时释放。
理解:一个处理器Handler继承SimpleChannelInboundHandler
中,无论有没有重写channelRead()
方法没有,当有数据的时候都会首先触发SimpleChannelInboundHandle
r中channelRead()
方法,然后在channelRead()
中会调用channelRead0()
方法,因为他是一个接口,所以调用的事处理器Handler中重写的channelRead0()
方法
在客户端的业务Handler继承的是SimpleChannelInboundHandler
,而在服务器端继承的是ChannelInboundHandlerAdapter
。
最主要的区别就是SimpleChannelInboundHandler
在接收到数据后会自动release
掉数据占用的Bytebuffer
资源(自动调用Bytebuffer.release())
。而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而服务器端有可能在channelRead
方法返回前还没有写完数据,因此不能让它自动release
在客户端,当 channelRead0() 方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler负责释放指向保存该消息的ByteBuf的内存引用。
那为什么服务端不需要这样处理呢?
在EchoServerHandler中,你仍然需要将传入消息回送给发送者,而 write() 操作是异步的,直到 channelRead() 方法返回后可能仍然没有完成。为此,EchoServerHandler扩展了 ChannelInboundHandlerAdapter ,其在这个时间点上不会释放消息。
EchoServerHandler#channelReadComplete,这是一个EchoServer小例子:
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
//将未决消息冲刷到远程节点,并且关闭该 Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
消息在 channelReadComplete() 方法中,当 writeAndFlush() 方法被调用时才被释放,我们点进去源码验证一下:
AbstractChannelHandlerContext#writeAndFlush,如下所示,最后的资源是在 writeAndFlush() 中被释放的。
public ChannelFuture writeAndFlush(Object msg) {
return writeAndFlush(msg, newPromise());// 跳转到另外一个重载的方法中
}
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
if (msg == null) {// msg不能为空
throw new NullPointerException("msg");
}
if (isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);// 释放资源(保存消息的ByteBuf)
// cancelled
return promise;
}
write(msg, true, promise);// 异步写操作
return promise;
}
上面的源码中,最后资源是通过 ReferenceCountUtil 来释放的。也就是说,当我们需要释放ByteBuf相关内存的时候,也可以使用 ReferenceCountUtil#release()。
ReferenceCountUtil 底层实现是 ReferenceCounted ,当新的对象初始化的时候计数为1,retain() 方法被调用时引用计数加1,release()方法被调用时引用计数减1,当计数减少到0的时候会被显示清除,再次访问被清除的对象会出现访问冲突(这里想起了JVM判断对象是否存活的引用计数算法)。
ReferenceCountUtil#release:
public static boolean release(Object msg) {
if (msg instanceof ReferenceCounted) {
return ((ReferenceCounted) msg).release();// Decreases the reference count by 1
}
return false;
}
原网址: 访问
创建于: 2023-09-14 16:41:16
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
java windows火焰图_mob64ca12ec8020的技术博客_51CTO博客 - 在windows下不可行,不知道作者是怎样搞的 监听SpringBoot 服务启动成功事件并打印信息_监听springboot启动完毕-CSDN博客 SpringBoot中就绪探针和存活探针_management.endpoint.health.probes.enabled-CSDN博客 u2u转换板 - 嘉立创EDA开源硬件平台 Spring Boot 项目的轻量级 HTTP 客户端 retrofit 框架,快来试试它!_Java精选-CSDN博客 手把手教你打造一套最牛的知识笔记管理系统! - 知乎 - 想法有重合-理论可参考 安宇雨 闲鱼 机械键盘 客制化 开贴记录 文本 linux 使用find命令查找包含某字符串的文件_beijihukk的博客-CSDN博客_find 查找字符串 ---- mac 也适用 安宇雨 打字音 记录集合 B站 bilibili 自行搭建 开坑 真正的客制化 安宇雨 黑苹果开坑 查找工具包maven pom 引用地 工具网站 Dantelis 介绍的玩轴入坑攻略 --- 关于轴的一些说法 --- 非官方 ---- 心得而已 --- 长期开坑更新 [本人问题][新开坑位]关于自动化测试的工具与平台应用 机械键盘 开团 网站记录 -- 能做一个收集的程序就好了 不过现在没时间 -- 信息大多是在群里发的 - 你要让垃圾佬 都去一个地方看难度也是很大的 精神支柱 [超级前台]sprinbboot maven superdesk-app 记录 [信息有用] [环境准备] [基本完成] [sebp/elk] 给已创建的Docker容器增加新的端口映射 - qq_30599553的博客 - CSDN博客 [正在研究] Elasticsearch, Logstash, Kibana (ELK) Docker image documentation elasticsearch centos 安装记录 及 启动手记 正式服务器 39 elasticsearch 问题合集 不断更新 6.1.1 | 6.5.1 两个版本 博客程序 - 测试 - bug记录 等等问题 laravel的启动过程解析 - lpfuture - 博客园 OAuth2 Server PHP 用 Laravel 搭建带 OAuth2 验证的 RESTful 服务 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 利用Laravel 搭建oauth2 API接口 附 Unauthenticated 解决办法 - 煮茶的博客 - SegmentFault 思否 使用 OAuth2-Server-php 搭建 OAuth2 Server - 午时的海 - 博客园 基于PHP构建OAuth 2.0 服务端 认证平台 - Endv - 博客园 Laravel 的 Artisan 命令行工具 Laravel 的文件系统和云存储功能集成 浅谈Chromium中的设计模式--终--Observer模式 浅谈Chromium中的设计模式--二--pre/post和Delegate模式 浅谈Chromium中的设计模式--一--Chromium中模块分层和进程模型 DeepMind 4 Hacking Yourself README.md update 20211011
Laravel China 简书 知乎 博客园 CSDN博客 开源中国 Go Further Ryan是菜鸟 | LNMP技术栈笔记 云栖社区-阿里云 Netflix技术博客 Techie Delight Linkedin技术博客 Dropbox技术博客 Facebook技术博客 淘宝中间件团队 美团技术博客 360技术博客 古巷博客 - 一个专注于分享的不正常博客 软件测试知识传播 - 测试窝 有赞技术团队 阮一峰 语雀 静觅丨崔庆才的个人博客 软件测试从业者综合能力提升 - isTester IBM Java 开发 使用开放 Java 生态系统开发现代应用程序 pengdai 一个强大的博主 HTML5资源教程 | 分享HTML5开发资源和开发教程 蘑菇博客 - 专注于技术分享的博客平台 个人博客-leapMie 流星007 CSDN博客 - 舍其小伙伴 稀土掘金 Go 技术论坛 | Golang / Go 语言中国知识社区
最新评论