NIO与Netty-3-NIO底层原理

  • NIO是调用的操作系统的io多路服用函数来实现的。

  • IO多路复用

    • IO多路复用实现了在一个线程监听多个IO,避免了一个线程监听一个IO并阻塞等待的极大性能开销。但这些方式仍然是阻塞的。

    • select

      • 工作原理
        • 每次调用select都要从用户空间将fdset拷贝到内核空间
        • 注册回调函数,遍历所有的fd,将当前线程挂到fd对应设备的等待队列中
        • 设备收到信息或写完数据后会唤醒等待队列中的线程
        • 超时时间到或者线程被唤醒时会遍历fd队列,找到有事件发生的fd
        • 根据发生的事件将fdset改为发生了事件的集合,把fdset从内核空间拷贝回用户空间
      • select返回时fdset已被修改有数据的位为1,因此下次调用select需要重新设置fdset
      • 监听的数量有大小限制
      • 每次调用select时都需要将fdset传入内核空间,
      • 寻找发生事件的fd时需要遍历fdset,遍历复杂度为o(n)
    • poll

      • poll原理同select类似。
      • 传入pollfd结构体数组,返回时将pollfd结构体中的reevents置位,因此无需重新设置pollfd数组
      • 监听的的fd数量无上限
      • 每调用次poll函数将pollfd数组拷贝到内核态的缺点仍存在
      • 寻找发生事件的fd的遍历复杂度为o(n)的缺点也存在
    • epoll

      • epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create,且epoll有两种模式。
      • epoll原理可见:https://blog.csdn.net/daaikuaichuan/article/details/83862311。
      • 无需每次重新设置fdevent集合
      • 监听的的fdevent数量无上限,一个fd包含多个fdevents(fd和关注事件组成)
      • 只在调用epoll_ct更改和添加监听fdeventl时拷贝epfd。
      • 有数据时返回,重排epfd有数据的放在前面,返回值为触发的总数,因此复杂度O(1)
    • 对比

      • select poll epoll
        操作方式 遍历 遍历 回调
        时间复杂度 O(n) O(n) O(1)
        最大连接数 1024(x86)或2048(x64) 无上限 无上限
        fd拷贝 每次调用都需要把fd集合从用户态拷贝到内核态 每次调用都需要把fd集合从用户态拷贝到内核态 调用epoll_ctl时拷贝,之后每次epoll_wait不拷贝
  • 零拷贝

    • 我们使用的网络服务器最常出现的功能就是文件的发送,而传统文件发送存在性能差的弊端,因此操作系统为我们提供了多种新的文件发送方式来提供网络服务器发送文件的性能。在Java的NIO中也提供了这些功能。

    • 传统拷贝

      • NIO与Netty-3-NIO底层原理
      • 1当调用read时,将陷入内核态等待DMA将数据拷贝到内核buffer|用户-内核
      • 2重新回到用户态将内核buffer的数据利用cpu拷贝到用户程序buffer|内核-用户
      • 3调用write时,利用cpu将数据拷贝到内核态的socketbuffer并陷入内核态|用户-内核
      • 4DMA负责将数据从socketbuffer拷贝到网卡中并由其负责发送|无切换
      • 5write方法返回,切换回用户态|内核-用户
      • 由此可见传统拷贝将涉及4次用户态内核态切换,且需要将数据来回拷贝4次,造成了极大的空间与时间浪费。
    • 内存映射方式mmap

      • NIO与Netty-3-NIO底层原理
      • mmap是通过内存映射,将内核buffer的内存映射到用户态的应用程序中,相当于用户态与内核态共享了这些数据,因此也就避免了一次数据的拷贝。
      • 1当调用mmap时,将陷入内核态等待DMA将数据拷贝到内核buffer|用户-内核
      • 2回到用户态,mmap返回|内核-用户
      • 3调用write时,利用cpu将数据拷贝到内核态的socketbuffer并陷入内核态|用户-内核
      • 4DMA负责将数据从socketbuffer拷贝到网卡中并由其负责发送|无切换
      • 5write方法返回,切换回用户态|内核-用户
      • 由此可见mmap方式发送文件将涉及4次用户态内核态切换,但只需将数据来回拷贝3次,且cpu拷贝只有1次。
    • 零拷贝sendfile

      • NIO与Netty-3-NIO底层原理

      • sendfile在linux2.1中提供,它主要用于发送文件到socket中,其较mmap避免了一次内核切换。

      • 1当调用sendfile时,将陷入内核态等待DMA将数据拷贝到内核buffer中|用户-内核

      • 2利用cpu将数据从内核buffer拷贝到socketbuffer中|无切换

      • 3DMA负责将数据从socketbuffer拷贝到网卡中并由其负责发送|无切换

      • 4sendfile方法返回,切换回用户态|内核-用户

      • 由此可见sendfile发送文件只需要2次内核状态切换,且数据只需要来回拷贝3次,且cpu拷贝只有1次。

      • Linux2.4的sendfile

        • 因为linux2.1提供的sendfile没有实现完全的零拷贝(不在内核中进行buffer到buffer的完整数据拷贝),在linux2.4时提供了真正的零拷贝。
        • NIO与Netty-3-NIO底层原理
        • 1当调用sendfile时,将陷入内核态等待DMA将数据拷贝到内核buffer中|用户-内核
        • 2利用cpu只将数据的一些位置、长度等很少的相关信息从内核buffer拷贝到socketbuffer中|无切换
        • 3DMA负责从内核buffer并利用socketbuffer中的相关信息一起拷贝到网卡中并由其负责发送|无切换
        • 4sendfile方法返回,切换回用户态|内核-用户
        • 因此新版本的sendfile发送文件也只需要2次内核状态切换,且但数据只需要完全拷贝2次(内核buffer-socketbuffer只有很少的数据信息),且cpu拷贝只有1次且那次拷贝的信息很少。
    • 四种拷贝方式的对比

      • 方式 内核态用户态切换 数据完全拷贝 cpu拷贝次数
        传统io 4次 3次 2次
        mmap 4次 3次 1次
        sendfile 2次 3次 1次
        新sendfile 2次 2次+一次不完全拷贝 1次(少量信息)

相关文章: