read(file, tmp_buf, len);
write(socket, tmp_buf, len);
tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);
sendfile(socket, file, len);
方式4:Hardware that supports gather can assemble data from multiple memory locations, eliminating another copy.
sendfile(socket, file, len);
调用无变化
The Linux kernel supports zero-copy through various system calls, such as
sys/socket.h's sendfile, sendfile64
splice, tee, vmsplice
process_vm_readv, process_vm_writev
copy_file_range
raw sockets with packet mmap[4] or AF_XDP
splice() 系统调用和 sendfile() 非常类似,
用户应用程序必须拥有两个已经打开的文件描述符,一个用于表示输入设备,一个用于表示输出设备。与 sendfile() 不同的是,splice() 允许任意两个文件之间互相连接,而并不只是文件到 socket 进行数据传输。对于从一个文件描述符发送数据到 socket 这种特例来说,一直都是使用 sendfile() 这个系统调用,而 splice 一直以来就只是一种机制,它并不仅限于 sendfile() 的功能。
也就是说,sendfile() 只是 splice() 的一个子集,在 Linux 2.6.23 中,sendfile() 这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过 API 以及相应的功能是利用了 splice() 这种机制来实现的。
总体来讲splice()是Linux 2.6.23 内核版本中替换sendfile()系统调用的一个方法,它不仅支持文件到Socket的直接传输,也支持文件到文件的直接传输I/O,但是其底层的传输过程和sendfile()并无区别。
directbytebuffer减少了堆大小,自己进行内存管理,可以避免gc引发的在不同内存区之间拷贝的问题
https://stackoverflow.com/questions/49174987/where-is-the-memory-of-direct-buffer-allocated-by-netty-kernel-space-or-user-sp
java实现:
https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/sun/nio/ch/FileChannelImpl.javaprivate long transferToDirectly(long position, int icount, WritableByteChannel target) throws IOException { if (!transferSupported) return IOStatus.UNSUPPORTED; FileDescriptor targetFD = null; if (target instanceof FileChannelImpl) { if (!fileSupported) return IOStatus.UNSUPPORTED_CASE; targetFD = ((FileChannelImpl)target).fd; } else if (target instanceof SelChImpl) { // Direct transfer to pipe causes EINVAL on some configurations if ((target instanceof SinkChannelImpl) && !pipeSupported) return IOStatus.UNSUPPORTED_CASE; targetFD = ((SelChImpl)target).getFD(); } if (targetFD == null) return IOStatus.UNSUPPORTED; int thisFDVal = IOUtil.fdVal(fd); int targetFDVal = IOUtil.fdVal(targetFD); if (thisFDVal == targetFDVal) // Not supported on some configurations return IOStatus.UNSUPPORTED; long n = -1; int ti = -1; try { begin(); ti = threads.add(); if (!isOpen()) return -1; do { n = transferTo0(thisFDVal, position, icount, targetFDVal); } while ((n == IOStatus.INTERRUPTED) && isOpen()); if (n == IOStatus.UNSUPPORTED_CASE) { if (target instanceof SinkChannelImpl) pipeSupported = false; if (target instanceof FileChannelImpl) fileSupported = false; return IOStatus.UNSUPPORTED_CASE; } if (n == IOStatus.UNSUPPORTED) { // Don't bother trying again transferSupported = false; return IOStatus.UNSUPPORTED; } return IOStatus.normalize(n); } finally { threads.remove(ti); end (n > -1); } }
依赖于native方法
c实现:
https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/solaris/native/sun/nio/ch/FileChannelImpl.cJNIEXPORT jlong JNICALL Java_sun_nio_ch_FileChannelImpl_transferTo0(JNIEnv *env, jobject this, jint srcFD, jlong position, jlong count, jint dstFD) { #if defined(__linux__) off64_t offset = (off64_t)position; jlong n = sendfile64(dstFD, srcFD, &offset, (size_t)count); if (n < 0) { if (errno == EAGAIN) return IOS_UNAVAILABLE; if ((errno == EINVAL) && ((ssize_t)count >= 0)) return IOS_UNSUPPORTED_CASE; if (errno == EINTR) { return IOS_INTERRUPTED; } JNU_ThrowIOExceptionWithLastError(env, "Transfer failed"); return IOS_THROWN; } return n; #elif defined (__solaris__) sendfilevec64_t sfv; size_t numBytes = 0; jlong result; sfv.sfv_fd = srcFD; sfv.sfv_flag = 0; sfv.sfv_off = (off64_t)position; sfv.sfv_len = count; result = sendfilev64(dstFD, &sfv, 1, &numBytes); /* Solaris sendfilev() will return -1 even if some bytes have been * transferred, so we check numBytes first. */ if (numBytes > 0) return numBytes; if (result < 0) { if (errno == EAGAIN) return IOS_UNAVAILABLE; if (errno == EOPNOTSUPP) return IOS_UNSUPPORTED_CASE; if ((errno == EINVAL) && ((ssize_t)count >= 0)) return IOS_UNSUPPORTED_CASE; if (errno == EINTR) return IOS_INTERRUPTED; JNU_ThrowIOExceptionWithLastError(env, "Transfer failed"); return IOS_THROWN; } return result; #elif defined(__APPLE__) off_t numBytes; int result; numBytes = count; #ifdef __APPLE__ result = sendfile(srcFD, dstFD, position, &numBytes, NULL, 0); #endif if (numBytes > 0) return numBytes; if (result == -1) { if (errno == EAGAIN) return IOS_UNAVAILABLE; if (errno == EOPNOTSUPP || errno == ENOTSOCK || errno == ENOTCONN) return IOS_UNSUPPORTED_CASE; if ((errno == EINVAL) && ((ssize_t)count >= 0)) return IOS_UNSUPPORTED_CASE; if (errno == EINTR) return IOS_INTERRUPTED; JNU_ThrowIOExceptionWithLastError(env, "Transfer failed"); return IOS_THROWN; } return result; #else return IOS_UNSUPPORTED_CASE; #endif }
会有陆续的文章进行介绍
https://www.linuxjournal.com/article/6345?page=0,0
https://blog.csdn.net/u013256816/article/details/52589524
https://en.wikipedia.org/wiki/Zero-copy