I/O复用适用于很多网络通信中,有以下适用场景:
- 当用户处理多个描述符,在socket通信中,服务器accept函数等待用户完成TCP三次握手,在未完成队列中处理监听套接字和在服务器在键入数据准备发送给客户端时,例如用fgets函数进行阻塞等待输入完成,这两种情况,不能阻塞在特定的描述符上,否则可能会引起软中断而终止程序。
- 单个服务器处理多个已连接套接字,就应该使用I/O复用让它成为迭代服务器处理客户请求。(这种情况比较少见)
- 单个服务器同时处理监听套接字和已连接套接字。
- 服务器既要处理TCP,又要处理UDP。
- 一个服务器处理多个协议或者多个服务。
I/O模型分为5种:
- 阻塞式I/O
- 非阻塞性I/O
- I/O复用
- 信号驱动式I/O
- 异步I/O
阻塞式I/O
如图,我们把recvfrom视为系统调用,它会一直阻塞到数据从内核写入应用程序缓冲区的时候才告诉应用程序有数据过来了,有点像read从套接字描述符中读取数据。不一定就一定是数据,也有可能是发生错误返回,常见的错误就是被信号中断,而内核又不准备重启。
关键在于阻塞式I/O会一直阻塞(等待)到有数据进入应用程序的缓冲区才返回。
非阻塞式I/O
非阻塞式I/O在阻塞式I/O上加入了轮询,隔一会儿就去询问内核有无数据。应用程序采用这种方式大大增加了CPU的负担。
I/O复用
有了I/O复用,我们可以调用select或者poll,阻塞在这两个系统调用(select和poll)的某一个上,而不是阻塞在真正的I/O调用上。当阻塞在select上时,我们等待我们“关心”的描述符变为可读(说的直白一点就是等待我们关心的描述符有数据过来,而select就是通知我们有数据过来了)
信号驱动式I/O模型
信号驱动式I/O模型在有数据到来的时候,通过发送SIGIO信号通知我们。首先sigaction系统调用安装一个信号处理函数,当有信号递交的时候触发这个信号处理函数,在这个函数中可以对描述符收到的数据进行处理,也可以通知主函数对这个描述符进行数据处理。这种模型优势在于,不用阻塞等待数据到来,但是一有数据到来,就通知我们。
异步I/O
异步I/O与信号驱动式I/O非常相似,区别在于信号驱动式I/O在有数据来的时候通知我们,而由我们对数据进行操作,是读取还是丢弃;而异步I/O则是通知我们I/O操作已经完成了。调用aio_read函数,给内核传递描述符、缓冲区指针、缓冲区大小和文件偏移,并告诉内核整个操作完成的时候通知我们。
前面四种I/O模型的主要区别在于第一阶段,但第二阶段都是一样的,进程都会阻塞于系统调用,等到内核将数据复制到应用程序缓冲区。相反,异步I/O模型这两个阶段合并在一起了,都要处理。
下面是五种模型时间片对比:
主要的还是这五种I/O模型,并不能说哪个就一定是最好的,还是要看使用场景。