一、传统网络IO(read/write方式)
场景:将一张本地图片展示给用户,首先将本地图片从磁盘中拷贝出来放到内存buffer中,然后将这个buffer通过socket传递给用户,这个过程抽象成下面的过程:
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
首先调用read
将本地图片,读取到jvm内存中,然后调用write
将jvm内存中的数据写道socket
中,如图:
kernel模式和user模式上下文切换:四次
数据拷贝:四次
- 首先,调用read时,本地图片从DMA拷贝到了kernel模式的 kernel buffer中
- 然后,CPU控制将kernel buffer中的数据拷贝到了user模式的 user buffer中
- 之后,调用write时,将user buffer中的数据拷贝到kernel模式下的socket buffer中
- 最后,将socket buffer中的数据拷贝到网卡设备protocol engine中传输
数据从kernel模式拷贝到user模式又拷贝回kernel再拷贝到网卡设备传输,浪费了2次拷贝;
应用程序用Zero-Copy来请求kernel直接把disk的data传输给socket,而不是通过应用程序传输。Zero-Copy大大提高了应用程序的性能,并且减少了kernel和user模式上下文的切换。
二、Zero-Copy网络IO
Zero-Copy
直接将kernel buffer
拷贝到socket buffer
,省去了将kernel buffer
拷贝到user buffer
然后从user buffer
拷贝到socket buffer
的过程,将整个过程的数据拷贝变为三次,并且减少了上下文切换;
Java NIO
中通过FileChannel.transferTo()
方法依赖于操作系统底层的sendFile()
实现Zero-Copy
//Java NIO 实现 Zero-Copy 方法
public void transferTo(long position, long count, WritableByteChannel target);
//transferTo底层调用方法sendfile(),这个系统内核调用本身被设计出来是用来从磁盘到TCP协议栈拷贝数据用的,但也我们也是可以把它用来做两个文件之间的数据拷贝。
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
//in_fd 被打开是等待读数据的fd.
//out_fd 被打开是等待写数据的fd.
//Offset 是在正式开始读取数据之前应该向前偏移的byte数.
//count 是需要在两个fd之间“搬移”的数据的byte数.
三、Linux2.1内核开始的sendfile函数
sendfile(socket, file, len);
kernel模式和user模式上下文切换:二次
数据拷贝:三次
- 首先,将本地图片从DMA拷贝到了kernel模式的 kernel buffer中
- 然后,CPU控制将kernel buffer中的数据拷贝到kernel模式下的socket buffer中
- 最后,将socket buffer中的数据拷贝到网卡设备protocol engine中传输
sendFile和read/write方式相比,只需要两次上下文切换,并且不需要将kernel buffer的数据拷贝到user buffer减少了一次数据拷贝。
但是Zero-Copy更理想的状态是直接从kernel buffer中获取数据,而不是从socket buffer,这样可以再减少一次数据拷贝
四、Linux2.4内核开始的sendfile函数
Linux2.4内核对sendfile做了改进
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
kernel模式和user模式上下文切换:二次
数据拷贝:二次
- 首先,将本地图片从
DMA
拷贝到了kernel
模式的kernel buffer
中 - 然后,向
socket buffer
中追加当前要使用的数据在kernel buffer
中的位置和偏移量 - 最后,根据
socket buffer
中的位置和偏移量直接将kernel buffer
中的数据拷贝到网卡设备protocol engine
中传输
升级后,数据经过两次拷贝就从磁盘中传输出去了,这就是目前java nio中TransferTo()的实现原理;
Zero-Copy技术已经应用于kafka、netty中等;