前言
自己学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数据报,当数据报处理完毕后,还会从将数据从内核空间
拷贝到用户空间
(内存)。完成上述步骤后才会唤醒用户进程,让用户进程处理数据。
步骤总结:
- 用户进程发起
recvfrom
调用,并阻塞于该系统调用 - 系统处理tcp数据,从
TCP缓冲区
中将数据拷贝到内核空间
的内存 - 数据报文接受完整后,内核将
内核空间
中的数据报文拷贝到用户空间
的内存中 - 唤醒用户进程,由用户进程处理数据
2. 非阻塞式I/O
非阻塞式I/O和阻塞式I/O的区别在于,非阻塞式I/O在数据报未准备好的时候,用户进程并不是阻塞在recvfrom
,而是会由内核返回一个错误(EWOULDBLOCK
)。用户进程收到这个错误后,就知道数据报没有准备好,会轮询的调用recvfrom
,直到数据报准备好之后,切换到内核态,由内核将数据从内核空间
拷贝到用户空间
(内存)。
步骤总结:
- 用户进程发起
recvfrom
调用 - 内核判断数据报是否准备好,如果没准备好返回
EWOULDBLOCK
错误, 用户进程重复步骤1。 - 数据报文接受完整后,用户进程阻塞,切换到内核态,由内核将
内核空间
中的数据报文拷贝到用户空间
的内存中 - 唤醒用户进程,由用户进程处理数据
非阻塞式I/O相比于阻塞式I/O的优缺点如下:
- 数据报没准备好之前,不会发生用户态到内核态的切换,这种切换的成本是很大的。
- 非阻塞式I/O会轮询调用,这往往耗费大量CPU时间。
3. I/O复用模型
I/O复用模型中,用户进程并不是阻塞在recvfrom
这个系统调用上,而是select/poll/epoll
之一的系统调用上。当数据报准备好的时候,由select/poll/epoll
唤醒用户进程,用户进程再发起recvfrom
,切换到内核态,由内核将数据从内核空间
拷贝到用户空间
(内存)。
步骤总结:
- 用户进程阻塞于
select/poll/epoll
调用 - 内核判断数据报准备好了之后,返回可读条件
- 数据报文接受完整后,用户进程阻塞,切换到内核态,由内核将
内核空间
中的数据报文拷贝到用户空间
的内存中 - 唤醒用户进程,由用户进程处理数据
I/O复用和阻塞式I/O对比:
- I/O复用第一步会阻塞于
select/poll/epoll
,然后再阻塞于recvfrom
,而阻塞式I/O会直接阻塞于recvfrom
,乍看之下,阻塞式I/O只调用了一个系统进程,应该会性能更好。但是,select/poll/epoll
是可以同时阻塞多个进程的,而recvfrom
每次只能负责一个进程的数据处理。
4. 信号驱动式I/O模型
信号驱动式I/O模型首先发起的并不是recvfrom
系统调用,而是sigaction
,这个系统调用会立即返回,用户进程可以继续工作,不会被阻塞。当数据报准备好的时候,由内核为用户进程产生一个SIGIO
信号,用户进程就可以在这个信号处理函数中可以发起recvfrom
的系统调用,拷贝数据了。
步骤总结:
- 用户进程发起
sigaction
调用(不会阻塞) - 内核判断数据包准备好之后,向用户进程发送
SIGIO
信号 - 用户进程在信号处理函数中,就可以发起
recvfrom
系统调用拷贝数据了
信号驱动式IO的优点:
从图中可以看出,只有发起recvfrom
系统调用拷贝数据的时候才会阻塞进程,相比较于前面的三种I/O模型,在阻塞的时间上,CPU的时间消耗上都得到了极大的提高。
5. 异步I/O模型
异步I/O模型是真正意义上的没有阻塞的IO模型,前面的四种模型都是阻塞的。用户进程再发起aio_read
调用后,就不管了,用户进程可以继续执行其他的事情了,由内核完成数据拷贝后(从内核空间拷贝到用户空间),向用户进程发起信号。这个时候数据已经完全准备好交给用户进程直接处理了。
步骤总结:
- 用户进程发起
aio_read
调用(不会阻塞) - 内核准备好数据,并拷贝到用户空间之后,向用户进程递交信号
- 用户进程在这个时候就可以直接处理数据了
异步I/O模型的优点:
纯异步,用户进程不需要考虑数据拷贝的问题,完全交由内核处理。
在当前常见的操作系统中,只有windows完整的支持了异步IO模型,而linux目前比较出名的异步I/O模型的支持库是libaio
,只有比较新的内核才有。
参考书籍:
《Unix网络编程 卷1:套接字联网API》(第三版)