0. 概述
在第五章的TCP客户同时处理两个输入:标准输入和TCP套接字。我们遇到的问题是在客户阻塞于fgets调用期间,服务器进程会被杀死。服务器TCP虽然正确的给客户TCP发送了一个FIN,但是既然客户进程正阻塞于从标准输入读入的过程,它将看不到这个EOF,直到从套接字读时为止(可能经过很长时间)。这样的进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(也就是说输入已准备好被读取,或者描述符已能承接更多的输出),它就通知进程。这个能力称为I/O复用,是由select和poll这两个函数支持。
I/O复用使用于以下场合:
1) 当客户处理多个描述符(通常是交互式输入和网络套接字)时,必须使用I/O复用。
2) 一个客户同时处理多个套接字是可能的,不过比较少见。
3) 如果一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用I/O复用。
4)如果一个服务器既要处理TCP,又要处理UDP,一般就要使用I/O复用。
5) 如果一个服务器要处理多个服务或者多个协议,一般就要使用I/O复用。
1. I/O模型
一个输入操作通常包括两个不同的阶段:
(1) 等待数据准备好
(2) 从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
1) 阻塞式I/O模型
进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发生错误才返回。我们说进程在从调用recvfrom开始到它返回的整段时间内饰被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。
2) 非阻塞式I/O模型
进程把一个套接字设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成,不要把本进程投入睡眠,而是返回一个错误。
当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间。
当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间。
3) I/O复用模型
我们阻塞于select调用(而非阻塞于recvfrom处),等待数据报套接字变为可读。当select返回套接字可读这一条件时,我们调用recvfrom把所读数据报复制到应用进程缓冲区。使用select的优势在于我们可以等待多个描述符就绪。
4) 信号驱动式I/O模型
让内核在描述符就绪时发送SIGIO信号通知我们。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
5) 异步I/O模型
这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们