零拷贝基本介绍
零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储的技术。
我们说的零拷贝 是从操作系统来看 因为内核缓冲区之间,没有数据是重复的(只有kernel buffer有一份数据)
1)零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。
2)零拷贝技术减少了用户应用程序地址空间和操作系统内核地址空间之间因为上下文切换而带来的开销。
零拷贝技术实现
传统拷贝
DMA: direct memory access直接内存拷贝(不使用CPU)
mmap
mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据,这样,在进行网络传输的时候,就可以减少内核空间到用户控件的拷贝次数。
sendFile优化1
linux2.1 版本 提供了sendFile函数,其基本原理:数据根本不经过用户态,直接从内核缓冲区进入到SocketBuffer,同时,由于和用户态完全无关,就减少了一次上下文切换
sendFile优化2
Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socket buffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。
这里其实有 一次cpu 拷贝 kernel buffer -> socket buffer 但是,拷贝的信息很少,比如lenght , offset , 消耗低,可以忽略
mmap 和 sendFile区别
1)mmap 适合小数据量读写,sendFile 适合大文件传输。
2)mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。
3)sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)。
nio实现零拷贝
public class NewIOClient {
public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 7001));
socketChannel.configureBlocking(true);
String fileName = "hello.jpg";
FileChannel fileChannel = new FileInputStream(fileName).getChannel();
long startTime = System.currentTimeMillis();
long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
System.out.println("发送总字节数:" + transferCount + ",耗时: " + (System.currentTimeMillis() - startTime));
fileChannel.close();
}
}
public class NewIOServer {
public static void main(String[] args) throws Exception{
InetSocketAddress address = new InetSocketAddress(7001);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.setReuseAddress(true);
serverSocket.bind(address);
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(true);
int readCount = 0;
while (-1 != readCount) {
try {
readCount = socketChannel.read(byteBuffer);
} catch (Exception ex) {
ex.printStackTrace();
}
byteBuffer.rewind();
}
}
}
}