簡單的聊聊網絡請求中的內存拷貝

掘金是本身剛發現不久的平臺,本來一些學習筆記都是記錄在有道,由於正好兩邊都支持markdown,如今打算把一些整理後的筆記分享出來。這篇主要來簡單的聊聊網絡請求中的內存拷貝。git

網絡請求中數據傳輸過程圖

數據傳輸類型一(read)

該數據傳輸模型正是傳統的IO進行網絡通信時所採用的方式,數據在用戶空間(JVM內存)與內核空間進行屢次拷貝和上下文切換,對於沒有對數據進行業務處理的時候,這樣拷貝顯得很沒有必要。github

數據傳輸類型二(sendFile)

該數據傳輸模型是的NIO進行網絡通信時所採用的方式,它依賴於操做系統是否支持這種對於內核的操做(圖中第4個過程),這個模型對比第一種減小了兩次沒必要要的用戶空間和內核之間的數據拷貝過程。bash

數據傳輸類型三(支持彙集的sendFile)

從中咱們能夠發現這種真正實現了零拷貝,這種傳輸模型它依賴於操做系統是否支持這種對於內核的操做(圖中4過程),圖中4過程看着很難理解,下面把四過程裏面的奧祕分解下。markdown

四過程實際上是將內核中文件信息(文件地址、大小等信息)appendStr到Sokcet Buffer中,這樣Sokcet Buffer中存有不多的信息,而後在協議引擎傳輸以前使用Gather將兩個Buffer彙集。網絡

數據傳輸類型四(mmap,本文先不介紹)

代碼實現

模型一(BIO)

/**
 * @Author CoderJiA
 * @Description TransferModel1Client
 * @Date 23/2/19 下午3:01
 **/
public class TransferModel1Client {

    private static final String HOST = "localhost";
    private static final int PORT = 8899;
    private static final String FILE_PATH = "/Users/coderjia/Documents/gradle-5.2.1-all.zip";
    private static final int MB = 1024 * 1024;

    public static void main(String[] args) throws Exception{
        Socket socket = new Socket(HOST, PORT);
        InputStream input = new FileInputStream(FILE_PATH);
        DataOutputStream output = new DataOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[MB];
        long start = System.currentTimeMillis();
        int len;
        while ((len = input.read(bytes)) != -1) {
            output.write(bytes, 0, len);
        }
        long end = System.currentTimeMillis();
        System.out.println("耗時:" + (end - start) + "ms");
        output.close();
        input.close();
        socket.close();
    }
}
複製代碼
/**
 * @Author CoderJiA
 * @Description TransferModel1Server
 * @Date 23/2/19 下午3:01
 **/
public class TransferModel1Server {

    private static final int PORT = 8899;
    private static final int MB = 1024 * 1024;

    public static void main(String[] args) throws Exception{
        ServerSocket serverSocket = new ServerSocket(PORT);
        for (;;) {
            Socket socket = serverSocket.accept();
            DataInputStream input = new DataInputStream(socket.getInputStream());
            byte[] bytes = new byte[MB];
            for (;;) {
                int readSize = input.read(bytes, 0, MB);
                if (-1 == readSize) {
                    break;
                }
            }
        }
    }
}
複製代碼

模型二(NIO)

/**
 * @Author CoderJiA
 * @Description TransferModel2Client
 * @Date 23/2/19 下午3:36
 **/
public class TransferModel2Client {

    private static final String HOST = "localhost";
    private static final int PORT = 8899;
    private static final String FILE_PATH = "/Users/coderjia/Documents/gradle-5.2.1-all.zip";

    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(HOST, PORT));
        socketChannel.configureBlocking(true);
        FileChannel fileChannel = new FileInputStream(FILE_PATH).getChannel();
        long start = System.currentTimeMillis();
        fileChannel.transferTo(0, fileChannel.size(), socketChannel);
        long end = System.currentTimeMillis();
        System.out.println("耗時:" + (end - start) + "ms");
        fileChannel.close();
    }

}
複製代碼
/**
 * @Author CoderJiA
 * @Description TransferModel2Server
 * @Date 23/2/19 下午3:36
 **/
public class TransferModel2Server {

    private static final int PORT = 8899;
    private static final int MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {

        InetSocketAddress address = new InetSocketAddress(PORT);

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.setReuseAddress(true);
        serverSocket.bind(address);

        ByteBuffer byteBuffer = ByteBuffer.allocate(MB);
        for (;;) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(true);
            int readSize = 0;
            while (-1 != readSize) {
                readSize = socketChannel.read(byteBuffer);
                byteBuffer.rewind();
            }

        }

    }
}
複製代碼

fileChannel.transferTo(0, fileChannel.size(), socketChannel)app

transferto方法的文檔註釋:This method is potentially much more efficient than a simple loop that reads from this channel and writes to the target channel.Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them.socket

這句話簡單的理解就是:該方法比傳統的簡單輪詢(指的就是IO中的拷貝過程)更加高效,tranferto的拷貝方式依賴於底層操做系統,目前不少操做系統支持像模型二拷貝過程。在內核版本2.4中,修改了套接字緩衝區描述符以適應這些要求——在Linux下稱爲零拷貝。 這種方法不只減小了多個上下文切換,還徹底消除了處理器的數據複製操做。oop

性能對比

在同一臺機器上,相同環境下測試結果如圖。性能

參考文章地址

www.jianshu.com/p/e9f422586…學習

源碼地址

github.com/coderjia061…

相關文章
相關標籤/搜索