Netty学习之旅------NioSocketChannel源码分析之读事件处理逻辑

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

boolean readPendingReset = false;

do {

byteBuf = allocHandle.allocate(allocator); //@5

int writable = byteBuf.writableBytes();

int localReadAmount = doReadBytes(byteBuf); //@6

if (localReadAmount <= 0) { //@7

// not was read release the buffer

byteBuf.release();

byteBuf = null;

close = localReadAmount < 0;

break;

}

if (!readPendingReset) {

readPendingReset = true;

setReadPending(false);

}

pipeline.fireChannelRead(byteBuf); //@8

byteBuf = null;

if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {

// Avoid overflow.

totalReadAmount = Integer.MAX_VALUE;

break;

}

totalReadAmount += localReadAmount;

// stop reading

if (!config.isAutoRead()) {

break;

}

if (localReadAmount < writable) { //@9

// Read less than what the buffer can hold,

// which might mean we drained the recv buffer completely.

break;

}

} while (++ messages < maxMessagesPerRead); //@10

pipeline.fireChannelReadComplete(); //@11

allocHandle.record(totalReadAmount); //@12

if (close) {

closeOnRead(pipeline);

close = false;

}

} catch (Throwable t) {

handleReadException(pipeline, byteBuf, t, close); //@13

} finally {

// Check if there is a readPending which was not processed yet.

// This could be for two reasons:

// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(…) method

// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(…) method

//

// See https://github.com/netty/netty/issues/2254

if (!config.isAutoRead() && !isReadPending()) {

removeReadOp();

}

}

}

代码@1,如果未开启自动读,并且没有读事件等待,则移除读事件。

代码@2,移除读事件,在具体的子类中实现,在AbstractNioUnsafe中的具体实现:

protected final void removeReadOp() {

SelectionKey key = selectionKey();

// Check first if the key is still valid as it may be canceled as part of the deregistration

// from the EventLoop

// See https://github.com/netty/netty/issues/2104

if (!key.isValid()) {

return;

}

int interestOps = key.interestOps();

if ((interestOps & readInterestOp) != 0) {

// only remove readInterestOp if needed

key.interestOps(interestOps & ~readInterestOp);

}

}

代码@3,允许消息预读取最大次数,循环次数,主要是避免一个通道大量数据的读写而拖慢其他通道的处理。

代码@4,内存分配的方式,这里的目的就是可以分配足够的内存字节以便于从通道读取数据。下文会重点讲解。

代码@5,利用代码@4处的分配器,分配一个ByteBuf,用于接收通道中的数据。

代码@6,从通道中读取数据,具体在子类中实现,在NioSocketChannel中的实现如下:

@Override

protected int doReadBytes(ByteBuf byteBuf) throws Exception {

return byteBuf.writeBytes(javaChannel(), byteBuf.writableBytes());//javaChannel()方法返回的就是java.nio.SocketChannel

}

这里涉及到ByteBuf与通道打交道,最终还是会落到java.nio.Channel和java.nio.ByteBuffer上去,所以下文我也觉得有必要深究一下整个API的调用,增强对java.nio原生api的使用。

代码@7,如果没有读到可用数据,则回收刚申请的ByteBuf,如果读到的数据小于0,则说明需要将通道关闭。设置close=true,并跳出循环。

代码@8,将读到的内容(ByteBuf)通过管道传播到各个Handler,此时Handler的处理,默认都会在IO线程中处理。

代码@9,如果本次读到的字节数小于分配ByteBuf的可写字节数,说明该通道已经没有数据可读,结束本次循环。

代码@10,再次从通道中读取,直到读取次数已经超过配置的最大预读取次数,maxMessagesPreRead,如果是ServerChannel或AbstractNioChannel,则默认为16,其他的默认为1。

代码@11,触发读完成事件。

代码@12,该方法非常重要,IO线程将本次读取的字节数反馈给 接收ByteBuf分配器,方便下一次分配合理的ByteBuf,足够用,但不能超过太多。

代码@13,如果发生异常触发异常事件。

对整个通道读事件处理,上述是主要的处理流程,接下来重点分析如下两个方面代码@4: RecvByteBufAllocator和代码@8

2.1 RecvByteBuffAllocator系列类分析

/**

  • Allocates a new receive buffer whose capacity is probably large enough to read all inbound data and small enough

  • not to waste its space.

*/

public interface RecvByteBufAllocator {

/**

  • Creates a new handle. The handle provides the actual operations and keeps the internal information which is

  • required for predicting an optimal buffer capacity.

*/

Handle newHandle();

interface Handle {

/**

  • Creates a new receive buffer whose capacity is probably large enough to read all inbound data and small

  • enough not to waste its space.

*/

ByteBuf allocate(ByteBufAllocator alloc);

/**

  • Similar to {@link #allocate(ByteBufAllocator)} except that it does not allocate anything but just tells the

  • capacity.

*/

int guess();

/**

  • Records the the actual number of read bytes in the previous read operation so that the allocator allocates

  • the buffer with potentially more correct capacity.

  • @param actualReadBytes the actual number of read bytes in the previous read operation

*/

void record(int actualReadBytes);

}

}

该接口主要要解决的问题就是:在处理通道读事件的时候,如何确定需要分配多大的ByteBuf呢?

对如下英文进行通俗化的理解:

  1. Allocates a new receive buffer whose capacity is probably large enough to read all inbound data and s

mall enough

* not to waste its space.

分配一个接收ByteBuf,希望这个容量足够大,能够容纳通道中可读数据,但又尽量少,够用就好,别浪费空间。

2)guess 方法,只返回猜测,建议的容量(capacity),不执行实际的内存申请。

3)Records the the actual number of read bytes in the previous read operation so that the allocator allocates the buffer with potentially more correct capacity.

记录上传读到的实际字节的大小,以便分配器更加准确的分配正确的容量。

2.1.1 FixedRecvByteBufAllocator  固定容量分配,该方式简单,但忽略了IO线程的反馈。

/**

* The {@link RecvByteBufAllocator} that always yields the same buffer

* size prediction.  This predictor ignores the feed back from the I/O thread.

*/

public class FixedRecvByteBufAllocator implements RecvByteBufAllocator

2.1.2 AdaptiveRecvByteBufAllocator 自适应分配算法

1)概述

/**

* The {@link RecvByteBufAllocator} that automatically increases and

* decreases the predicted buffer size on feed back.

* It gradually increases the expected number of readable bytes if the previous

* read fully filled the allocated buffer.  It gradually decreases the expected

* number of readable bytes if the read operation was not able to fill a certain

* amount of the allocated buffer two times consecutively.  Otherwise, it keeps

* returning the same prediction.

*/

AdaptiveRecvByteBufAllocator 根据IO线程的反馈自动增加或减少预期的buffer size。

如果上一次读填满了分配的缓存区,则增大猜测缓存区的大小,如果上一次读没有填满分配的缓冲区,则减少。

2)AdaptiveRecvByteBufAllocator 源码分析

1、核心属性详解

static final int DEFAULT_MINIMUM = 64; // 分配的最小容量

static final int DEFAULT_INITIAL = 1024; // 初始容量

static final int DEFAULT_MAXIMUM = 65536; // 最大容量64k

private static final int INDEX_INCREMENT = 4; //增长索引数,在SIZE_TABLE下的索引

private static final int INDEX_DECREMENT = 1; // 减少索引数

private static final int[] SIZE_TABLE; //小于512个字节,按16个字节递增,大于512字节,成倍增长

// SIZE_TABLE的初始化,其实这里有效使用的最大内存为65536, 64K

static {

List sizeTable = new ArrayList();

for (int i = 16; i < 512; i += 16) { //小于512字节,从16开始,以16递增

sizeTable.add(i);

}

for (int i = 512; i > 0; i <<= 1) { //大于512字节,成倍增长,该循环在超过int最大值时退出

sizeTable.add(i);

}

SIZE_TABLE = new int[sizeTable.size()];

for (int i = 0; i < SIZE_TABLE.length; i ++) {

SIZE_TABLE[i] = sizeTable.get(i);

}

}

private final int minIndex; // DEFAULT_MINIMUM 所在SIZE_TABLE中的下标

private final int maxIndex; // DEFAULT_MAXIMUM 所在SIZE_TABLE中的下标

private final int initial; // 初始时,分配的buffer大小,默认为1K

2、构造方法

/**

  • Creates a new predictor with the default parameters. With the default

  • parameters, the expected buffer size starts from {@code 1024}, does not

  • go down below {@code 64}, and does not go up above {@code 65536}.

*/

private AdaptiveRecvByteBufAllocator() {

this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);

}

/**

  • Creates a new predictor with the specified parameters.

  • @param minimum the inclusive lower bound of the expected buffer size

  • @param initial the initial buffer size when no feed back was received

  • @param maximum the inclusive upper bound of the expected buffer size

最后

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

  • Java基础部分

  • 算法与编程

  • 数据库部分

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
数据库部分

[外链图片转存中…(img-urGScAwq-1713567022935)]

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

[外链图片转存中…(img-6HOm3ulm-1713567022935)]

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-4fVxnxxe-1713567022936)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 26
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值