文章目录
一、I/O复用
前面提到,当客户阻塞于fgets时,服务器进程若被杀死,虽然会给客户端发送FIN,但客户端看不到这个EOF,直到从套接字读。这样的进程需要一种预先告知内核的能力,使内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程。这个能力称为I/O复用(multiplexing)。
I/O复用通常用于服务器设计:
(1)处理多个连接,或者同时处理监听套接字和连接套接字
(2)同时处理TCP协议和UDP协议
(3)同时处理多个服务
二、I/O模型
2.1 阻塞I/O模型
默认情况下,所有套接字都是阻塞的。为了简单起见,以数据报套接字为例:
2.2 非阻塞I/O模型
前三次调用recvfrom时无数据返回,因此内核返回EWOULDBLOCK错误。第四次调用已有数据报准备好,它被复制到应用进程缓冲区,recvfrom成功返回。
这种轮询(polling)内核查看某个操作是否就绪的方法,比较耗费CPU时间。
2.3 I/O复用模型
我们可以调用select或poll,使进程阻塞在其中一个系统调用上,而不是阻塞在真正的I/O系统调用上。
在多线程中使用阻塞式I/O与这种模型很相似,使用一个线程来控制一个描述符。
2.4 信号驱动式I/O模型
我们也可以使用信号,让内核在描述符就绪时发送SIGIO信号通知我们,称为signal-driven I/O:
这种模型的优势在于等待数据报到达期间进程不被阻塞,主循环可以继续执行。
2.5 异步I/O模型
异步I/O(asynchronous I/O)由POSIX规范定义,其工作机制是:告知内核启动某个操作,并在整个操作完成后通知我们。这种模型与前一节的信号驱动模型的主要区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
例如调用aio_read函数,给内核传递描述符、缓冲区指针、缓冲区大小和文件偏移,并告知内核整个操作完成时如何通知我们。
注:异步I/O模型需要支持的系统
前四种模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom)将阻塞进程。
三、select函数
该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生的时或经历一段指定的时间后才唤醒它。
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
//若有就绪描述符则返回其数量,超时返回0,出错返回-1
struct timeval{
long tv_sec;
long tv_usec;
};
关于timeval参数意义:
(1)永远等待直至有描述符准备好,参数置为空指针
(2)等待一段固定的时间,参数所指结构体设为需要等待的时间
(3)不等待,立即返回,参数所指结构体时间设为0
中间的三个参数分别表示要测试读、写和异常的描述符集合。描述符集通常是一个整数数组,其中每一个整数中的每一位对应一个描述符。假设使用32位整数,则该数组的第一个元素对应描述符0~31,第二个元素对应描述符32-63,以此类推(实现细节由内核完成)。
有关操作的四个宏:
void FD_ZERO(fd_set* fdset); //清空(重置)fdset
void FD_SET(int fd, fd_set* fdset); //fd加入set(开启set中对应位)
void FD_CLR(</