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 的过程的更多相关文章

  1. Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程

    Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...

  2. Hadoop基础-HDFS的读取与写入过程剖析

    Hadoop基础-HDFS的读取与写入过程剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客会简要介绍hadoop的写入过程,并不会设计到源码,我会用图和文字来描述hdf ...

  3. Hadoop基础-HDFS的读取与写入过程

    Hadoop基础-HDFS的读取与写入过程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 为了了解客户端及与之交互的HDFS,NameNode和DataNode之间的数据流是什么样 ...

  4. Netty(一):ByteBuf读写过程图解

    我们知道ByteBuf通过读写两个索引分离,避免了NIO中ByteBuffer中读写模式切换时,需要flip等繁琐的操作. 今天就通过一段测试代码以及图例来直观的了解下ByteBuf中的readInd ...

  5. Java SocketChannel 读取ByteBuffer字节的处理模型

    在JAVA中的流分为字节流或字符流,一般来说采用字符流处理起来更加方便.字节流处理起来相对麻烦,SocketChannel中将数据读取到ByteBuffer中,如何取出完整的一行数据(使用CRLF分隔 ...

  6. 【原创】大数据基础之Spark(7)spark读取文件split过程(即RDD分区数量)

    spark 2.1.1 spark初始化rdd的时候,需要读取文件,通常是hdfs文件,在读文件的时候可以指定最小partition数量,这里只是建议的数量,实际可能比这个要大(比如文件特别多或者特别 ...

  7. MapReduce: map读取文件的过程

    我们的输入文件 hello0, 内容如下: xiaowang 28 shanghai@_@zhangsan 38 beijing@_@someone 100 unknown 逻辑上有3条记录, 它们以 ...

  8. Maven工程读取properties文件过程

    1.创建需要读取的properties文件 2.在xml文件中加载配置文件 <!-- 加载配置文件 --> <context:property-placeholder locatio ...

  9. 使用ReadStream方法读取文件事件传递过程

    const fs = require('fs'); let file = fs.createReadStream("filename.js"); file.on("ope ...

随机推荐

  1. Insomni'hack teaser 2019 - Pwn - 1118daysober

    参考链接 https://ctftime.org/task/7459 Linux内核访问用户空间文件:get_fs()/set_fs()的使用 漏洞的patch信息 https://maltekrau ...

  2. 详解InitializingBean、initMethod和@PostConstruct

    转载:https://blog.csdn.net/nrsc272420199/article/details/95033223 1. InitializingBean.initMethod和@Post ...

  3. Maven项目jar和war部署

    Spring Boot可以打成jar包,也可以打成war包.启动方式类有main函数做为入口,比如:java -jar xxx.jar.Spring Boot默认内置Tomcat服务器,当然也可以将w ...

  4. easyuUI实现客户分页显示逻辑分析

    页面 前端 前端easyUI,自带分页功能,添加pagination属性 前端会传给后端两个属性: page:当前页码 rows:每页显示记录数 后端 接收page和rows参数 根据参数分页查询 获 ...

  5. STM32开发板的TIM3开启和关闭

    关闭定时器中断要考虑好多情况 1)关闭定时器时,定时器是否在处在工作状态 2)关闭定时器时,定时器是否正好进入中断,造成关闭程序出现断层,进而无法实现完整关闭程序,此时可以使用高一级别的外部中断强制进 ...

  6. 【rust】rust安装,运行第一个Rust 程序 (1)

    安装 Rust 在 Unix 类系统如 Linux 和 macOS 上,打开终端并输入: curl https://sh.rustup.rs -sSf | sh 回车后安装过程出现如下显示: info ...

  7. Jquery TextBox进入focus的时候 改变background-color

    <!DOCTYPE html><html><head><script src="/jquery/jquery-1.11.1.min.js" ...

  8. LUOGU P2569 [SCOI2010]股票交易(单调队列优化dp)

    传送门 解题思路 不难想一个\(O(n^3)\)的\(dp\),设\(f_{i,j}\)表示第\(i\)天,手上有\(j\)股的最大收益,因为这个\(dp\)具有单调性,所以\(f_i\)可以贪心的直 ...

  9. 小程序封装wx.request,以及调用

    1.新建一个api目录,与pages同级 2.在api目录下新建一个api.js文件 3.编写代码 const host = 'http://test.test.cn' const wxRequest ...

  10. ORACLE表空间offline谈起,表空间备份恢复

    从ORACLE表空间offline谈起,表空间备份恢复将表空间置为offline,可能的原因包括维护.备份恢复等目的:表空间处于offline状态,那么Oracle不会允许任何对该表空间中对象的SQL ...