阻塞与非阻塞IO
一切皆可以阻塞或非阻塞
阻塞的概念:
什么是阻塞
因为某些资源和条件没有达成,导致程序停滞不前
阻塞的代价
浪费时间,与sleep一样,浪费系统资源
非阻塞的概念:
什么是非阻塞:完成不了的操作直接不做了,即为非阻塞
非阻塞的优点
非阻塞的代价:会错过相应的信息
非阻塞有什么用
当写文件的时候,如果想往文件中写内容,发生了哪些内容?
打开->write(系统调用,用户态,下沉到内核态),内核拿到数据写道磁盘中,内核写文件快缓冲,缓冲一会开始写,以机械硬盘为例,内核调度底层I/O设备管理,根据unload 和 block, 找到实际上东西在磁盘的哪个位置,然后磁头移动,磁盘转动,告诉移动到位置,通过磁的信息,写入数据,在这个过程中需要等待,对于阻塞I/O,涉及很多,为了方便上层应用快速返回,由内核开始往磁盘写的过程是透明的,对于普通用户,写入数据,数据返回了,就是写入了,但是这个数据,极有可能是在内核中,缓冲I/O,阻塞意味着,你把数据给内核,内核没给你返回之前,必须等待,非阻塞意味着给数据,但是写不写成功,与我无关。不过返回成功或者失败之后,会返回结果
fcntl:操作文件描述符
int fcntl(int fd, int cmd, …/* arg*/) : 后面的变参取决于cmd,需要的参数类型也取决于cmd,大多数情况下是int(比特掩码),返回值取决于文件操作
cmd 最多有一个参数
F_DUPFD(int) //复制一个文件描述符
// 文件描述flag
F_GETFD(void) // 获得一个文件描述符
F_SETFD(int) // 设置文件描述符
// 文件状态flag
F_GETFL(void) // 获得一个文件状态, 放在cmd 的地方,以返回值返回
F_SETFL(int) // 设置文件状态
/*************************************************************************
> File Name: common.h
> Author: Monster
> Mail: 2788490159@qq.com
> Created Time: Tue 05 Jan 2021 06:37:32 PM CST
************************************************************************/
#ifndef _COMMON_H
#define _COMMON_H
int make_nonblock(int fd);
int make_block(int fd);
#endif
/*************************************************************************
> File Name: common.c
> Author: Monster
> Mail: 2788490159@qq.com
> Created Time: Thu 30 Sep 2021 12:22:25 AM CST
************************************************************************/
#include "head.h"
int make_nonblock(int fd) {
int flag = fcntl(fd, F_GETFL);
if (flag < 0) {
return -1;
}
flag |= O_NONBLOCK;
return fcntl(fd, F_SETFL, flag);
}
int make_block(int fd) {
int flag = fcntl(fd, F_GETFL);
if (flag < 0) {
return -1;
}
flag &= ~O_NONBLOCK;//将阻塞位设为0
return fcntl(fd, F_SETFL, flag);
}
/*************************************************************************
> File Name: head.h
> Author: Monster
> Mail: 2788490159@qq.com
> Created Time: Sat 20 Mar 2021 10:31:49 AM CST
************************************************************************/
#ifndef _HEAD_H
#define _HEAD_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "common.h"//尽可能把自己的头文件放在后面
#endif
/*************************************************************************
> File Name: 1.test1.c
> Author: Monster
> Mail: 2788490159@qq.com
> Created Time: Sat 20 Mar 2021 10:50:22 AM CST
************************************************************************/
#include "head.h"
int main() {
int age;
make_nonblock(0);
sleep(5);
int ret = scanf("%d", &age);
printf("yuansiyu is %d year(s) old!\n", age);
printf("return value = %d\n", ret);
perror("scanf");
return 0;
}
tips : 当不加sleep的时候,程序会直接输出一个值,当加sleep 后,有5s的时间可以输入,数据会输入到缓冲区, 当0号文件运行的时候,直接读取缓冲区作为输入
使用场景:
1. 去动物园,猴子带游客参观,如果是阻塞的,如果打电话,上厕所,猴子会一直等你,人的阻塞操作,会影响猴子的工作效率,如果把阻塞放进高并发的服务器中,会严重拖累我们的系统。非阻塞为如果有需要,猴子出现,自己进行阻塞,猴子去服务其他人,所以需要进行一个I/O感知
IO 感知,在阻塞IO时候,在不需要的时候IO在睡眠状态,但是我们需要的是,在我们需要的那个IO 需要使用的时候,来告诉我。
select
I/O多路复用:一个I/O处理多个事情
int select(int nfds, fd_set *readfds, fd_set, *wirtefds, fd_set *exceptfds, struct timeval *timeout);
nfds:文件描述符的数量
fd_set: 文件描述符集合
readfds:读的文件描述符集合, 当有数据给自己
writefds:写的文件描述符集合,当这个文件描述符需要自己写数据
exceptfds:异常的文件描述符集合
timeout: 超时时间
void FD_CLR(int fd, fd_set *set); //把fd 从集合中清除
int FD_ISSET(int fd, fd_set *set); //判断集合中是否有fd
void FD_SET(int fd, fd_set *set);//把fd, 放到set 中
void FD_ZERO(fd_set *set); //清空set 集合
//以上用来操作集合
select 监控多个文件描述符,直到文件描述符就绪,可以执行io操作(可以直接读,或者可以直接写)
select 可以监控文件描述符的数量小于FD_SETSIZE, FD_SETSIZE 是一个宏定义, poll 没有这个限制
返回值,是被修改的文件数据个数,也就是可以进行读或者写的文件描述符的数量。原来的集合被抹掉了
因为原来的集合中的文件描述符会被抹掉,所以,如果select在循环中调用,那么sets在每次调用前必须初始化
三个文件描述符,中的每一个都可以为NULL
nfds应该是三个文件描述符最大的加1
timeout指定了监控时间,等待一个文件描述符准备好,调用会被阻塞,直到 1. 文件描准备好,2. 到时间,3. 调用被信号中断
因为进程很多,所以时间未必会精确,会超过一小部分时间,如果 timeout 里面的参数都是0, 那么会直接返回,用的比较少,如果timeout为NULL, 那么会一直阻塞下去
在Linux上,select 会更新timeout时间,反映了sleep了多少时间,其他操作系统未必
返回值:会返回所有文件描述符就绪的数量,如果timeout 为0,但是没有任何返回值,返回0,返回-1,需要用perror去查看发生什么错误,麻烦点在于,我们并不知道返回的io是哪一个io ,所以我们需要遍历我们所有的文件描述符,并且依次确认他们是否在读或者写或者异常的文件描述符之中来确认需要处理的文件描述符。
/*************************************************************************
> File Name: 1.select.c
> Author: Monster
> Mail: 2788490159@qq.com
> Created Time: Thu 30 Sep 2021 01:21:57 AM CST
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
fd_set rfds;
struct timeval tv;
int retval;
char in[1005];
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval) {
scanf("%s", in);
printf("Data is available now.\n");
/* FD_ISSET(0, &rfds) will be true. */
}
else
printf("No data within five seconds.\n");
exit(EXIT_SUCCESS);
}
poll /epoll
poll 执行与select 类似的操作,epoll 执行与poll 类似的操作,都是进行并发的时候使用的。