之前在服务器进程终止中讨论的情形,TCP客户端同时要处理两个输入,一是标准输入,二是TCP套接口。而此时若是服务器进程被杀死,服务器尽管正确地给客户发送了FIN
分节,但是由于此时客户正阻塞于标准输入fgets()
,直到读完一行用户输入(也许此时TCP服务器已经死透了),才能看到那个文件结束符。
因此,我们需要一个能力,对于上面两个I/O,只要有一个或多个I/O条件满足,都应该正确地通知到,这个能力被称为I/O复用,由函数select
和poll
支持。
针对网络应用场景,有以下情形,
I/O复用并非只限于网络编程,许多其他应用也大范围使用这个能力。
首先来看一下Unix下我们可用的五个I/O模型:
SIGIO
)一个输入操作一般有两个不同的阶段:
对于套接口上的输入操作,上述步骤具体是:
缺省时,所有套接口都是阻塞的。《Unix网络编程》一书中,前五章的所有例子都使用阻塞I/O模型。
为了阐述简单,这里以UDP套接口作为例子,将函数recvfrom
视为系统调用,它会有从应用进程中运行和内核中运行的相互切换。
进程调用recvfrom
,此系统调用直到数据报到达且拷贝到应用缓冲区或是出错才返回。此过程可能出现的错误是系统调用被信号中断。我们说的进程阻塞,指的是从进程调用recvfrom
开始到它返回的这段时间,当进程返回成功提示时,应用进程开始处理数据报。
如果一个套接口被设置成非阻塞模式时,上面说的数据报没有准备好时,进程不会睡眠,而是由内核返回一个错误。
仍讨论之前说的UDP数据报例子:
recvfrom
,内核立即返回一个EWOULDBLOCK
错误。recvfrom
,数据报从内核拷贝到应用缓冲区,recvfrom
返回成功提示,进程紧接着会处理数据。当一个应用进程对一个非阻塞描述字巡回调用recvfrom
时,我们称此过程为轮询(polling)。应用进程连续不断地查询内核,数据是否准备好,这对CPU时间是极大的浪费。
在I/O复用模型下,我们不再阻塞于真正的I/O系统调用recvfrom
,而是在select
和poll
这两个系统调用之一阻塞。
例如,阻塞于select
调用,等待数据报套接口(可以是多个中任意一个)可读,函数返回对应标识,此时便可调用recvfrom
将数据报拷贝到应用缓冲区中。
尽管多了一次系统调用,但是select
函数可以等待多个套接口描述字这一点,是使用I/O复用模型的一大理由。
之前讨论过,内核可以产生信号,进程可以调用sigaction
安装一个信号处理函数。这个模型的思路是,数据报准备号以后发送一个信号给进程,信号处理函数收到信号后,调用recvfrom
,进行后续的拷贝和处理。
这一模型的好处是,任意一个系统调用都是非阻塞的,主循环可以继续进行,直到数据报准备好的信号到达。
异步I/O模型中,不再调用函数recvfrom
,而是调用函数aio_read
,给内核传递描述字,缓冲区指针,缓冲区大小,文件偏移,并告诉内核当整个操作完成时如何通知。此调用立即返回,进程不必阻塞等待I/O操作完成。
这里内核在操作完成时也会产生一个信号,与信号驱动I/O模型不同,这个信号要在数据由内核拷贝到应用缓冲区才产生。
除了真正的异步I/O模型以外,其他几种模型,最后一阶段的处理都是相同的——阻塞于recvfrom
调用,将数据从内核拷贝到应用缓冲区。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。