有时候会有一系列的处理in的handler,使用fireChannelRead处理传递

转载自https://blog.csdn.net/u011702633/article/details/82051329

Netty源码解析(八) —— channel的read操作

2018年08月25日 14:41:47 靛蓝忆 阅读数:1431
 

客户端channel在建立连接之后会关注read事件,那么read事件在哪触发的呢? 
NioEventLoop中

            /**
* 读事件和 accept事件都会经过这里,但是拿到的unsafe对象不同 所以后续执行的read操作也不一样
* NioServerChannel进行accept操作
* NioChannel进行read操作
*/
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read

        @Override
public final void read() {
final ChannelConfig config = config();
//若 inputClosedSeenErrorOnRead = true ,移除对 SelectionKey.OP_READ 事件的感兴趣。
if (shouldBreakReadReady(config)) {
//清楚读事件
clearReadPending();
return;
} final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
//获取并重置allocHandle对象
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config); ByteBuf byteBuf = null;
boolean close = false;//是否关闭
try {
do {
//申请bytebuf
byteBuf = allocHandle.allocate(allocator);
//读取数据,设置最后读取字节数
allocHandle.lastBytesRead(doReadBytes(byteBuf));
//没读到数据
if (allocHandle.lastBytesRead() <= 0) {
// 梅毒到数据 释放buf
byteBuf.release();
byteBuf = null;
//如果最后读取的字节为小于 0 ,说明对端已经关闭
close = allocHandle.lastBytesRead() < 0;
if (close) {
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
} //读到了数据
allocHandle.incMessagesRead(1);
readPending = false;
//通知pipline read事件
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());//循环判断是否继续读取 //读取完成
allocHandle.readComplete();
//通知pipline读取完成
pipeline.fireChannelReadComplete(); if (close) {//关闭连接
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
.....
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
  1. 若 inputClosedSeenErrorOnRead = true ,移除对 SelectionKey.OP_READ 事件的感兴趣。
  2. 获取并重置allocHandle对象(代理alloctor的一些功能)
  3. 读取数据,设置最后读取字节数
  4. 通知pipline read事件
  5. 通知pipline读取完成 
    这次先跟着主线走,然后再回头看细节,直接定位到事件通知 
    io.netty.channel.DefaultChannelPipeline#fireChannelRead
    /**
* 有数据读入的时候会调用(InBound) 也可以手动调用
* @param msg 客户端连接的channel
* @return
*/
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
//pipline节点类的静态方法 穿进去的head
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

最后调用到io.netty.channel.DefaultChannelPipeline.HeadContext#channelRead

        /**
* 被调用read 可以向下传递
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

ctx.fireChannelRead(msg);向下个节点传递,如果自定义了handler来处理就可以拦截channelRead的bytebuf数据来进行处理,负责一直向下传递到TailContext节点处理 
io.netty.channel.DefaultChannelPipeline.TailContext#channelRead

        @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //如果不去自定义handler处理byteBuf 最终会到TailContext 来处理
onUnhandledInboundMessage(msg);
}
protected void onUnhandledInboundMessage(Object msg) {
try {
//日志提醒没有处理
logger.debug(
"Discarded inbound message {} that reached at the tail of the pipeline. " +
"Please check your pipeline configuration.", msg);
} finally {
//释放byteBuf内存
ReferenceCountUtil.release(msg);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

数据到达TailContext节点之后,再onUnhandledInboundMessage方法中打印数据未处理日志,然后释放bytebuf内存 
io.netty.channel.nio.AbstractNioByteChannel#doReadBytes读取操作的方法

    @Override
/**
* 读取数据
*/
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
//获取handle对象
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
//设置读索引=写索引
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
//读取无数据到buf
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. allocHandle.lastBytesRead()小于0证明没有读取到数据,要释放bytebuf
  2. 读取到数据,allocHandle.incMessagesRead(1);
  3. allocHandle.continueReading() 循环判断是否继续读取

        @Override
public boolean continueReading() {
return continueReading(defaultMaybeMoreSupplier);
} @Override
public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
// Keep reading if we are allowed to read more bytes, and our last read filled up the buffer we provided.
//最后读取的字节数大于0 并且等于最大可写入的字节数
return bytesToRead > 0 && maybeMoreDataSupplier.get();
}
private final UncheckedBooleanSupplier defaultMaybeMoreSupplier = new UncheckedBooleanSupplier() {
@Override
public boolean get() {
//最后读取的字节数 是否等于最大可写入字节数
return attemptedBytesRead == lastBytesRead;
}
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

最后读取的字节数大于0,并且最后读取的数据==尝试可写入的大小,即证明可以继续读取 
出现异常时候调用io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#handleReadException

        private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close,
RecvByteBufAllocator.Handle allocHandle) {
if (byteBuf != null) {
if (byteBuf.isReadable()) {
readPending = false;
//把已经读取到的数据 通知到pipline中
pipeline.fireChannelRead(byteBuf);
} else {
//释放bytebuf
byteBuf.release();
}
}
//读取完成
allocHandle.readComplete();
//通知pipline读取完成
pipeline.fireChannelReadComplete();
//通知pipline异常
pipeline.fireExceptionCaught(cause);
if (close || cause instanceof IOException) {
closeOnRead(pipeline);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

io.netty.channel.DefaultChannelPipeline#fireExceptionCaught

    @Override
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
return this;
}

最终调用到TailContext的io.netty.channel.DefaultChannelPipeline.TailContext#exceptionCaught

        @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
onUnhandledInboundException(cause);
}
protected void onUnhandledInboundException(Throwable cause) {
try {
logger.warn(
"An exceptionCaught() event was fired, and it reached at the tail of the pipeline. " +
"It usually means the last handler in the pipeline did not handle the exception.",
cause);
} finally {
ReferenceCountUtil.release(cause);
}
}

和读取操作一样,最终也是要再发生异常的时候释放buf的内存

netty之handler read的更多相关文章

  1. 7.Netty中 handler 的执行顺序

    1.Netty中handler的执行顺序 Handler在Netty中,无疑占据着非常重要的地位.Handler与Servlet中的filter很像,通过Handler可以完成通讯报文的解码编码.拦截 ...

  2. netty学习--handler传递

    在netty中的处理链pipeline中,事件是按顺序传递的,把自己拟人为netty程序,针对进来(inbound)的请求,会从head开始,依次往tail传递. pipeline采用了链表结构,he ...

  3. netty(七) Handler的执行顺序

    Handler在netty中,无疑占据着非常重要的地位.Handler与Servlet中的filter很像,通过Handler可以完成通讯报文的解码编码.拦截指定的报文.统一对日志错误进行处理.统一对 ...

  4. netty之handler write

    转载自:https://blog.csdn.net/FishSeeker/article/details/78447684 实验过,例如channle的handler里有很多个outhandler,在 ...

  5. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

  6. Netty之有效规避内存泄漏

    有过痛苦的经历,特别能写出深刻的文章 —— 凯尔文. 肖 直接内存是IO框架的绝配,但直接内存的分配销毁不易,所以使用内存池能大幅提高性能,也告别了频繁的GC.但,要重新培养被Java的自动垃圾回收惯 ...

  7. 【Netty学习】 ChannelInitializer 学习

    ChannelInitializer在Netty中是一个很重要的东西.也是4.x版本中用户接触比较多的一个类 它本身是继承ChannelInboundHandlerAdapter的.实现Channel ...

  8. 【Netty学习】Netty 4.0.x版本和Flex 4.6配合

    笔者的男装网店:http://shop101289731.taobao.com .冬装,在寒冷的冬季温暖你.新品上市,环境选购 =================================不华丽 ...

  9. 【转】Netty那点事(三)Channel中的Pipeline

    [原文]https://github.com/code4craft/netty-learning/blob/master/posts/ch3-pipeline.md Channel是理解和使用Nett ...

随机推荐

  1. 秒杀 2Sum 3Sum 4Sum 算法题

    2 Sum 这题是 Leetcode 的第一题,相信大部分小伙伴都听过的吧. 作为一道标着 Easy 难度的题,它真的这么简单吗? 我在之前的刷题视频里说过,大家刷题一定要吃透一类题,为什么有的人题目 ...

  2. 关于Java的对象,锁和对象的内存布局、访问定位

    1. 对象的创建和分配 创建对象(如克隆.反序列化)通常仅仅一个new关键字,但在虚拟机中,对象的创建的过程需要如下步骤: 类加载检查 先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并 ...

  3. (转)文件上传org.apache.tomcat.util.http.fileupload.FileUploadException: Stream closed

    文件上传时,tomcat报错org.springframework.web.multipart.MultipartException: Failed to parse multipart servle ...

  4. keil5 使用JLink 向nrf52840DK 下载程序出现No Cortex-M SW Device Found

    今天打开52840 keil5 工程,下载程序无法下载成功,提示如下: 在Jlink 配置中(Option for target....-> Debug->Setting )SWDIO也无 ...

  5. CSS动画实例:移动的眼珠子

    适当地利用CSS的box-shadow可以构造图形,然后可以对构造的图形添加动画效果.下面我们通过移动的眼珠子.圆珠一二三.一分为四.四小圆旋转扩散等实例来体会box-shadow属性在动画制作中的使 ...

  6. 5 个 Git 工作流,改善你的开发流程

    原文地址:5 Git workflows you can use to deliver better code and improve your development process 原文作者:Vi ...

  7. Leetcode 24. Swap Nodes in Pairs(详细图解一看就会)

    题目内容 Given a linked list, swap every two adjacent nodes and return its head. You may not modify the ...

  8. 区块链入门到实战(27)之以太坊(Ethereum) – 智能合约开发

    智能合约的优点 与传统合同相比,智能合约有一些显著优点: 不需要中间人 费用低 代码就是规则 区块链网络中有多个备份,不用担心丢失 避免人工错误 无需信任,就可履行协议 匿名履行协议 以太坊(Ethe ...

  9. Charles 断点修改Response

    前言: 我们可以通过map功能进行重定向,但如果同一个域名进行的是不同请求与返回.此时map在这里就不适用了. 我们可以通关对某一请求进行断点,在进行修改请求或者返回.这样就可以满足我们的需求了. 一 ...

  10. 使用Arcgis时,在sde空间库常用的相关函数

    一.Oracle库中配置好sde空间库常见的场景 1.在sde库中创建表:community 创建表:community 字段:id(INTEGER), shape(ST_GEOMETRY) 2.往s ...