零拷贝指不发生CPU拷贝
传统IO
经历了4次拷贝、三次上下文切换
4次拷贝:
- DMA(直接内存拷贝)拷贝到内核buffer
- CPU拷贝到用户Buffer
—用户进行修改操作— - CPU拷贝到SocketBuffer
- DMA拷贝到协议栈
mmap内存映射优化
经历了3次拷贝、3次上下文切换(内核缓冲和User缓冲共享数据)
三次拷贝
- DMA(直接内存拷贝)
- CPU拷贝
—用户进行修改操作— - DMA拷贝到协议栈
sendFile优化(Linux 2.1)
经历了3次拷贝、2次上下文切换(数据不经过用户态、直接从内核缓冲进入SocketBuffer)
- DMA(直接内存拷贝)
- CPU拷贝
—用户进行修改操作— - DMA拷贝到协议栈
sendFile再优化(Linux 2.4)
经历了2次拷贝、2次上下文切换
- DMA(直接内存拷贝)
只有极少的数据需要执行cpu拷贝(长度、偏移量等等)
—用户进行修改操作— - DMA拷贝到协议栈
mmap和sendFile的区别
- mmap适合小数据量的读写,sendFile适合大文件的传输
- mmap需要3次上下文切换,3次数据拷贝,sendFile需要2次上下文切换和至少2次数据拷贝。
案例
在NIO中实现零拷贝,使用transferTo
服务端代码
public class NewIOService {
public static void main(String[] args) throws IOException {
InetSocketAddress address = new InetSocketAddress(7000);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);
// 创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while (-1 == readCount){
readCount = socketChannel.read(byteBuffer);
byteBuffer.rewind();
}
}
}
}
客户端代码
public class NewIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost",7000));
String filename = "1.txt";
//获取文件Channel
FileChannel fileChannel = new FileInputStream(filename).getChannel();
// 准备发送
long startTime = System.currentTimeMillis();
// Linux下使用transferTo方法可以完成传输
// 在windows下一次调用最多发送8M文件,需要分段传输文件,要注意转出时的位置
long count = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
System.out.println("发送的总的字节数="+ count+"耗时:"+(System.currentTimeMillis()-startTime));
// 关闭通道
fileChannel.close();
}
}