零拷贝

零拷贝主要是优化内核缓冲区和用户缓存区的之间拷贝次数

怎么出现一步一步出现零拷贝的呢,下面跟大家讲一下。

下图是当用户发出读写请求到操作系统进行交互的简单流程图
image

传统模式

从上图描述,把数据从内核缓冲区拷贝到用户缓冲区只是一次读操作,但是在网络编程中,该操作需要4次拷贝,4次上下文切换,因此性能低。
image

零拷贝模式(主要有mmap和sendFlie)

先了解一个概念:
DMA拷贝(direct memory access):直接内存拷贝,不使用CPU

mmap优化

mmap通过内存映射,将内存缓冲区中的数据映射到用户缓冲区中,这样用户空间可以共享内核空间的数据。在进行网络传输时,就可以减少内核空间到用户空间的CPU拷贝次数。但是还需要将内核缓冲区中的数据通过CPU拷贝到socket缓冲区中。该操作有3次拷贝,4次上下文切换
image

sendFile优化(有两个版本)

1、在Linux2.1版本中使用sendFile系统调用函数时,把用户缓冲区去掉了,直接将内核缓冲区通过CPU拷贝到socket缓冲区中(由于把用户缓冲区去掉了,因此就减少了两次上下文切换)。该操作3次拷贝,2次上下文切换

2、在Linux在2.4版本中真正实现了零拷贝,对2.1版本进行了修改,避免了将内核缓冲区中的数据通过CPU拷贝到socket缓冲区的操作,而是直接将内核缓冲区中的数据通过DMA拷贝到网卡中。该操作有2次拷贝,2次上下文切换
image

1、与传统模式相比,零拷贝实现了更少的数据复制、更少的上下文切换、更少的CPU缓存伪共享以及CPU校验和计算。
2、sendFile可以利用DMA方式来减少CPU拷贝;mmap不可以利用DMA的方式来减少CPU拷贝,而是必须从内核缓冲区将数据通过CPU拷贝到socket缓冲区。

在NIO中,使用fileChannel.transferTo就可以实现零拷贝

  • 在linux一次调用transferTo方法就可以完成传输
  • 在windows每次调用transferTo只能发送8MB,就需要分段传输文件

案例如下
NewIOServer.java服务端

服务端代码
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

//服务器
public class NewIOServer {

    public static void main(String[] args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(7001);
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(address);

        //创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            int readcount = 0;
            while (-1 != readcount) {
                try {
                    readcount = socketChannel.read(byteBuffer);
                } catch (Exception ex) {
                    // ex.printStackTrace();
                    break;
                }
                //
                byteBuffer.rewind(); //倒带 position = 0 mark 作废
            }
        }
    }
}

NewIOClient.java客户端

客户端代码
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class NewIOClient {

    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost", 7001));
        String filename = "protoc-3.6.1-win32.zip";
        //得到一个文件channel
        FileChannel fileChannel = new FileInputStream(filename).getChannel();
        //准备发送
        long startTime = System.currentTimeMillis();
        //在 linux 下一个 transferTo 方法就可以完成传输
        //在 windows 下一次调用 transferTo 只能发送 8m, 就需要分段传输文件
        //transferTo 底层使用到零拷贝
        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
        System.out.println("发送的总的字节数 = " + transferCount + " 耗时: " + (System.currentTimeMillis() - startTime));

        //关闭
        fileChannel.close();
    }
}
posted @ 2022-03-16 19:17  阿宁你好啊  阅读(855)  评论(0编辑  收藏  举报