零拷贝指的是没有CPU拷贝,并不是不拷贝;减少上下文切换

一、概念说明

1、传统IO

需要4次拷贝,3次上下文切换

Java图文并茂详解NIO与零拷贝

2、mmap

mmap 通过内存映射,将文件映射到内存缓冲区,同时用户空间可以共享内存缓冲区的数据,减少内核空间到用户空间的拷贝

需要3次拷贝,3次上下文切换

Java图文并茂详解NIO与零拷贝

3、sendfile

Linux 2.4 避免了从内核缓冲区到Socket Buffer的拷贝,直接拷贝到协议栈,从而减少一次数据拷贝

需要2次拷贝,3次上下文切换

Java图文并茂详解NIO与零拷贝

4、mmap与sendfile

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

mmap需要4次上下文切换,3次数据拷贝;sendfile需要3次上下文切换,最少2次数据拷贝

send可用利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)

二、传统IO传输文件代码示例

1、服务端代码

import java.io.DataInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;public class BIOServer {    public static void main(String[] args) throws IOException {        ServerSocket serverSocket = new ServerSocket(7000);        while (true) {            Socket socket = serverSocket.accept();            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());            try {                long total = 0;                byte[] bytes = new byte[4096];                FileOutputStream fileOutputStream = new FileOutputStream("d:\\temp\\04.zip");                while (true) {                    int read = dataInputStream.read(bytes, 0, bytes.length);                    if (read == -1) {                        //文件读取结束,退出循环                        break;                    }                    total += read;                    fileOutputStream.write(bytes, 0, read);                }                System.out.println("收到客户端发送文件,总字节数:" + total);                fileOutputStream.close();            } catch (Exception e) {                e.printStackTrace();            }        }    }}

2、客户端代码

import java.io.DataOutputStream;import java.io.FileInputStream;import java.io.IOException;import java.net.Socket;public class BIOClient {    public static void main(String[] args) throws IOException {        Socket socket = new Socket("127.0.0.1", 7000);        FileInputStream fileInputStream = new FileInputStream("d:\\temp\\03.zip");        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());        byte[] bytes = new byte[4096];        long readCount;        long total = 0;        long start = System.currentTimeMillis();        while ((readCount = fileInputStream.read(bytes)) >= 0) {            total += readCount;            dataOutputStream.write(bytes);        }        System.out.println("发送总字节数:" + total + ", 总耗时:" + (System.currentTimeMillis() - start));        dataOutputStream.close();        socket.close();        fileInputStream.close();    }}

3、控制台出输出

测试发送9M的压缩文件,耗时在26ms左右

发送总字节数:9428963, 总耗时:26

三、NIO传输文件代码示例

1、服务端代码

package com.hj.io.nio.zero;import java.io.FileOutputStream;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;public class NIOServerFile {    public static void main(String[] args) throws IOException {        InetSocketAddress address = new InetSocketAddress(7000);        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();        serverSocketChannel.socket().bind(address);        //创建buffer        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);        while (true) {            //等待客户端链接            SocketChannel socketChannel = serverSocketChannel.accept();            System.out.println("收到客户端链接");            int total = 0;            FileOutputStream fileOutputStream = new FileOutputStream("d:\\temp\\05.zip");            //循环读取数据,并存储到硬盘            while (true) {                try {                    int readCount = socketChannel.read(byteBuffer);                    if (readCount == -1) {                        //文件读取结束                        break;                    }                    total += readCount;                    fileOutputStream.write(byteBuffer.array(),0,readCount);                    //将buffer倒带                    byteBuffer.rewind();                } catch (IOException e) {                   break;                }            }            System.out.println("收到客户端发送文件,总字节数:" + total);            fileOutputStream.close();        }    }}

2、客户端代码

import java.io.FileInputStream;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.channels.FileChannel;import java.nio.channels.SocketChannel;public class NIOClientFile {    public static void main(String[] args) throws IOException {        //打开一个SocketChannel并链接到服务器端        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 7000));        //打开一个文件        FileChannel fileChannel = new FileInputStream("d:\\temp\\03.zip").getChannel();        //发送文件到服务器        long start = System.currentTimeMillis();        //在linux下,一次transferTo调用就可以完成传输        //在windows下,一次transferTo调用最多只能传8M,大文件需要分段传输,需要注意传输位置        //transferTo底层使用零拷贝        long total = fileChannel.size();        long sended = 0;        while (sended < total) {            //从上一次传输位置继续发送            long lenth = fileChannel.transferTo(sended, fileChannel.size(), socketChannel);            sended += lenth;        }        System.out.println("发送总字节数:" + sended + ",总耗时:" + (System.currentTimeMillis() - start));        //关闭channel        fileChannel.close();        socketChannel.close();    }}

3、控制台出输出

测试发送9M的压缩文件,耗时在16ms左右

发送总字节数:9428963,总耗时:16

四、总结

使用零拷贝传输,性能明显高于传统IO传输

原文地址:https://blog.csdn.net/wlddhj/article/details/123778139

相关文章: