Netty学习-1. I/O模型

438 阅读5分钟

前言

自己学netty的经历一直是断断续续的,希望通过写博客方式鞭策自己。一方面可以记录自己的学习历程,一方面激励自己坚持下去。

1. 背景介绍

Netty作为Java中最出名的高性能网络通信的框架,一直备受各大公司喜爱。比如dubbo框架的底层也是采用Netty作为通信框架。熟悉Netty的同学都知道,Netty支持BIO(OioServerSocketChannel)和NIO(NioServerSocketChannel)两种模式的IO模型,那么这些I/O模型有啥区别呢?还有别的I/O模型吗?

2. 五种I/O模型

1. 阻塞式I/O

阻塞式I/O模型是最常见的I/O模型,进程调用recvfrom后,当前进程会阻塞于recvfrom的系统调用,同时会系统会从用户态切换到内核态,由内核处理tcp数据报,当数据报处理完毕后,还会从将数据从内核空间拷贝到用户空间(内存)。完成上述步骤后才会唤醒用户进程,让用户进程处理数据。

步骤总结:

  1. 用户进程发起recvfrom调用,并阻塞于该系统调用
  2. 系统处理tcp数据,从TCP缓冲区中将数据拷贝到内核空间的内存
  3. 数据报文接受完整后,内核将内核空间中的数据报文拷贝到用户空间的内存中
  4. 唤醒用户进程,由用户进程处理数据

image.png

2. 非阻塞式I/O

非阻塞式I/O和阻塞式I/O的区别在于,非阻塞式I/O在数据报未准备好的时候,用户进程并不是阻塞在recvfrom,而是会由内核返回一个错误(EWOULDBLOCK)。用户进程收到这个错误后,就知道数据报没有准备好,会轮询的调用recvfrom,直到数据报准备好之后,切换到内核态,由内核将数据从内核空间拷贝到用户空间(内存)。 步骤总结:

  1. 用户进程发起recvfrom调用
  2. 内核判断数据报是否准备好,如果没准备好返回EWOULDBLOCK错误, 用户进程重复步骤1。
  3. 数据报文接受完整后,用户进程阻塞,切换到内核态,由内核将内核空间中的数据报文拷贝到用户空间的内存中
  4. 唤醒用户进程,由用户进程处理数据

非阻塞式I/O相比于阻塞式I/O的优缺点如下:

  1. 数据报没准备好之前,不会发生用户态到内核态的切换,这种切换的成本是很大的。
  2. 非阻塞式I/O会轮询调用,这往往耗费大量CPU时间。

image.png

3. I/O复用模型

I/O复用模型中,用户进程并不是阻塞在recvfrom这个系统调用上,而是select/poll/epoll之一的系统调用上。当数据报准备好的时候,由select/poll/epoll唤醒用户进程,用户进程再发起recvfrom,切换到内核态,由内核将数据从内核空间拷贝到用户空间(内存)。

步骤总结:

  1. 用户进程阻塞于select/poll/epoll调用
  2. 内核判断数据报准备好了之后,返回可读条件
  3. 数据报文接受完整后,用户进程阻塞,切换到内核态,由内核将内核空间中的数据报文拷贝到用户空间的内存中
  4. 唤醒用户进程,由用户进程处理数据

I/O复用和阻塞式I/O对比:

  1. I/O复用第一步会阻塞于select/poll/epoll,然后再阻塞于recvfrom,而阻塞式I/O会直接阻塞于recvfrom,乍看之下,阻塞式I/O只调用了一个系统进程,应该会性能更好。但是,select/poll/epoll是可以同时阻塞多个进程的,而recvfrom每次只能负责一个进程的数据处理。 image.png

4. 信号驱动式I/O模型

信号驱动式I/O模型首先发起的并不是recvfrom系统调用,而是sigaction,这个系统调用会立即返回,用户进程可以继续工作,不会被阻塞。当数据报准备好的时候,由内核为用户进程产生一个SIGIO信号,用户进程就可以在这个信号处理函数中可以发起recvfrom的系统调用,拷贝数据了。 步骤总结:

  1. 用户进程发起sigaction调用(不会阻塞)
  2. 内核判断数据包准备好之后,向用户进程发送SIGIO信号
  3. 用户进程在信号处理函数中,就可以发起recvfrom系统调用拷贝数据了

信号驱动式IO的优点: 从图中可以看出,只有发起recvfrom系统调用拷贝数据的时候才会阻塞进程,相比较于前面的三种I/O模型,在阻塞的时间上,CPU的时间消耗上都得到了极大的提高。

image.png

5. 异步I/O模型

异步I/O模型是真正意义上的没有阻塞的IO模型,前面的四种模型都是阻塞的。用户进程再发起aio_read调用后,就不管了,用户进程可以继续执行其他的事情了,由内核完成数据拷贝后(从内核空间拷贝到用户空间),向用户进程发起信号。这个时候数据已经完全准备好交给用户进程直接处理了。 步骤总结:

  1. 用户进程发起aio_read调用(不会阻塞)
  2. 内核准备好数据,并拷贝到用户空间之后,向用户进程递交信号
  3. 用户进程在这个时候就可以直接处理数据了

异步I/O模型的优点: 纯异步,用户进程不需要考虑数据拷贝的问题,完全交由内核处理。 image.png

在当前常见的操作系统中,只有windows完整的支持了异步IO模型,而linux目前比较出名的异步I/O模型的支持库是libaio,只有比较新的内核才有。

参考书籍: 《Unix网络编程 卷1:套接字联网API》(第三版)