NIO零拷贝分析

场景介绍:

在客户端通过读取本地文件并发送给服务器端,记录从发送开始到发送完成的执行时间,采用BIO方式和NIO零拷贝发送进行说明。

先上代码,BIO方式代码如下:

public class OldServer {
    public static void main(String[] args) throws Exception{
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());
            byte[] bytes=new byte[4096];
            while(true){
                int readCount=dataInputStream.read(bytes,0,bytes.length);
                if(readCount==-1){
                    break;
                }
            }
        }

    }
}

public class OldClient {
    public static void main(String[] args) throws Exception{
        Socket soket=new Socket("localhost",8888);
        String fileName="F:\\soft\\mysql-installer-community-5.6.25.0.zip";
        InputStream inputStream=new FileInputStream(fileName);

        DataOutputStream dataOutputStream=new DataOutputStream(soket.getOutputStream());
        byte[] bytes=new byte[4096];
        long readCount;
        long total=0;
        long starttime=System.currentTimeMillis();
        while((readCount=inputStream.read(bytes))>=0){
            total+=readCount;
            dataOutputStream.write(bytes);
        }

        System.out.println("发送字节数:"+total+",耗时"+ (System.currentTimeMillis()-starttime));

        dataOutputStream.close();
        soket.close();
        inputStream.close();
    }
}

NIO方式代码如下:

public class NewServer {

    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        ServerSocket serverSocket=serverSocketChannel.socket();
        serverSocket.setReuseAddress(true);
        serverSocket.bind(new InetSocketAddress("localhost",9999));


        ByteBuffer byteBuffer=ByteBuffer.allocate(4096);
        while(true){
            SocketChannel socketChannel=serverSocketChannel.accept();
            socketChannel.configureBlocking(true);
            int readCount=0;
            long allCount=0;
            while(-1!=readCount){
                try {
                    readCount=socketChannel.read(byteBuffer);
                    allCount+=readCount;
                }catch (Exception e){
                    e.printStackTrace();
                    byteBuffer.clear();
                    break;
                }

                byteBuffer.clear();
            }
            System.out.println(allCount+"---"+System.currentTimeMillis());
            socketChannel.close();
        }
    }
}

public class NewClient {
    public static void main(String[] args) throws Exception{
        SocketChannel socketChannel=SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost",9999));
        socketChannel.configureBlocking(true);

        String fileName="F:\\soft\\mysql-installer-community-5.6.25.0.zip";
        FileChannel fileChannel=new FileInputStream(fileName).getChannel();

        long position = 0;
        long size = fileChannel.size();
        long startTime = System.currentTimeMillis();
        while (position < size) {
            //重点代码,是实现零拷贝到远程服务器的关键
            long transfer = fileChannel.transferTo(position, fileChannel.size(), socketChannel);
            if (transfer <= 0) {
                break;
            }
            position += transfer;
        }


        System.out.println(System.currentTimeMillis());
        System.out.println("发送字节数:"+position+",耗时"+ (System.currentTimeMillis()-startTime));
        fileChannel.close();

    }
}

同一个文件经过2种不同方式进行拷贝并发送给远程服务器,在客户端打印出来的效率完全不一样,为什么呢?因为NIO使用的是零拷贝方式。

几个名词解释:

用户空间:可以理解为应用程序所管控的内存空间,如jvm堆

内核空间:可以理解为操作系统内核所管控的内存空间

磁盘或网卡等硬件:磁盘上的文件或网络发送过来的数据流

零拷贝:并不是指数据一次都不拷贝,而是数据不用从内核空间拷贝到用户空间。

BIO方式流程如下图所示:

步骤为:

1.当应用程序需要读取磁盘上的某个文件时,需要调用操作系统内核的read方法,并将进程状态切换到内核态,即从用户态到内核态的上下文切换。

2.内核通过直接内存访问(DMA)方式将磁盘上的数据拷贝到内核空间,一次数据拷贝。

3.将内核空间的数据拷贝到用户空间,一次数据拷贝,一次内核态到用户态的上下文切换。

4.应用程序什么也不做,将用户空间的数据拷贝一份作为发送数据的源数据。一次数据拷贝。

5.调用内核的write方法,用户空间的新数据源拷贝到内核空间,一次数据拷贝,一次用户态到内核态的上下文切换。。

6.内核返回发送结果,又一次内核态到用户态的上下文切换。

综上所述:一共需要经历4次用户态和内核态的切换,4次数据拷贝

NIO减少用户空间和内核空间的拷贝,流程如下图所示:

1.应用程序向操作系统内核发起sendfile调用,一次用户态到内核态的上下文切换。

2.内核通过直接内存访问(DMA)方式从磁盘将数据拷贝到内核空间,一次文件拷贝。

3.内核将文件内容拷贝到另外一个缓冲区,一次文件拷贝

4.内核将另外一个缓冲区的数据写入网络或磁盘

5.向应用程序返回发送结果,一次内核态到用户态的上下文切换。

综上所述:一共产生了2次用户态和内核态的切换,2次数据拷贝。 

NIO直接传递文件描述符方式,如下图所示:

1.应用程序向操作系统内核发起sendfile调用,一次用户态到内核态的上下文切换。

2.内核通过直接内存访问(DMA)方式从磁盘将数据拷贝到内核空间,一次文件拷贝。

3.内核将文件描述信息拷贝到另外一个缓冲区,即另外一个缓存去里存储的是远缓冲区的内存地址和长度

4.内核将另外一个缓冲区指向的数据写入网络或磁盘

5.向应用程序返回发送结果,一次内核态到用户态的上下文切换。

综上所述:一共产生了2次用户态和内核态的切换,1次数据拷贝,1次文件描述符和长度的拷贝。 

注意:上述功能需要Linux2.4以后版本才支持,也就是说要实现所谓的零拷贝,是需要操作系统支持。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值