再回首一些概念
- 阻塞IO
- 数据没有准备好, 读操作就会阻塞
- 数据不能立即被收时, 写操作就会阻塞
- 打开文件时阻塞, 直到某些条件发生
- 非阻塞IO
- 立即返回, 并用错误值来表示当前的状态
- 指定非阻塞方式
- 打开时指定O_NONBLOCK 标志
- 使用fcntl 打开或关闭非阻塞方式
- 网络编程时, 可以使用非阻塞, 用轮询方式发送
- 使用多线程可以避免使用非阻塞IO, 但是同步开销较大
多路IO
- 当程序需要同时从多个文件描述符读数据时
- 使用多进程/多线程, 同步复杂, 进程线程开销
- 使用非阻塞IO, 交替轮询
- 通过信号使用异步IO, 无法判断哪个IO完成
- 多路IO: 把关心的IO放入一个列表/文件描述符集合, 调用多路函数
- 多路IO函数阻塞, 直到有一个IO数据准备好后返回
- 返回后告诉调用者哪个描述符准备好了
select实现说明
- 调用select时通过参数告诉内核用户感兴趣的IO描述符
- 关心的IO状态: 输入,输出或错误三种状态
- 调用者等待指定的时间
- 返回之后内核告诉调用者哪些个描述符准备好了
- 哪些描述符发生了变化
- 调用返回后对准备好的描述符调用读写操作
- 不关心的描述符集合传NULL
函数详解
- man手册
select()
/* According to POSIX 1003.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
返回值:如果成功,返回所有sets中描述符的个数;如果超时,返回0;如果出错,返回-1。
监视readfds来查看是否read的时候会被堵塞
注意,即便到了end-of-file,fd也是可读的。
监视writefds看写的时候会不会被堵塞。
监视exceptfd是否出现了异常。
主要用来读取OOB数据,异常并不是指出错。
注意当一个套接口出错时,它会变得既可读又可写。
- 如果有了状态改变,会将其他fd清零,只有那些发生改变了的fd保持置位,以用来指示set中的哪一个改变了状态。
- 参数n是所有set里所有fd里,具有最大值的那个fd的值加1
与fd_set有关的函数
fd_set是一个位向量, 每位表示一个描述符
int FD_ISSET(int fd, fd_set *fdset);
//测试某个描述符是否在集合内用来指示一个fd是不是一个set的一部分。他很有用,用来看select后哪一个fd可用了。
void FD_CLR(int fd, fd_set *fdset);
//从集合内把一个描述符移除
void FD_SET(int fd, fd_set *fdset);
//把一个描述符加入集合
void FD_ZERO(fd_set *fdset);
//清空描述符集合
关于time_out
- timeout是从调用开始到select返回前,会经历的最大等待时间。
- 两种特殊情况:如果为值为0,会立刻返回。如果timeout是NULL,会阻塞式等待。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
一些调用使用3个空的set, n为zero, 一个非空的timeout来达到较为精确的sleep.
为了较好的可移植性,timeout在循环中需要被重新赋初值。
timeout== NULL
- 无限等待
- 被信号打断时返回-1, errno 设置成 EINTR
- timeout->tv_sec == 0 && timeout ->tv_usec == 0
- 不等待立即返回
- timeout->tv_sec != 0 || timeout ->tv_usec != 0
- 等待特定时间长度, 超时返回0
注意事项
- 可以把同一个描述符同时放入读和写集合
- 当读和写准备好时, 返回值的计数分别加1次
- 普通文件的三种状态总是返回准备好的状态
- 是否阻塞式IO不会影响select的结果
- 如果一个描述符到了文件结尾,select返回的状态是准备好
- 对一个准备好的描述符, 读出长度是0表示到达结尾