Netty详解(二)Linux 网络IO模型

1. Linux I/O基础知识

针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

Netty详解(二)Linux 网络IO模型_第1张图片

有了用户空间和内核空间,整个linux内部结构可以分为三部分,从最底层到最上层依次是:硬件–>内核空间–>用户空间。我们都知道,为了OS的安全性等的考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer。

Netty详解(二)Linux 网络IO模型_第2张图片

整个请求过程为: 用户进程发起请求,内核接受到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数据后再响应客户端。

2. Linux 网络I/O模型

在整个请求过程中,I/O设备数据输入至内核buffer需要时间,而从内核buffer复制数据至进程Buffer也需要时间。因此根据在这两段时间内等待方式的不同,I/O动作可以分为以下五种模式:

  • 阻塞I/O (Blocking I/O)
  • 非阻塞I/O (Non-Blocking I/O)
  • I/O复用(I/O Multiplexing)
  • 信号驱动的I/O (Signal Driven I/O)
  • 异步I/O (Asynchrnous I/O)

2.1 阻塞I/O (Blocking I/O)

Netty详解(二)Linux 网络IO模型_第3张图片

当用户进程调用了recvfrom这个系统调用,内核就开始了IO的第一个阶段:等待数据准备。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户进程才解除block的状态,重新运行起来。 所以,blocking IO的特点就是在IO执行的两个阶段都被block了。(整个过程一直是阻塞的)

2.2 非阻塞I/O (Non-Blocking I/O)

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

Netty详解(二)Linux 网络IO模型_第4张图片

当用户进程调用recvfrom时,系统不会阻塞用户进程,而是立刻返回一个ewouldblock错误,从用户进程角度讲 ,并不需要等待,而是马上就得到了一个结果(这个结果就是ewouldblock )。用户进程判断标志是ewouldblock时,就知道数据还没准备好,于是它就可以去做其他的事了,于是它可以再次发送recvfrom,一旦内核中的数据准备好了。并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。 当一个应用程序在一个循环里对一个非阻塞调用recvfrom,我们称为轮询。应用程序不断轮询内核,看看是否已经准备好了某些操作。这通常是浪费CPU时间。

2.3 I/O复用(I/O Multiplexing)

我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

Netty详解(二)Linux 网络IO模型_第5张图片

Linux提供select/epoll,进程通过将一个或者多个fd传递给select或者poll系统调用,阻塞在select操作上,这样select/poll可以帮我们侦测多个fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限,因此它的使用受到一定的限制。Linux还提供了一个epoll系统调用,epoll使用基于事件驱动的方式代替顺序扫描,因此性能更高一些。

I/O复用模型具体流程:用户进程调用了select,那么整个进程会被block,而同时,内核会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程。 这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个
system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。

2.4 信号驱动的I/O (Signal Driven I/O)

首先用户进程建立SIGIO信号处理程序,并通过系统调用sigaction执行一个信号处理函数,这时用户进程便可以做其他的事了,一旦数据准备好,系统便为该进程生成一个SIGIO信号,去通知它数据已经准备好了,于是用户进程便调用recvfrom把数据从内核拷贝出来,并返回结果。

Netty详解(二)Linux 网络IO模型_第6张图片

2.5 异步I/O

一般来说,这些函数通过告诉内核启动操作并在整个操作(包括内核的数据到缓冲区的副本)完成时通知我们。这个模型和前面的信号驱动I/O模型的主要区别是,在信号驱动的I/O中,内核告诉我们何时可以启动I/O操作,但是异步I/O时,内核告诉我们何时I/O操作完成。

Netty详解(二)Linux 网络IO模型_第7张图片

当用户进程向内核发起某个操作后,会立刻得到返回,并把所有的任务都交给内核去完成(包括将数据从内核拷贝到用户自己的缓冲区),内核完成之后,只需返回一个信号告诉用户进程已经完成就可以了。

3. 五种Linux I/O网络模型对比

Netty详解(二)Linux 网络IO模型_第8张图片
结果表明:前四个模型之间的主要区别是第一阶段,四个模型的第二阶段是一样的:过程受阻在调用recvfrom当数据从内核拷贝到用户缓冲区。然而,异步I/O处理两个阶段,与前四个不同。

从同步、异步,以及阻塞、非阻塞两个维度来划分来看

Netty详解(二)Linux 网络IO模型_第9张图片

4. I/O多路复用技术

在I/O编程中,当需要同时处理多个客户端接入请求的时候,可以利用多线程或者I/O多路复用技术来进行处理。I/O多路复用技术是通过I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。

I/O多路复用技术的优势:系统开销小,系统不需要创建新的额外进程或者线程,而不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源,I/O多路复用技术的应用场景如下:

  • 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字
  • 服务器需要同时处理多种网络协议的套接字

目前支持的I/O多路复用的系统调用有select、pselect、poll、epoll,为了克服select的缺点,epoll作了很多重大的改进:

  • 支持一个进程打开的socket描述符(FD)不受限制(仅受限于操作系统地最大文件句柄数)
  • I/O 效率不会随着FD的增加而直线下降
  • 使用mmap加速内核与用户空间的消息传递
  • epoll的API更加简单

5. 四种I/O的对比

5.1 同步非阻塞I/O

在早期的JDK1.4和1.5update10版本之前,JDK的Selector基于select/poll模型实现,它是基于I/O复用技术的非阻塞I/O,不是异步I/O。在JDK1.5 update10和Linux2.6以上版本,Sun优化了Selector的实现,它在底层使用了epoll替换了select/poll,但是上层的API并没有变化。所以NIO还是一种同步非阻塞I/O

5.2 异步非阻塞I/O

在JDK1.7提供的AIO新增了异步的套接字通道,它是真正的异步I/O,在异步I/O操作的时候可以传递信号变量,当操作完成之后会回调相关的方法,异步I/O也称为AIO。

5.3 多路复用器Selector

多路复用器的核心就是通过Selector来轮询其上的Channel,当发现某个或者多个Channel处于就绪状态后,从阻塞状态返回就绪的Channel的选择键集合,进行I/O操作。由于多路复用器是NIO实现非阻塞的关键,它又是主要通过Selector实现的。

5.4 伪异步 I/O

5.5 不同I/O模型对比

你可能感兴趣的:(Netty框架,Netty)