网络IO的发展过程随着Linux内核版本的迭代而发生变化,主要经历以下几个阶段:
1. 阻塞 IO(BIO)
2. 非阻塞 IO(NIO)
3. IO 多路复用第一版(select/poll)
4. IO 多路复用第二版(epoll)
5. 异步 IO(AIO)
而每一个阶段,都是因为当前的网络有一些缺陷,因此又在不断改进该缺陷。这是网络 IO 一直演变过程中的本质。
1.网络的两个阶段
在网络中,我们通常可以将其广义上划分为以下两个阶段:
第一阶段:硬件接口到内核态
第二阶段:内核态到用户态
我们上网发送信息时,大部分数据都是通过网线传递的。因此对于两台计算机而言,要进行网络通信,其数据都是先从应用程序传递到传输层(TCP/UDP)到达内核态,然后再到网络层、数据链路层、物理层,接着数据传递到硬件网卡。接收信息时,通过网络传输介质传递到对端机器的网卡,然后再一步一步数据从网卡传递到内核态,最后再拷贝到用户态。
上面所说的两个阶段是接收信息时的两个阶段。
2.概念区别
2.1 阻塞 IO 和非阻塞 IO 的区别
阻塞IO与非阻塞IO主要区别是在第一阶段,硬件接口到内核态是否发生阻塞。
如果用户发起了读写请求,但内核态数据还未准备就绪,该阶段不会阻塞用户操作,内核立马返回,则称为非阻塞 IO。
如果该阶段一直阻塞用户操作。直到内核态数据准备就绪,才返回。这种方式称为阻塞 IO。
2.2 同步 IO 和异步 IO 的区别
数据传输有两个阶段,在此处只要任何一个阶段会阻塞用户请求,都将其称为同步 IO,两个阶段都不阻塞,则称为异步 IO。
在目前所有的操作系统中,linux 中的 epoll、mac 的 kqueue 都属于同步 IO,因为其在第二阶段(数据从内核态到用户态)都会发生拷贝阻塞。而只有 windows 中的 IOCP 才真正属于异步 IO,即 AIO。
3. 阻塞 IO
根据前面的介绍,阻塞 IO 主要指的是第一阶段(硬件网卡到内核态)。
3.1 阻塞 IO的概念
阻塞 IO,顾名思义当用户发生了系统调用后,如果数据未从网卡到达内核态,内核态数据未准备好,此时会一直阻塞。直到数据就绪,然后从内核态拷贝到用户态再返回。
3.2 阻塞 IO 的缺点
在一般使用阻塞 IO 时,都需要配置多线程来使用,最常见的模型是阻塞 IO+多线程,每个连接一个单独的线程进行处理。
我们知道,一般一个程序可以开辟的线程是有限的,而且开辟线程的开销也是比较大的。也正是这种方式,会导致一个应用程序可以处理的客户端请求受限。面对百万连接的情况,是无法处理。
4. 非阻塞 IO
非阻塞 IO 是为了解决前面提到的阻塞 IO 的缺陷而引出的。
4.1 非阻塞 IO 的概念
非阻塞 IO:见名知意,就是在第一阶段(网卡-内核态)数据未到达时不等待,然后直接返回。因此非阻塞 IO 需要不断的用户发起请求,询问内核数据好了没,好了没。非阻塞 IO 是需要系统内核支持的。
4.2 非阻塞 IO 的优点
非阻塞 IO 解决了阻塞 IO每个连接一个线程处理的问题,所以其最大的优点就是 一个线程可以处理多个连接,这也是其非阻塞决定的。
4.3 非阻塞 IO 的缺点
但这种模式,也有一个问题,就是需要用户多次发起系统调用。频繁的系统调用是比较消耗系统资源的。
因此,既然存在这样的问题,那么自然而然我们就需要解决该问题:保留非阻塞 IO 的优点的前提下,减少系统调用。
5. IO 多路复用第一版
为了解决非阻塞 IO 存在的频繁的系统调用这个问题,随着内核的发展,出现了 IO 多路复用模型。
5.1 IO 多路复用概念
多路复用主要复用的是通过有限次的系统调用来实现管理多个网络连接。最简单来说,我目前有 10 个连接,我可以通过一次系统调用将这 10 个连接都丢给内核,让内核告诉我,哪些连接上面数据准备好了,然后我再去读取每个就绪的连接上的数据。因此,IO 多路复用,复用的是系统调用。通过有限次系统调用判断海量连接是否数据准备好了。
5.2 IO 多路复用第一版的概念
使用select/poll做多路复用。
5.3 IO 多路复用第一版的优点
IO 多路复用,主要在于复用,通过 select()或者 poll()将多个 socket fds 批量通过系统调用传递给内核,由内核进行循环遍历判断哪些 fd 上数据就绪了,然后将就绪的 readyfds 返回给用户。再由用户进行挨个遍历就绪好的 fd,读取或者写入数据。
所以通过 IO 多路复用+非阻塞 IO,一方面降低了系统调用次数,另一方面可以用极少的线程来处理多个网络连接。
5.4 IO 多路复用第一版的缺点
虽然第一版 IO 多路复用解决了之前提到的频繁的系统调用次数,但同时引入了新的问题:用户需要每次将海量的 socket fds 集合从用户态传递到内核态,让内核态去检测哪些网络连接数据就绪了
但这个地方会出现频繁的将海量 fd 集合从用户态传递到内核态,再从内核态拷贝到用户态。所以,这个地方开销也挺大。
既然还有这个问题,那我们继续开始解决这个问题,因此就引出了第二版的 IO 多路复用。
其实思路也挺简单,既然需要拷贝,那就想办法,不拷贝。既然不拷贝,那就在内核开辟一段区域咯
6. IO 多路复用第二版
epoll 的出现是为了解决前面提到的 IO 多路复用第一版的问题。
6.1 IO 多路复用第二版的优点
IO 多路复用第二版 epoll 的优点在于:
一开始就在内核态分配了一段空间,来存放管理的 fd,所以在每次连接建立后,交给 epoll 管理时,需要将其添加到原先分配的空间中,后面再管理时就不需要频繁的从用户态拷贝管理的 fd 集合。通过这种方式大大的提升了性能。
所以现在的 IO 多路复用主要指 epoll。
7. 异步 IO
前面介绍的所有网络 IO 都是同步 IO,因为当数据在内核态就绪时,在内核态拷贝用用户态的过程中,仍然会有短暂时间的阻塞等待。而异步 IO 指:内核态拷贝数据到用户态这种方式也是交给系统线程来实现,不由用户线程完成,目前只有 windows 系统的 IOCP 是属于异步 IO。