栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【java网络编程】零拷贝

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【java网络编程】零拷贝

简介

零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储的技术。针对操作系统中的设备驱动程序、文件系统以及网络协议堆栈而出现的各种零拷贝技术极大地提升了特定应用程序的性能,并且使得这些应用程序可以更加有效地利用系统资源。这种性能的提升就是通过在数据拷贝进行的同时,允许 CPU 执行其他的任务来实现的。

1、传统文件发送
   while((n = read(diskfd, buf, BUF_SIZE)) > 0)
       socket.getOutputStream().write(sockfd, buf , n);

应用程序和磁盘之间想要传输数据,是没有办法直接进行传输的。

整个过程为:应用程序向操作系统发起一个读请求,那么首先磁盘中的数据会被读取到内核地址空间中,然后会把内核地址空间中的数据拷贝到用户地址空间中(其实就是 JVM 内存中),最后再把这个数据读取到应用程序中来。

上图共产生了四次数据拷贝,在用户态与内核态多次切换,是造成效率低下的主要原因。

java代码
​
public static void test1() throws Exception {
        // 利用通道完成文件的复制(非直接缓冲区)
        FileInputStream fis = new FileInputStream("a.txt");
        FileOutputStream fos = new FileOutputStream("b.txt");
        // 获取通道
        FileChannel fisChannel = fis.getChannel();
        FileChannel foschannel = fos.getChannel();

        // 通道没有办法传输数据,必须依赖缓冲区
        // 分配指定大小的缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 将通道中的数据存入缓冲区中
        while (fisChannel.read(byteBuffer) != -1) {  // fisChannel 中的数据读到 byteBuffer 缓冲区中
            byteBuffer.flip();  // 切换成读数据模式
            // 将缓冲区中的数据写入通道
            foschannel.write(byteBuffer);
            byteBuffer.clear();  // 清空缓冲区
        }
        foschannel.close();
        fisChannel.close();
        fos.close();
        fis.close();
    }

​

2、使用mmap+write发送
buf = mmap(file, len);
write(sockfd, buf, len);

mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

 使用mmap替代read很明显可以减少一次拷贝

java代码
public static void test2() throws Exception {
    // 使用直接缓冲区完成文件的复制(内存映射文件)
    
    FileChannel inChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
    FileChannel outChannel = FileChannel.open(Paths.get("c.txt"), StandardOpenOption.WRITE,
                                              StandardOpenOption.READ, StandardOpenOption.CREATE);

    
    MappedByteBuffer inMapped = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
    MappedByteBuffer outMapped = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

    // 直接对缓冲区进行数据的读写操作
    byte[] dst = new byte[inMapped.limit()];
    inMapped.get(dst);  // 把数据读取到 dst 这个字节数组中去
    outMapped.put(dst); // 把字节数组中的数据写出去

    inChannel.close();
    outChannel.close();
}

3、使用sendfile

在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile(),函数形式如下:

替代了前面的 read() 和 write() 这两个系统调用,减少一次系统调用,减少 2 次上下文切换的开销

java代码
public static void test3() throws Exception {
    
    FileChannel inChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
    FileChannel outChannel = FileChannel.open(Paths.get("d.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE,
                                              StandardOpenOption.CREATE);
    inChannel.transferTo(0, inChannel.size(), outChannel);
    // 或者可以使用下面这种方式
    //outChannel.transferFrom(inChannel, 0, inChannel.size());
    inChannel.close();
    outChannel.close();
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/946669.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号