NIO 零拷贝

9 篇文章 0 订阅

在 Java 程序中,常用的零拷贝有 mmap ( 内存 映射 )  和 sendFile。

传统IO: 经过4次拷贝,3次状态切换

 

 mmap 优化: 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数。拷贝次数是3次,3次状态切换

sendFile 优化: Linux 2.1 版本提供了sendFile 函数,数据不经过用户态,直接从内核缓冲区进入到socket buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。3次拷贝,2次状态切换

 

 Linux 2.4 版本做了一些修改,避免了从内核缓冲区拷贝到socket buffer的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝(kernel buffer -> socket buffer 会拷贝一些 length, offset 等描述信息,可以忽略)。 2次拷贝, 2次状态切换

 零拷贝:

  • 是指从操作系统角度看,不存在CPU拷贝(仅有 kernel buffer 一份数据)
  • 不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算

mmap 和 sendFile 的区别:

  • mmap 适合小数据量读写,sendFile 适合大文件传输
  • mmap 需要4次上下文切换,3次数据拷贝。sendFile 需要3次上下文切换,最少2次数据拷贝
  • sendFile 可以利用DMA方式,减少 CPU 拷贝, mmap 则不能

传统 IO 服务器端:

import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 传统IO 服务器端
 * @author doubily
 */
public class IOServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9527);

        while(true) {
            Socket socket = serverSocket.accept();

            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());

            try {
                byte[] bytes = new byte[4096];
                while (true) {
                    int readCount = dataInputStream.read(bytes, 0, bytes.length);
                    if (-1 == readCount) {
                        break;
                    }
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

传统 IO 客户端:

/**
 * 传统IO 客户端
 * @author doubily
 */
public class IOClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 9527);

        String fileName = "F:\\3rdlib.zip";
        InputStream inputStream = new FileInputStream(fileName);

        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
        byte[] buffer = new byte[4096];
        long readCount ;
        long total = 0;

        long startTime = System.currentTimeMillis();

        while ((readCount = inputStream.read(buffer)) >= 0) {
            total += readCount;
            dataOutputStream.write(buffer);
        }
        System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));
        dataOutputStream.close();
        socket.close();
        inputStream.close();
    }
}

NIO 服务端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**
 * NIO 服务器端
 * @author doubily
 */
public class NIOServer {
    public static void main(String[] args) throws IOException {
        InetSocketAddress address = new InetSocketAddress(9527);

        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 e) {
                    // e.printStackTrace();
                    break;
                }
                // 倒带 Position = 0; mark 标志作废
                byteBuffer.rewind();
            }
        }
    }
}

NIO 客户端:

/**
 * NIO 客户端
 * @author doubily
 */
public class NIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost", 9527));

        // 得到文件channel
        String fileName = "F:\\3rdlib.zip";
        FileChannel fileChannel = new FileInputStream(fileName).getChannel();

        // 准备发送
        long startTime = System.currentTimeMillis();

        // 在 linux下一个transferTo 方法就可以完成传输
        // 在 windows 下 一次调用 transferTo 只能发送 8M, 就需要分段传输文件,而且要注意传输时的位置
        // 底层使用零拷贝
        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
        System.out.println("发送总字节数: " + transferCount + ", 耗时: " + (System.currentTimeMillis() - startTime));

        // 关闭
        fileChannel.close();
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值