SocketChannel 读取ByteBuf 的过程
SocketChannel 读取ByteBuf 的过程:
我们首先看NioEventLoop 的processSelectedKey 方法:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
//获取到channel 中的unsafe
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
//如果这个key 不是合法的, 说明这个channel 可能有问题
if (!k.isValid()) {
//代码省略
}
try {
//如果是合法的, 拿到key 的io 事件
int readyOps = k.readyOps();
//链接事件
if ((readyOps & SelectionKey.OP_CONNECT) != ) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
//写事件
if ((readyOps & SelectionKey.OP_WRITE) != ) {
ch.unsafe().forceFlush();
}
//读事件和接受链接事件
//如果当前NioEventLoop 是work 线程的话, 这里就是op_read 事件
//如果是当前NioEventLoop 是boss 线程的话, 这里就是op_accept 事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != || readyOps == ) {
unsafe.read();
if (!ch.isOpen()) {
return;
}
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) 这里的判断表示轮询到事件是OP_READ 或者OP_ACCEPT 事件。之前我们分析过, 如果当前NioEventLoop 是work 线程的话, 那么这里就是OP_READ 事件, 也就是读事件, 表示客户端发来了数据流,这里会调用unsafe 的redis()方法进行读取。那么这里的channel 是NioServerSocketChannel, 其绑定的unsafe 是NioByteUnsafe, 这里会走进NioByteUnsafe 的read()方法中
public final void read() {
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config); ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= ) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < ;
break;
} allocHandle.incMessagesRead();
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete(); if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
首先获取SocketChannel 的config, pipeline 等相关属性,final ByteBufAllocator allocator = config.getAllocator(); 这一步是获取一个ByteBuf 的内存分配器, 用于分配ByteBuf。这里会走到DefaultChannelConfig 的getAllocator 方法中:
public ByteBufAllocator getAllocator() {
return allocator;
}
这里返回的DefualtChannelConfig 的成员变量, 我们看这个成员变量:
private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
这里调用ByteBufAllocator 的属性DEFAULT, 跟进去:
ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
我们看到这里又调用了ByteBufUtil 的静态属性DEFAULT_ALLOCATOR, 再跟进去:
static final ByteBufAllocator DEFAULT_ALLOCATOR;
DEFAULT_ALLOCATOR 这个属性是在static 块中初始化的,我们跟到static 块中:
static {
String allocType = SystemPropertyUtil.get(
"io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
allocType = allocType.toLowerCase(Locale.US).trim(); ByteBufAllocator alloc;
if ("unpooled".equals(allocType)) {
alloc = UnpooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: {}", allocType);
} else if ("pooled".equals(allocType)) {
alloc = PooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: {}", allocType);
} else {
alloc = PooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
} DEFAULT_ALLOCATOR = alloc; THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", * );
logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE); MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", * );
logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
}
首先判断运行环境是不是安卓, 如果不是安卓, 在返回"pooled"字符串保存在allocType 中,然后通过if 判断, 最后局部变量alloc = PooledByteBufAllocator.DEFAULT, 最后将alloc 赋值到成员变量DEFAULT_ALLOCATOR , 我们跟到PooledByteBufAllocator 的DEFAULT 属性中:
public static final PooledByteBufAllocator DEFAULT =
new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());
我们看到这里直接通过new 的方式, 创建了一个PooledByteBufAllocator 对象, 也就是基于申请一块连续内存进行缓冲区分配的缓冲区分配器。缓冲区分配器的知识, 我们在前面的章节进行了详细的剖析, 这里就不再赘述。回到NioByteUnsafe 的read()方法中:
public final void read() {
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config); ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= ) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < ;
break;
} allocHandle.incMessagesRead();
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete(); if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
这里ByteBufAllocator allocator = config.getAllocator()中的allocator , 就是PooledByteBufAllocator。final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle() 是创建一个handle, 我们之前的章节讲过,handle 是对RecvByteBufAllocator 进行实际操作的对象,我们跟进recvBufAllocHandle:
public RecvByteBufAllocator.Handle recvBufAllocHandle() {
if (recvHandle == null) {
recvHandle = config().getRecvByteBufAllocator().newHandle();
}
return recvHandle;
}
这里是我们之前剖析过的逻辑, 如果不存在, 则创建handle 的实例。同样allocHandle.reset(config)是将配置重置。重置完配置之后, 进行do-while循环, 有关循环终止条件allocHandle.continueReading()。在do-while 循环中, 首先看byteBuf = allocHandle.allocate(allocator) 这一步, 这里传入了刚才创建的allocate 对象, 也就是PooledByteBufAllocator,这里会进入DefaultMaxMessagesRecvByteBufAllocator 类的allocate()方法中:
public ByteBuf allocate(ByteBufAllocator alloc) {
return alloc.ioBuffer(guess());
}
这里的guess 方法, 会调用AdaptiveRecvByteBufAllocator 的guess 方法:
public int guess() {
return nextReceiveBufferSize;
}
这里会返回AdaptiveRecvByteBufAllocator 的成员变量nextReceiveBufferSize, 也就是下次所分配缓冲区的大小, 第一次分配的时候会分配初始大小, 也就是1024 字节。这样, alloc.ioBuffer(guess())就会分配一个PooledByteBuf,我们跟到AbstractByteBufAllocator 的ioBuffer 方法中:
public ByteBuf ioBuffer(int initialCapacity) {
if (PlatformDependent.hasUnsafe()) {
return directBuffer(initialCapacity);
}
return heapBuffer(initialCapacity);
}
这里首先判断是否能获取jdk 的unsafe 对象, 默认为true, 所以会走到directBuffer(initialCapacity)中, 这里最终会分配一个PooledUnsafeDirectByteBuf 对象。回到NioByteUnsafe 的read()方法中,分配完了ByteBuf 之后, 再看这一步allocHandle.lastBytesRead(doReadBytes(byteBuf))。首先看参数doReadBytes(byteBuf)方法, 这步是将channel 中的数据读取到我们刚分配的ByteBuf 中, 并返回读取到的字节数,这里会调用到NioSocketChannel 的doReadBytes()方法:
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
首先拿到绑定在channel 中的handler, 因为我们已经创建了handle, 所以这里会直接拿到。再看allocHandle.attemptedBytesRead(byteBuf.writableBytes())这步, byteBuf.writableBytes()返回byteBuf 的可写字节数,也就是最多能从channel 中读取多少字节写到ByteBuf, allocate 的attemptedBytesRead 会把可写字节数设置到DefaultMaxMessagesRecvByteBufAllocator 类的attemptedBytesRead 属性中, 跟到DefaultMaxMessagesRecvByteBufAllocator 中的attemptedBytesRead 我们会看到:
public void attemptedBytesRead(int bytes) {
attemptedBytesRead = bytes;
}
继续看doReadBytes()方法。往下看最后, 通过byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead())将jdk 底层的channel 中的数据写入到我们创建的ByteBuf 中, 并返回实际写入的字节数。回到NioByteUnsafe 的read()方法中继续看allocHandle.lastBytesRead(doReadBytes(byteBuf))这步,刚才我们剖析过doReadBytes(byteBuf)返回的是实际写入ByteBuf 的字节数, 再看lastBytesRead() 方法, 跟到DefaultMaxMessagesRecvByteBufAllocator 的lastBytesRead()方法中:
public final void lastBytesRead(int bytes) {
lastBytesRead = bytes;
// Ignore if bytes is negative, the interface contract states it will be detected externally after call.
// The value may be "invalid" after this point, but it doesn't matter because reading will be stopped.
totalBytesRead += bytes;
if (totalBytesRead < ) {
totalBytesRead = Integer.MAX_VALUE;
}
}
这里会赋值两个属性, lastBytesRead 代表最后读取的字节数, 这里赋值为我们刚才写入ByteBuf 的字节数,totalBytesRead 表示总共读取的字节数, 这里将写入的字节数追加。继续来到NioByteUnsafe 的read()方法,如果最后一次读取数据为0, 说明已经将channel 中的数据全部读取完毕, 将新创建的ByteBuf 释放循环利用, 并跳出循环。allocHandle.incMessagesRead(1)这步是增加消息的读取次数, 因为我们循环最多16 次, 所以当增加消息次数增加到16会结束循环。读取完毕之后, 会通过pipeline.fireChannelRead(byteBuf)将传递channelRead 事件, 有关channelRead事件我们在前面的章节也进行了详细的剖析。至此,小伙伴们应该有个疑问, 如果一次读取不完, 就传递channelRead 事件, 那么server 接收到的数据有可能就是不完整的, 其实关于这点, Netty 也做了相应的处理, 我们会在之后的章节详细剖析Netty 的半包处理机制。循环结束后,会执行到allocHandle.readComplete()这一步。
我们知道第一次分配ByteBuf 的初始容量是1024, 但是初始容量不一定一定满足所有的业务场景, netty 中, 将每次读取数据的字节数进行记录, 然后之后次分配ByteBuf 的时候, 容量会尽可能的符合业务场景所需要大小, 具体实现方式,就是在readComplete()这一步体现的。我们跟到AdaptiveRecvByteBufAllocator 的readComplete()方法中:
public void readComplete() {
record(totalBytesRead());
}
这里调用了record()方法, 并且传入了这一次所读取的字节总数,跟到record()方法中:
private void record(int actualReadBytes) {
if (actualReadBytes <= SIZE_TABLE[Math.max(, index - INDEX_DECREMENT - )]) {
if (decreaseNow) {
index = Math.max(index - INDEX_DECREMENT, minIndex);
nextReceiveBufferSize = SIZE_TABLE[index];
decreaseNow = false;
} else {
decreaseNow = true;
}
} else if (actualReadBytes >= nextReceiveBufferSize) {
index = Math.min(index + INDEX_INCREMENT, maxIndex);
nextReceiveBufferSize = SIZE_TABLE[index];
decreaseNow = false;
}
}
首先看判断条件if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) 。这里index 是当前分配的缓冲区大小所在的SIZE_TABLE 中的索引, 将这个索引进行缩进, 然后根据缩进后的所以找出SIZE_TABLE中所存储的内存值, 再判断是否大于等于这次读取的最大字节数, 如果条件成立, 说明分配的内存过大, 需要缩容操作,我们看if 块中缩容相关的逻辑。首先if (decreaseNow) 会判断是否立刻进行收缩操作, 通常第一次不会进行收缩操作,然后会将decreaseNow 设置为true, 代表下一次直接进行收缩操作。假设需要立刻进行收缩操作, 我们看收缩操作的相关逻辑:
index = Math.max(index - INDEX_DECREMENT, minIndex) 这一步将索引缩进一步, 但不能小于最小索引值;然后通过nextReceiveBufferSize = SIZE_TABLE[index] 获取设置索引之后的内存, 赋值在nextReceiveBufferSize, 也就是下次需要分配的大小, 下次就会根据这个大小分配ByteBuf 了, 这样就实现了缩容操作。再看else if (actualReadBytes >= nextReceiveBufferSize) ,这里判断这次读取字节的总量比上次分配的大小还要大,则进行扩容操作。扩容操作也很简单, 索引步进, 然后拿到步进后的索引所对应的内存值, 作为下次所需要分配的大小在NioByteUnsafe 的read()方法中,经过了缩容或者扩容操作之后, 通过pipeline.fireChannelReadComplete()传播ChannelReadComplete()事件,以上就是读取客户端消息的相关流程。
SocketChannel 读取ByteBuf 的过程的更多相关文章
- Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程
Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...
- Hadoop基础-HDFS的读取与写入过程剖析
Hadoop基础-HDFS的读取与写入过程剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客会简要介绍hadoop的写入过程,并不会设计到源码,我会用图和文字来描述hdf ...
- Hadoop基础-HDFS的读取与写入过程
Hadoop基础-HDFS的读取与写入过程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 为了了解客户端及与之交互的HDFS,NameNode和DataNode之间的数据流是什么样 ...
- Netty(一):ByteBuf读写过程图解
我们知道ByteBuf通过读写两个索引分离,避免了NIO中ByteBuffer中读写模式切换时,需要flip等繁琐的操作. 今天就通过一段测试代码以及图例来直观的了解下ByteBuf中的readInd ...
- Java SocketChannel 读取ByteBuffer字节的处理模型
在JAVA中的流分为字节流或字符流,一般来说采用字符流处理起来更加方便.字节流处理起来相对麻烦,SocketChannel中将数据读取到ByteBuffer中,如何取出完整的一行数据(使用CRLF分隔 ...
- 【原创】大数据基础之Spark(7)spark读取文件split过程(即RDD分区数量)
spark 2.1.1 spark初始化rdd的时候,需要读取文件,通常是hdfs文件,在读文件的时候可以指定最小partition数量,这里只是建议的数量,实际可能比这个要大(比如文件特别多或者特别 ...
- MapReduce: map读取文件的过程
我们的输入文件 hello0, 内容如下: xiaowang 28 shanghai@_@zhangsan 38 beijing@_@someone 100 unknown 逻辑上有3条记录, 它们以 ...
- Maven工程读取properties文件过程
1.创建需要读取的properties文件 2.在xml文件中加载配置文件 <!-- 加载配置文件 --> <context:property-placeholder locatio ...
- 使用ReadStream方法读取文件事件传递过程
const fs = require('fs'); let file = fs.createReadStream("filename.js"); file.on("ope ...
随机推荐
- CSRF拦截
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF. 事实上 ...
- [Comet OJ - Contest #9 & X Round 3] Namid[A]me
传送门 一开始读错题了,以为是\(\sum_{1\leq u\leq v\leq n}f(u,v)\),还疑惑这题这么简单怎么没人做( 实际上是\(\sum_{1\leq u\leq v\leq n} ...
- js原生高逼格插件
如何定义一个高逼格的原生JS插件 作为一个前端er,如果不会写一个小插件,都不好意思说自己是混前端界的.写还不能依赖jquery之类的工具库,否则装得不够高端.那么,如何才能装起来让自己看起来逼格更高 ...
- 《SaltStack技术入门与实践》——执行结果处理
执行结果处理 本章节参考<SaltStack技术入门与实践>,感谢该书作者: 刘继伟.沈灿.赵舜东 Return组件可以理解为SaltStack系统对执行Minion返回后的数据进行存储或 ...
- 【ZJOI2008】树的统计
题目 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: ...
- 【NOIP2016提高A组模拟7.17】寻找
题目 Bob和Alice出去度蜜月,但Alice不慎走失,Bob在伤心过后,决定前去寻找Alice. 他们度蜜月的地方是一棵树,共有N个节点,Bob会使用下列DFS算法对该树进行遍历. startin ...
- Mike的农场
题目 Mike有一个农场,这个农场n个牲畜围栏,现在他想在每个牲畜围栏中养一只动物,每只动物可以是牛或羊,并且每个牲畜围栏中的饲养条件都不同,其中第i个牲畜围栏中的动物长大后,每只牛可以卖a[i]元, ...
- LeetCode 1143 最长公共子序列
链接:https://leetcode-cn.com/problems/longest-common-subsequence 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序 ...
- M(model)V(view)C(controller,serlvet),(分) 静态工厂模式,单例模式
- POJ 3261 Milk Patterns ( 后缀数组 && 出现k次最长可重叠子串长度 )
题意 : 给出一个长度为 N 的序列,再给出一个 K 要求求出出现了至少 K 次的最长可重叠子串的长度 分析 : 后缀数组套路题,思路是二分长度再对于每一个长度进行判断,判断过程就是对于 Height ...