阻塞IO与非阻塞IO
IO模型
IO本质是基于操作系统接口来控制底层的硬件之间数据传输,并且在操作系统中实现了多种不同的IO方式
IO模型描述的是不同的IO方式,比较常用的几种:
- 阻塞型IO模型
- 非阻塞型IO模型
- 多路复用IO模型
- 信号驱动IO模型
阻塞IO模型
当程序发出IO请求后,阻塞进程(让进程进入睡眠状态),资源就绪后唤醒进程继续执行
一般默认的IO操作都是阻塞的,即程序发出IO请求后,如果IO操作没有完成,则进程会被阻塞,直到IO操作完成后才会返回结果
非阻塞IO模型
非阻塞IO模型的特点是当发出IO请求后,如果IO操作没有完成,则立即返回,告诉调用者IO操作还未完成,并不等待IO操作完成
- 实现非阻塞IO模型,需要设置O_NONBLOCK标志,表示打开非阻塞模式
- 设置方式有两种:
- 使用fcntl函数设置
- 使用open函数设置,如:
fd = open(filename, O_NONBLOCK)
fcntl函数设置非阻塞模式:
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
- fd参数: 文件描述符
- cmd参数:
- F_SETFL: 设置文件状态的标志
- F_GETFL: 获取文件状态的标志
- F_SETFD: 设置文件描述符的附加标志
- F_GETFD: 获取文件描述符的附加标志
- arg参数:
- O_NONBLOCK: 设置非阻塞模式
- O_BLOCK: 设置阻塞模式
// todo 设置非阻塞的IO模型
//将标准输入改为非阻塞的IO模式
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
int flags;//保存原来的标志位
flags = fcntl(0, F_GETFL);//获取原来的标志位
flags |= O_NONBLOCK;//追加一个标志位,设置非阻塞的IO模式
fcntl(0, F_SETFL, flags);//设置新的标志位
char c;
while(1){
fgets(&c, 1, stdin);
printf("read %c\n", c);
}
return 0;
}
open函数设置非阻塞模式:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
-
flags参数:
- O_NONBLOCK: 设置非阻塞模式
- O_BLOCK: 设置阻塞模式
多路复用IO模型
多路复用IO模型是利用select、poll、epoll等系统调用,可以同时监视多个文件描述符,一旦某个文件描述符就绪,则立即通知调用者
- select: 监视的文件描述符数量有限制,一般1024个
- poll: 监视的文件描述符数量没有限制
- epoll: 监视的文件描述符数量没有限制,支持水平触发和边缘触发
本质上是通过复用一个进程来处理多个IO请求:
由内核来监控多个文件描述符是否可以进行IO操作,一旦可以进行IO操作,则立即通知调用者,否则继续等待
select 多路复用方案
select模型的基本思路是:
- 通过单进程创建一个文件描述符集合,将需要监控的文件描述符添加到这个集合中
- 由内核负责监控文件描述符是否可以进行读写,一旦可以读写,则通知相应的进程可以进行相应的 I/O 操作
select模型的缺点:
- 单个进程能够监视的文件描述符数量有限,因此在大量连接的情况下,效率较低
- 同时监视的文件描述符数量受限,因此在大量并发的情况下,效率较低
函数实现
select 函数
监控一组文件描述符,阻塞当前进程,由内核检测相应的文件描述符是否就绪,一旦有文件描述
符就绪,将就绪的文件描述符拷贝给进程,唤醒进程处理
函数原型:
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
函数参数:
- nfds: 集合中最大的文件描述符号+1
- readfds: 可读文件描述符集合的指针
- writefds: 可写文件描述符集合的指针
- exceptfds: 其他文件描述符集合的指针
- timeout: 超时时间结构体变量的指针
可用宏:
-
- FD_SETSIZE: 文件描述符集合的最大容量
-
- FD_SET(int fd, fd_set* set )//设置文件描述符集合
-
- FD_CLR( int fd, fd_set* set )//清除文件描述符集合
-
- FD_ISSET( int fd, fd_set* set )//判断文件描述符是否在文件描述符集合中
-
- FD_ZERO( fd_set* set )//清空文件描述符集合
函数返回值:
- 若超时,返回0
- 若有文件描述符就绪,返回就绪的文件描述符个数
示例: 多路复用IO select 函数,使用select 函数监控标准输入,有输入打印相应的信息
// todo 多路复用IO select 函数,,使用select 函数监控标准输入,有输入打印相应的信息
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(){
int ret;
int maxfd=0;
fd_set readfds;// 定义一个文件描述符集合
FD_ZERO(&readfds);// 初始化文件描述符集合
FD_SET(0,&readfds);// 将标准输入加入文件描述符集合
struct timeval timeout; // 定义一个超时时间
timeout.tv_sec=3; // 设置超时时间
timeout.tv_usec=0;
//? 3s后超时,如果文件描述符集合没有任何描述符就绪,就会超时返回
//? timeout超时时间,会被select函数首次超时修改,循环时候需要重新赋值
//? select函数还会将每次就绪的文件描述符直接拷贝到原集合中,相当于将未就绪的从文件中删除了
//? 如果超时返回,相当于原集合直接被清空了.
struct timeval temp;
fd_set tempfds;
while (1){
temp=timeout; // 超时时间重新赋值
tempfds=readfds; // 文件描述符集合重新赋值
// 调用select函数,监控文件描述符集合中的文件描述符
//@param maxfd: 最大的文件描述符
//@param readfds: 监控的文件描述符集合
//@param writefds: 监控可写的文件描述符集合
//@param exceptfds: 监控异常的文件描述符集合
//@param timeout: 超时时间
//@return: 返回值是监控的文件描述符集合中就绪的文件描述符个数
ret=select( maxfd+1, &tempfds, NULL, NULL, &temp); // 监控标准输入
if (ret<0){
perror("select error");
return -1;
}
if (ret==0){
//超时
printf("timeout\n");
continue;
}
if (ret>0){
//有文件描述符就绪
//! FD_ISSET函数功能:判断文件描述符fd是否在文件描述符集合fdset中
//! @param fd: 文件描述符
//! @param fdset: 文件描述符集合
if (FD_ISSET(0,&readfds)){
//标准输入就绪
char buffer[1024]={
0};
fgets(buffer,1024,stdin);
printf("input:%s",buffer);
}
}
}
return 0;
}
select 函数原理:
//select 函数原理
//函数原型:
#include <sys/select.h>
extern int select (int __nfds, fd_set *__restrict __readfds,
fd_set *__restrict __writefds,
fd_set *__restrict __exceptfds,
struct timeval