我的编程空间,编程开发者的网络收藏夹
学习永远不晚

对于Netty ByteBuf的零拷贝的理解是怎样的

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

对于Netty ByteBuf的零拷贝的理解是怎样的

这篇文章给大家介绍对于Netty ByteBuf的零拷贝的理解是怎样的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

根据 Wiki 对 Zero-copy 的定义:

"Zero-copy" describes computer operations in which the CPU does not perform  the task of copying data from one memory area to another. This is frequently  used to save CPU cycles and memory bandwidth when transmitting a file over a  network.

即所谓的 Zero-copy, 就是在操作数据时, 不需要将数据 buffer 从一个内存区域拷贝到另一个内存区域. 因为少了一次内存的拷贝, 因此  CPU 的效率就得到的提升.

在 OS 层面上的 Zero-copy 通常指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据. 例如  Linux 提供的mmap 系统调用, 它可以将一段用户空间内存映射到内核空间, 当映射成功后, 用户对这段内存区域的修改可以直接反映到内核空间; 同样地,  内核空间对这段区域的修改也直接反映用户空间. 正因为有这样的映射关系, 我们就不需要在 用户态(User-space) 与 内核态(Kernel-space)  之间拷贝数据, 提高了数据传输的效率.

而需要注意的是, Netty 中的 Zero-copy 与上面我们所提到到 OS 层面上的 Zero-copy 不太一样, Netty的  Zero-coyp 完全是在用户态(Java 层面)的, 它的 Zero-copy 的更多的是偏向于 优化数据操作 这样的概念.

Netty 的 Zero-copy 体现在如下几个个方面:

  • Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf  之间的拷贝.

  • 通过 wrap 操作, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象,  进而避免了拷贝操作.

  • ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝.

  • 通过 FileRegion 包装的FileChannel.tranferTo 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel,  避免了传统通过循环 write 方式导致的内存拷贝问题.

下面我们就来简单了解一下这几种常见的零拷贝操作.

通过 CompositeByteBuf 实现零拷贝

假设我们有一份协议数据, 它由头部和消息体组成, 而头部和消息体是分别存放在两个 ByteBuf 中的, 即:

ByteBuf header = ... ByteBuf body = ...

我们在代码处理中, 通常希望将 header 和 body 合并为一个 ByteBuf, 方便处理, 那么通常的做法是:

ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes()); allBuf.writeBytes(header); allBuf.writeBytes(body);

可以看到, 我们将 header 和 body 都拷贝到了新的 allBuf 中了, 这无形中增加了两次额外的数据拷贝操作了.

那么有没有更加高效优雅的方式实现相同的目的呢? 我们来看一下 CompositeByteBuf 是如何实现这样的需求的吧.

ByteBuf header = ... ByteBuf body = ... CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); compositeByteBuf.addComponents(true, header, body);

上面代码中, 我们定义了一个 CompositeByteBuf 对象, 然后调用

public CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers) { ... }

方法将 header 与 body 合并为一个逻辑上的 ByteBuf, 即:

对于Netty ByteBuf的零拷贝的理解是怎样的

不过需要注意的是, 虽然看起来 CompositeByteBuf 是由两个 ByteBuf 组合而成的, 不过在 CompositeByteBuf 内部,  这两个 ByteBuf 都是单独存在的, CompositeByteBuf 只是逻辑上是一个整体.

上面 CompositeByteBuf 代码还以一个地方值得注意的是, 我们调用 addComponents(boolean  increaseWriterIndex, ByteBuf... buffers) 来添加两个 ByteBuf, 其中***个参数是 true, 表示当添加新的  ByteBuf 时, 自动递增 CompositeByteBuf 的 writeIndex.

如果我们调用的是

compositeByteBuf.addComponents(header, body);

那么其实 compositeByteBuf 的 writeIndex 仍然是0, 因此此时我们就不可能从 compositeByteBuf 中读取到数据,  这一点希望大家要特别注意.

除了上面直接使用 CompositeByteBuf 类外, 我们还可以使用 Unpooled.wrappedBuffer 方法, 它底层封装了  CompositeByteBuf 操作, 因此使用起来更加方便:

ByteBuf header = ... ByteBuf body = ... ByteBuf allByteBuf = Unpooled.wrappedBuffer(header, body);

通过 wrap 操作实现零拷贝

例如我们有一个 byte 数组, 我们希望将它转换为一个 ByteBuf 对象, 以便于后续的操作, 那么传统的做法是将此 byte 数组拷贝到  ByteBuf 中, 即:

byte[] bytes = ... ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeBytes(bytes);

显然这样的方式也是有一个额外的拷贝操作的, 我们可以使用 Unpooled 的相关方法, 包装这个 byte 数组, 生成一个新的 ByteBuf 实例,  而不需要进行拷贝操作. 上面的代码可以改为:

byte[] bytes = ... ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);

可以看到, 我们通过 Unpooled.wrappedBuffer 方法来将 bytes 包装成为一个 UnpooledHeapByteBuf 对象,  而在包装的过程中, 是不会有拷贝操作的. 即***我们生成的生成的 ByteBuf 对象是和 bytes 数组共用了同一个存储空间, 对 bytes  的修改也会反映到 ByteBuf 对象中.

Unpooled 工具类还提供了很多重载的 wrappedBuffer 方法:

public static ByteBuf wrappedBuffer(byte[] array) public static ByteBuf wrappedBuffer(byte[] array, int offset, int length)  public static ByteBuf wrappedBuffer(ByteBuffer buffer) public static ByteBuf wrappedBuffer(ByteBuf buffer)  public static ByteBuf wrappedBuffer(byte[]... arrays) public static ByteBuf wrappedBuffer(ByteBuf... buffers) public static ByteBuf wrappedBuffer(ByteBuffer... buffers)  public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays) public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers) public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers)

这些方法可以将一个或多个 buffer 包装为一个 ByteBuf 对象, 从而避免了拷贝操作.

通过 slice 操作实现零拷贝

slice 操作和 wrap 操作刚好相反, Unpooled.wrappedBuffer 可以将多个 ByteBuf 合并为一个, 而 slice  操作可以将一个 ByteBuf 切片为多个共享一个存储区域的 ByteBuf 对象.

ByteBuf 提供了两个 slice 操作方法:

public ByteBuf slice(); public ByteBuf slice(int index, int length);

不带参数的 slice 方法等同于 buf.slice(buf.readerIndex(), buf.readableBytes()) 调用, 即返回  buf 中可读部分的切片. 而slice(int index, int length) 方法相对就比较灵活了, 我们可以设置不同的参数来获取到 buf  的不同区域的切片.

下面的例子展示了 ByteBuf.slice 方法的简单用法:

ByteBuf byteBuf = ... ByteBuf header = byteBuf.slice(0, 5); ByteBuf body = byteBuf.slice(5, 10);

用 slice 方法产生 header 和 body 的过程是没有拷贝操作的, header 和 body 对象在内部其实是共享了 byteBuf  存储空间的不同部分而已. 即:

对于Netty ByteBuf的零拷贝的理解是怎样的

通过 FileRegion 实现零拷贝

Netty 中使用 FileRegion 实现文件传输的零拷贝, 不过在底层 FileRegion 是依赖于 Java NIO  FileChannel.transfer 的零拷贝功能.

首先我们从最基础的 Java IO 开始吧. 假设我们希望实现一个文件拷贝的功能, 那么使用传统的方式, 我们有如下实现:

public static void copyFile(String class="lazy" data-srcFile, String destFile) throws Exception {     byte[] temp = new byte[1024];     FileInputStream in = new FileInputStream(class="lazy" data-srcFile);     FileOutputStream out = new FileOutputStream(destFile);     int length;     while ((length = in.read(temp)) != -1) {         out.write(temp, 0, length);     }      in.close();     out.close(); }

上面是一个典型的读写二进制文件的代码实现了. 不用我说, 大家肯定都知道, 上面的代码中不断中源文件中读取定长数据到 temp 数组中, 然后再将  temp 中的内容写入目的文件, 这样的拷贝操作对于小文件倒是没有太大的影响, 但是如果我们需要拷贝大文件时,  频繁的内存拷贝操作就消耗大量的系统资源了.

下面我们来看一下使用 Java NIO 的 FileChannel 是如何实现零拷贝的:

public static void copyFileWithFileChannel(String class="lazy" data-srcFileName, String destFileName) throws Exception {     RandomAccessFile class="lazy" data-srcFile = new RandomAccessFile(class="lazy" data-srcFileName, "r");     FileChannel class="lazy" data-srcFileChannel = class="lazy" data-srcFile.getChannel();      RandomAccessFile destFile = new RandomAccessFile(destFileName, "rw");     FileChannel destFileChannel = destFile.getChannel();      long position = 0;     long count = class="lazy" data-srcFileChannel.size();      class="lazy" data-srcFileChannel.transferTo(position, count, destFileChannel); }

可以看到, 使用了 FileChannel 后, 我们就可以直接将源文件的内容直接拷贝(transferTo) 到目的文件中, 而不需要额外借助一个临时  buffer, 避免了不必要的内存操作.

有了上面的一些理论知识, 我们来看一下在 Netty 中是怎么使用 FileRegion 来实现零拷贝传输一个文件的:

@Override public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {     RandomAccessFile raf = null;     long length = -1;     try {         // 1. 通过 RandomAccessFile 打开一个文件.         raf = new RandomAccessFile(msg, "r");         length = raf.length();     } catch (Exception e) {         ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');         return;     } finally {         if (length < 0 && raf != null) {             raf.close();         }     }      ctx.write("OK: " + raf.length() + '\n');     if (ctx.pipeline().get(SslHandler.class) == null) {         // SSL not enabled - can use zero-copy file transfer.         // 2. 调用 raf.getChannel() 获取一个 FileChannel.         // 3. 将 FileChannel 封装成一个 DefaultFileRegion         ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));     } else {         // SSL enabled - cannot use zero-copy file transfer.         ctx.write(new ChunkedFile(raf));     }     ctx.writeAndFlush("\n"); }

上面的代码是 Netty 的一个例子, 其源码在  netty/example/class="lazy" data-src/main/java/io/netty/example/file/FileServerHandler.java

可以看到, 第一步是通过 RandomAccessFile 打开一个文件, 然后 Netty 使用了 DefaultFileRegion 来封装一个  FileChannel 即:

new DefaultFileRegion(raf.getChannel(), 0, length)

当有了 FileRegion 后, 我们就可以直接通过它将文件的内容直接写入 Channel 中, 而不需要像传统的做法: 拷贝文件内容到临时  buffer, 然后再将 buffer 写入 Channel. 通过这样的零拷贝操作, 无疑对传输大文件很有帮助.

关于对于Netty ByteBuf的零拷贝的理解是怎样的就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

对于Netty ByteBuf的零拷贝的理解是怎样的

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

对于Netty ByteBuf的零拷贝的理解是怎样的

这篇文章给大家介绍对于Netty ByteBuf的零拷贝的理解是怎样的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。根据 Wiki 对 Zero-copy 的定义:"Zero-copy" describes compu
2023-06-17

windows远程桌面不能拷贝文件的解决方法是怎样的

本篇文章给大家分享的是有关windows远程桌面不能拷贝文件的解决方法是怎样的,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。网上说远程服务器的clipbook服务没有启动。服务
2023-06-14

对Python循环对象的理解是怎么样的

对Python循环对象的理解是怎么样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。循环对象的并不是随着Python的诞生就存在的,但它的发展迅速,特别是Python 3x
2023-06-02

关于WCF异常处理解决方案是怎样的

这期内容当中小编将会给大家带来有关关于WCF异常处理解决方案是怎样的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。异常处理在我们的程序中是不可缺少的,异常可以反馈我们信息,如果还不知道WCF异常的朋友请看
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录