1. 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力
2. 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标的输入、中断信号等等事件,再比如web服务器如nginx,需要同时处理来来自N个客户端的事件。
逻辑控制流在时间上的重叠叫做 并发
而CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。
2. 使用并发处理的成本:
线程/进程创建成本
CPU切换不同线程/进程成本 Context Switch
多线程的资源竞争
有没有一种可以在单线程/进程中处理多个事件流的方法呢?一种答案就是 IO多路复用。
因此IO多路复用解决的本质问题是在用更少的资源完成更多的事(read\write)。
IO模型
1、阻塞IO
2、非阻塞IO EAGAIN 忙等待 errno(提高效率)
3、信号驱动IO SIGIO 用的相对少(了解)
4、并行模型 进程,线程
5、IO多路复用 select、poll、epoll(效率相对高些)
一、阻塞io
二、非阻塞io
非阻塞IO ===》在阻塞IO的基础上 调整其为 不再阻塞等待。
在程序执行阶段调整文件的执行方式为非阻塞:
fcntl() ===>动态调整文件的阻塞属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改指定文件的属性信息。
参数:fd 要调整的文件描述符
cmd 要调整的文件属性宏名称
... 可变长的属性值参数
返回值:成功 不一定,看cmd
失败 -1;
eg:修改文件的非阻塞属性:
int flag ;
flag = fcntl(fd,F_GETFL,0); ///获取fd文件的默认属性到flag变量中。
flag = flag | O_NONBLOCK; ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag); ///将新属性flag设置到fd对应的文件生效。
以上代码执行后的阻塞IO将变成非阻塞方式。
fifo_w.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> int main(int argc, char *argv[]) { int ret = mkfifo("myfifo",0666); if(-1 == ret) { //如果是管道文件已存在错误,让程序继续运行 if(EEXIST== errno) { }else { perror("mkfifo"); exit(1); } } //open 会阻塞,等到另一端读段打开,解除阻塞 int fd = open("myfifo",O_WRONLY); if(-1 == fd) { perror("open"); exit(1); } while(1) { char buf[256]="hello,fifo,test"; write(fd,buf,strlen(buf)); sleep(3); } close(fd); return 0; }
fifo_r.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> int main(int argc, char *argv[]) { int ret = mkfifo("myfifo",0666); if(-1 == ret) { //如果是管道文件已存在错误,让程序继续运行 if(EEXIST== errno) { }else { perror("mkfifo"); exit(1); } } int fd = open("myfifo",O_RDONLY); if(-1 == fd) { perror("open"); exit(1); } int flag = fcntl(fd,F_GETFL); flag = flag|O_NONBLOCK ; fcntl(fd,F_SETFL,flag); flag = fcntl(0,F_GETFL); fcntl(0,F_SETFL,flag|O_NONBLOCK); while(1) { char buf[256]={0}; if(read(fd,buf,sizeof(buf))>0) { printf("fifo %s\n",buf); } bzero(buf,sizeof(buf)); if(fgets(buf,sizeof(buf),stdin)) { printf("terminal:%s\n",buf); } } close(fd); //remove("myfifo"); return 0; }
三、信号驱动io
文件描述符需要追加 O_ASYNC 标志。
设备有io事件可以执行时,内核发送SIGIO信号。
1)追加标志
int flag ;
flag = fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flag | O_ASYNC);
2)设置信号接收者
fcntl(fd,F_SETOWN,getpid());//常用设置
3)对信号进行捕获
signal(SIGIO,myhandle);
fifo_w.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> int main(int argc, char *argv[]) { int ret = mkfifo("myfifo",0666); if(-1 == ret) { //如果是管道文件已存在错误,让程序继续运行 if(EEXIST== errno) { }else { perror("mkfifo"); exit(1); } } //open 会阻塞,等到另一端读段打开,解除阻塞 int fd = open("myfifo",O_WRONLY); if(-1 == fd) { perror("open"); exit(1); } while(1) { char buf[256]="hello,fifo,test"; write(fd,buf,strlen(buf)); sleep(3); } close(fd); return 0; }
fifo_r.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <signal.h> int fd; void handle(int num) { char buf[256]={0}; read(fd,buf,sizeof(buf)); printf("fifo %s\n",buf); } int main(int argc, char *argv[]) { signal( SIGIO,handle); int ret = mkfifo("myfifo",0666); if(-1 == ret) { //如果是管道文件已存在错误,让程序继续运行 if(EEXIST== errno) { }else { perror("mkfifo"); exit(1); } } fd = open("myfifo",O_RDONLY); if(-1 == fd) { perror("open"); exit(1); } int flag = fcntl(fd,F_GETFL); fcntl(fd,F_SETFL ,flag|O_ASYNC); fcntl(fd,F_SETOWN, getpid() ); while(1) { char buf[256]={0}; fgets(buf,sizeof(buf),stdin); printf("terminal:%s\n",buf); } close(fd); //remove("myfifo"); return 0; }
四、并行
五、io多路复用
1.阻塞版
select循环服务器 ===> 用select函数来动态检测 有数据流动的文件描述符
#include <sys/select.h>
#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);
功能:完成指定描述符集合中有效描述符的动态检测。
该函数具有阻塞等待功能,在函数执行完毕后,目标测试集合中将只保留最后有数据的描述符。
参数:nfds 描述符的上限值,一般是链接后描述符的最大值+1;
readfds 只读描述符集
writefds 只写描述符集
exceptfds 异常描述符集
以上三个参数都是 fd_set * 的描述符集合类型
timeout 检测超时 如果是NULL表示一直检测不超时 。
返回值:超时 0
失败 -1
成功 >0
为了配合select函数执行,有如下宏函数:
void FD_CLR(int fd, fd_set *set);
功能:将指定的set集合中编号为fd的描述符号删除。
int FD_ISSET(int fd, fd_set *set);
功能:判断值为fd的描述符是否在set集合中,如果在则返回真,否则返回假。
void FD_SET(int fd, fd_set *set);
功能:将指定的fd描述符,添加到set集合中。
void FD_ZERO(fd_set *set);
功能:将指定的set集合中所有描述符删除。