Netty 源码分析系列(八)Netty 如何实现零拷贝,16条代码规范建议

FileChannel sourceChannel = new RandomAccessFile(source, “rw”).getChannel();

SocketChannel socketChannel = SocketChannel.open(sa);

sourceChannel.transferTo(0, sourceChannel.size(), socketChannel);

复制代码

Netty 实现零拷贝


Netty 中的零拷贝的实现是基于 Java 的,换言之,底层也是基于操作系统实现的。相对于 Java 中的零拷贝而言,Netty 的零拷贝更多的是偏向于优化数据操作的概念。

Netty 中的零拷贝体现在以下几个方面:

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

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

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

  • 通过FileRegion包装的FileChannel.transferTo()实现文件传输,可以直接将文件缓冲区的数据发送到目标Channel,避免了通过循环 while方式导致的内存复制问题。

从上面几个方法可以看出,前三个方法都是广义零拷贝,其实现方式都是为了减少不必要的数据复制,偏向于应用层数据优化操作。而第四个方法,FileRegion

包装的FileChannel.transferTo(),才是真正的零拷贝(狭义零拷贝)。

下面分别来看其每一种实现。

1、CompositeByteBuf 方式

CompositeByteBuf 将多个ByteBuf合并为一个逻辑上的ByteBuf,类似于用一个链表,把分散的多个ByteBuf通过引用连接起来。分散的多个ByteBuf在内存中可能是大小各异、互不相连的区域,通过链表串联起来,作为一块逻辑上的大区域。而在实际数据读取时,还是会去各自每一小块上读取。

下图展示了 CompositeByteBuf 的原理:

image-20210813093354168

以下是 CompositeByteBuf 使用的代码示例:

ByteBuf header = …

ByteBuf body = …

CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer();

compositeBuffer.addComponents(true, header,body);

复制代码

2、wrap 方式

可以通过 wrap操作来实现零拷贝。

通过 wrap 操作,可以将 byte [] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象。

例如,通过 Unpooled.wrappedBuffer方法来将 bytes 包装成为一个UnpooledHeapByteBuf对象,而在包装的过程中,是不会有复制操作的。即最后生成的 ByteBuf 对象是和 bytes 数组共用了同一个存储空间,对 bytes 的修改也会反映到 ByteBuf 对象中。

以下是Unpooled.wrappedBuffer使用的代码示例:

ByteBuf header = …

ByteBuf body = …

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

复制代码

3、slice 方式

可以通过 slice 方式实现零拷贝,原理图如下:

image-20210813095044982

通过 Slice 操作,将ByteBuf分解为多个共享同一个存储区域的ByteBuf。slice 恰好是将一整块区域,划分成逻辑上的独立小区域,在读取每个逻辑上的小区域时,实际会去按 slice(int index,int length)方法中的indexlength去读取原内存 buffer 的数据。

以下是 slice 使用的示例代码:

ByteBuf bytebuf = …

ByteBuf header = bytebuf.slice(0,5);

ByteBuf body = bytebuf.slice(5,10);

复制代码

4、 FileRegion 方式

FileRegion底层包装的是 Java

【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 开源分享

FileChannel.transferTo()实现文件传输,因此可以直接将文件缓冲区的数据发送到目标Channel。这种方式才是真正操作系统级别的零拷贝。

以下是FileRegion使用的代码示例:

public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

RandomAccessFile raf = null;

long length = 0;

try {

//1.通过 RandomAccessFile 打开一个文件

raf = new RandomAccessFile(msg, “r”);

length = raf.length();

} catch (Exception e) {

ctx.writeAndFlush(“ERR:” + e.getClass() + ": " + e.getMessage());

return;

} finally {

if (length < 0 & raf != null) {

raf.close();

}

}

ctx.write(raf.length());

if (ctx.pipeline().get(SslHandler.class) == null) {

//2.调用 raf.getChannel() 方法获取一个 FileChannel

//3.将FileChannel封装成一个DefaultFileRegion

ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));

} else {

ctx.write(new ChunkedFile(raf));

}

ctx.write("\n");

}

复制代码

总结

通过以上的介绍,相信小伙伴们对于Netty的零拷贝机制原理也有了一定的了解,有没有思考一个问题,当我们向缓冲区写入数据时,如果写入的数据超过设置的容量(capacity)怎么办?其实Netty 提供了动态扩容机制,有兴趣的小伙伴们可以自己去了解一下。

我们下节来讲讲Netty的引导程序的源码分析。

结尾

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值