五种常见的io模型
- 阻塞式io:阻塞IO:在内核将数据准备好之前,系统调用会一直等待所有的套接字, 默认都是阻塞方式.
- 非阻塞io:如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码.
- 信号驱动io:内核将数据准备好的时候, 使⽤用SIGIO信号通知应⽤用程序进行IO操作.
- io多路转接:虽然从流程图上看起来和阻塞IO类似实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态.
- 异步io:由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).
任何IO过程中, 都包含两个步骤.
- 第一是等待, 第二是拷贝数据.而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间.让IO更高效, 最核心的办法就是让等待的时间尽量少.
I/O多路转接之select
select 系统调用的用途是:在指定的一段时间内,监听用户感兴趣的文件描述符上的可读、可写、和异常等事件。该函数允许进程指示内核等待多个事件中的任何一个事件发生,并且只在有一个或多个事件发生或者经历一段指定的时间后才唤醒它。
select函数原型:
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数解释:
1. maxfds 参数指定被监听的(待测定)文件描述符的个数,它通常被设置为select监听的所有文件描述符的最大值加一,因为文件描述符是从0开始计数的。
2. readfds、writefds、exceptfds 三个参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用select函数,通过这三个参数传入自己感兴趣的描述符。select函数返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。*输入输出型参数*
3. 参数timeout为结构timeval,⽤用来设置select()的等待时间
timeval 结构体的定义如下:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
参数timeout取值:
- NULL:则表⽰示select()没有timeout,select将⼀一直被阻塞,直到某个⽂文件描述符上发生了事件;
- 0:仅检测描述符集合的状态,然后⽴立即返回,并不等待外部事件的发⽣生。
- 特定的时间值:如果在指定的时间段⾥里没有事件发⽣生,select将超时返回。
关于fd_set结构
每个fd_set结构体中仅包含一个整型数组,该数组的每个元素中的每一位(bit)标记一个文件描述符。
提供了一组操作fd_set的接口, 来比较⽅方便的操作位图.
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
函数返回值:
- 执行成功则返回文件描述词状态已改变的个数
- 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回。
- 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测。
错误值可能为: * EBADF 文件描述词为无效的或该文件已关闭 * EINTR 此调用被信号所中断 * EINVAL
参数n 为负值。 * ENOMEM 核心内存不足
socket就绪条件:
读就绪:
- socket内核中, 接收缓冲区中的字节数, ⼤大于等于低⽔水位标记SO_RCVLOWAT. 此时可以⽆阻塞的读该文件描述符, 并且返回值⼤大于0;
- socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
- 监听的socket上有新的连接请求;
- socket上有未处理的错误;
写就绪
- socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置⼤小), 大于等于低⽔水位标记SO_SNDLOWAT, 此时可以⽆无阻塞的写, 并且返回值⼤大于0;
- socket的写操作被关闭(close或者shutdown).对⼀个写操作被关闭的socket进⾏行写操作, 会触发SIGPIPE信号;
- socket使⽤非阻塞connect连接成功或失败之后;
- ocket上有未读取的错误;
异常就绪
- socket上收到带外数据.