这里不再赘述 零拷贝 定义的相关内容,感兴趣可以查看 维基百科 - 零拷贝 。
Java 原生
在 Java 中,零拷贝具体的 API 指的是
java.nio.channels
FileChannel#transferTo(long position, long count, WritableByteChannel target);
FileChannel#transferFrom(ReadableByteChannel src, long position, long count)
其中的 WritableByteChannel
和 ReadableByteChannel
可以使用 java.nio.channels.Channels#newChannel
(注意这里是 Channels 后面有 s 是工具类) 从 OutputStream
和 InputStream
中获取。
Netty 中使用
// 接口
io.netty.channel.FileRegion
// 实现,把原生 FileChannel 包装下使用
io.netty.channel.DefaultFileRegion
Handler
直接 write
出去 FileRegion
类型的对象就可以,netty 会主动判断类型,如果是 FeilRegion
会做相应处理。
io.netty.channel.nio.AbstractNioByteChannel#
doWriteInternal(ChannelOutboundBuffer in, Object msg) {
if (msg instanceof ByteBuf) {
// ...
} else if (msg instanceof FileRegion) {
FileRegion region = (FileRegion) msg;
if (region.transferred() >= region.count()) {
in.remove();
return 0;
}
// 这个地方调用的 doWriteFileRegion 就是了
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount > 0) {
in.progress(localFlushedAmount);
if (region.transferred() >= region.count()) {
in.remove();
}
return 1;
}
} else {
// Should not reach here.
throw new Error();
}
return WRITE_STATUS_SNDBUF_FULL;
}
// 最终调用到
io.netty.channel.socket.nio.NioSocketChannel#
doWriteFileRegion(FileRegion region) {
final long position = region.transferred();
// 这里的 transferTo 就是了
return region.transferTo(javaChannel(), position);
}
可点击查看 Netty 关于 FileRegion 的详细文档。
问题
-
Windows 下大文件,效果可能不佳,所以在 windows 上写 demo 程序可能得出截然相反的结果。
-
Windows 下直接使用 FileChannel 传输大于 8M 的文件,需要多次传输。
- Linux 中无此问题
- 如果直接使用 netty 中的 FileRegion 无需关心此问题,其通过 transferred 字段记录了 position 位置。
windows 中使用原生 Channel 记录传输位置的方案
/**
* Transfers bytes from origin channel to the given writable byte
* channel.
*
* @param origin The origin channel
* @param target The target channel
* @return amount The number of bytes that were actually transferred
* @throws IOException If some I/O error occurs
*/
public static long transferTo(FileChannel origin, WritableByteChannel target)
throws IOException {
long size = origin.size();
long transferred = 0;
while (transferred < size) {
transferred += origin.transferTo(transferred, size, target);
}
return transferred;
}