NIO/ZeroCopy

1、传统IO:面向流

  • 字节流和字符流:
    • 字节流:以字节为单位,每次次读入或读出是8位数据。能够读任何类型数据。
    • 字符流:以字符为单位,每次次读入或读出是16位数据。其只能读取字符类型数据。
  • 输出流和输入流:
    • 输出流:从内存读出到文件。只能进行写操做。
    • 输入流:从文件读入到内存。只能进行读操做。
  • 节点流和处理流:
    • 节点流:直接与数据源相连,读入或读出。
    • 处理流:与节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。

2、BIO

BIO就是传统的socket编程。每有一个客户端连入,服务端就须要另起线程为其服务,很容易形成资源枯竭。html

3、NIO:面向缓冲区

Channel管道比做成铁路,buffer缓冲区比做成火车(运载着货物)java

  • buffer演示:

public static void main(String[] args) {

        // 建立一个缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 添加一些数据到缓冲区中
        String s = "Java3y";
        byteBuffer.put(s.getBytes());
        //切换成读模式
        byteBuffer.flip();
        // 建立一个limit()大小的字节数组(由于就只有limit这么多个数据可读)
        byte[] bytes = new byte[byteBuffer.limit()];
        // 将读取的数据装进咱们的字节数组中
        byteBuffer.get(bytes);
        // 输出数据
        System.out.println(new String(bytes, 0, bytes.length));
}

 

  • channel演示

// 1. 经过本地IO的方式来获取通道
        FileInputStream fileInputStream = new FileInputStream("F:\\3yBlog\\JavaEE经常使用框架\\Elasticsearch就是这么简单.md");

        // 获得文件的输入通道
        FileChannel inchannel = fileInputStream.getChannel();

        // 2. jdk1.7后经过静态方法.open()获取通道
        FileChannel.open(Paths.get("F:\\3yBlog\\JavaEE经常使用框架\\Elasticsearch就是这么简单2.md"), StandardOpenOption.WRITE);

    复制文件编程

    

    使用内存映射文件的方式实现文件复制的功能(直接操做缓冲区):设计模式

    

    通道之间经过transfer()实现数据的传输(直接操做缓冲区):数组

    

 

  • 网络IO模型

阻塞I/O:网络

非阻塞I/O:并发

I/O多路复用:框架

在Linux下对文件的操做是利用文件描述符(file descriptor)来实现的socket

在Linux下它是这样子实现I/O复用模型的:调用select/poll/epoll/pselect其中一个函数,传入多个文件描述符,若是有一个文件描述符就绪,则返回,不然阻塞直到超时。函数

  1. 当用户进程调用了select,那么整个进程会被block;
  2. 而同时,kernel会“监视”全部select负责的socket;
  3. 当任何一个socket中的数据准备好了,select就会返回;
  4. 这个时候用户进程再调用read操做,将数据从kernel拷贝到用户进程(空间)。

因此,I/O 多路复用的特色是经过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符其中的任意一个进入读就绪状态,select()函数就能够返回。select/epoll的优点并非对于单个链接能处理得更快,而是在于能处理更多的链接

IO多路复用对应的设计模式:Reactor模式

http://www.javashuo.com/article/p-yeqleamk-kt.html

 

  • 零拷贝

1、普通拷贝

    

    

read(file, tmp_buf, len);
write(socket, tmp_buf, len);
  1. 程序使用read()系统调用,系统由用户态转换为内核态,磁盘中的数据由DMA(Direct memory access)的方式读取到内核读缓冲区(kernel buffer)。DMA过程当中CPU不须要参与数据的读写,而是DMA处理器直接将硬盘数据经过总线传输到内存中。
  2. 系统由内核态转为用户态,当程序要读的数据已经彻底存入内核读缓冲区之后,**程序会将数据由内核读缓冲区,写入到用户缓冲区,**这个过程须要CPU参与数据的读写。
  3. 程序使用write()系统调用,系统由用户态切换到内核态,数据从用户缓冲区写入到网络缓冲区(Socket Buffer),这个过程须要CPU参与数据的读写。
  4. 系统由内核态切换到用户态,网络缓冲区的数据经过DMA的方式传输到网卡的驱动(存储缓冲区)中(protocol engine)

能够看到,普通的拷贝过程经历了四次内核态和用户态的切换(上下文切换),两次CPU从内存中进行数据的读写过程,这种拷贝过程相对来讲比较消耗系统资源。

2、零拷贝

内存映射方式(NIO中的map()就是用的这种):

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);
  1. mmap()系统调用首先会使用DMA的方式将磁盘数据读取到内核缓冲区,而后经过内存映射的方式,使用户缓冲区和内核读缓冲区的内存地址为同一内存地址,也就是说不须要CPU再讲数据从内核读缓冲区复制到用户缓冲区。
  2. 当使用write()系统调用的时候,cpu将内核缓冲区(等同于用户缓冲区)的数据直接写入到网络发送缓冲区(socket buffer),而后经过DMA的方式将数据传入到网卡驱动程序中准备发送。

能够看到这种内存映射的方式减小了CPU的读写次数,可是用户态到内核态的切换(上下文切换)依旧有四次(调用map()是两次,调用write()是两次),同时须要注意在进行这种内存映射的时候,有可能会出现并发线程操做同一块内存区域而致使的严重的数据不一致问题,因此须要进行合理的并发编程来解决这些问题。

sendfile()方式(NIO中的transfer()就是用的这种):

sendfile(socket, file, len);
  1. sendfile()系统调用也会引发用户态到内核态的切换,与内存映射方式不一样的是,用户空间此时是没法看到或修改数据内容,也就是说这是一次彻底意义上的数据传输过程。
  2. 从磁盘读取到内存是DMA的方式,从内核读缓冲区读取到网络发送缓冲区,依旧须要CPU参与拷贝,而从网络发送缓冲区到网卡中的缓冲区依旧是DMA方式。

依旧有一次CPU进行数据拷贝,两次用户态和内核态的切换操做,相比较于内存映射的方式有了很大的进步,但问题是程序不能对数据进行修改,而只是单纯地进行了一次数据的传输过程。

4、select,poll,epoll

http://www.javashuo.com/article/p-cntwfxgr-eb.html

===========================

NIO:

https://juejin.im/post/5af942c6f265da0b7026050c

ZeroCopy:

https://blog.csdn.net/cringkong/article/details/80274148

https://www.jianshu.com/p/8c6b056f73ce

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息