亲宝软件园·资讯

展开

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

顽石九变 人气:0

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

一、概念说明

1、传统IO

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

2、mmap

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

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

3、sendfile

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

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

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传输

加载全部内容

相关教程
猜你喜欢
用户评论