文章目录


什么是零拷贝

传统Linux操作系统的标准IO接口基于数据拷贝,虽然不需要进行实际物理磁盘的IO操作,但是传输过程中的数据拷贝操作会极大消耗CPU,造成操作系统的有效数据传输操作能力,零拷贝在数据拷贝同时允许CPU执行其他的任务

在java程序中常用的零拷贝有mmap(内存映射)和sendFile

传统IO模型

File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");

byte[] arr = new byte[(int) file.length()];
raf.read(arr);

Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);

Netty(三):NIO与零拷贝_零拷贝

DMA:direct memory access 直接内存拷贝(不使用 CPU)

mmap 优化

通过内存映射,将文件映射到内核缓冲区,同时用户空间可以共享内核空间的数据,这样在进行网络传输时,减少内核空间到用户空间的拷贝次数

Netty(三):NIO与零拷贝_数据_02

sendFile优化

数据根本不经过用户态.直接从内核缓冲区进入到SocketBuffer,同时由于和用户态完全无关,就减少了一次上下文切换

Netty(三):NIO与零拷贝_数据_03

这里其实有一次cpu 拷贝 kernel buffer -> socket buffer,但是开吧数据很少,基本是length,offset,消耗低

零拷贝是从操作系统角度来说,因为内核缓冲区之间没有数据重复(只有kernel buffer有一份数据),可以减少数据复制,更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算

mmap和sendFile区别

mmap适合小数据量读写,sendFile适合大文件传输

mmap上下文切换4次,sendFile 三次上下文切换,最少2次数据拷贝

sendFile可以利用DMA方式,减少cpu拷贝,mmap则不能

案例 传统IO与NIO传输文件时间对比

server

package com.zyd.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NewIOServer {
public static void main(String[] args) throws IOException {
InetSocketAddress address = new InetSocketAddress(8888);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);

ByteBuffer allocate = ByteBuffer.allocate(4096);

while (true) {
SocketChannel socket = serverSocketChannel.accept();
int readCount = 0;
while (-1 != readCount){
try {
readCount = socket.read(allocate);
}catch (Exception e){
break;
}
}
allocate.rewind();
}
}
}

client

package com.zyd.nio;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class NewIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost",8888));
String file = "";
//得到一个文件channel
FileChannel channel = new FileInputStream(file).getChannel();
//准备发送
long startTime = System.currentTimeMillis();
long transferTo = channel.transferTo(0, channel.size(), socketChannel);
System.out.println("发送的总字节数 = " + transferTo + "耗时: "+(System.currentTimeMillis() - startTime));
}
}