五种IO模型
在冯诺依曼计算机体系结构中,输入输出设备是其中的两大组件, 那么今天我们就来谈谈计算机中IO(输入输出)模型
IO分为 阻塞IO、非阻塞IO、信号驱动IO、IO多路转接、异步IO
阻塞IO
阻塞IO是我们在开发中非常常见的IO模型,我们在c中调用scanf、在c++中调用cin、调用系统read都会阻塞的等待用户输入,用户不输入,就会一直等待,形成的过程是分为
应用进程调用输入函数,内核进入等待,输入数据后数据是在内核,数据就拷贝到用户,再进行数据的处理
我们来画图:
非阻塞IO
阻塞IO效率往往是比较低,因为在IO输入或者输出的时候要等用户输入,如果用户不输入就会一直等下去,当然评论优缺点是根据场景的。这里我们如果用到的是当用户在没输入的时候输入输出就返回,这时候,就可以去干其它的事情,返回的是EWOULDBLOCK的错误码,但是这往往就需要搭配轮询的方式来进行去看输入输出是否有数据。
这种方式最大的缺点就是cpu的资源浪费比较大。
信号驱动IO
信号驱动IO是以信号的方式来通知输入输出的数据是否就绪,当调用read后,在内核等待数据时候,可以去干其它的事情,只要数据就绪,这个时候就会发送一个SIGIO信号通知应用程序进行IO操作,这个听起来很不错,就像你要去吃饭,但是饭没做好,你可以先去做别的事情,等饭好了系统在通知你。
但是这种方式适合一个线程的时候,如果是多个线程,那么当收到信号后,要进行数据拷贝,那么如果数据非常大,当收到信号进入内核后,所有线程都要停止,所以反而影响了其它的线程的执行。
IO多路转接
IO多路转接是一种,一个线程等待多个线程的文件描述符就绪,就是也是阻塞的等待,那么意思就是要多个线程的时候,是一个比较好的方式,如果只有一个线程那么和阻塞等待没有什么差别。
如何用一个线程去等待多个文件描述符的就绪呢?这就用到了我们的select、poll和epoll函数
就是把所有的等待交给select等待,如果好了就可以直接read数据无需等待。
我们画图来看:
异步IO
异步IO和信号驱动IO很像,但是又不一样。信号驱动是发出信号,告诉应用程序可以开始拷贝数据了,而异步IO是当数据拷贝完成了再通知,应用程序。
IO多路复用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:设置select的等待时间
返回值:
小于0:表示发生错误,错误存于errno中
等于0:表述设置的timeout时间已经超时
大于0:表示当前文件描述符状态改变的个数
这里我们要注意每个fd_set是一个结构体,结构体中用位图表示对应文件描述符的状态
所以用来操作结构体中位图就有以下四个函数
// 用来清除fd_set中某一位的文件描述符
void FD_CLR(int fd, fd_set* set);
// 用来检测fd_set中某一位的fd是否为真
int FD_ISSET(int fd, fd_set* set);
// 用来设置fd_set中某一位fd的值
void FD_SET(int fd, fd_set* set);
// 用来清除fd_set中所有的位·
void FD_ZERO(fd_set* set);
值得注意的一点是在select中,fd_set的三个参数否是输入输出参数,输入为设置需要监视的文件描述符,输出则就是就绪好的文件描述符,所以一般要保存原来的值用作下一次监视。
关于select的使用我们举个例子:
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
int main()
{
fd_set read_fd; // 输入输出参数
FD_ZERO(&read_fd); // 初始化为0
FD_SET(0, &read_fd); // 设置要监视的文件描述符
struct timeval time;
time.tv_sec = 5; // 5秒后超时 select返回不会再进行等待判断
for (;;)
{
fd_set read_fd_tmp = read_fd; // 保存设置好的文件描述符
printf(">");
fflush(stdout);
// 在这里阻塞等待
int result = select(1, &read_fd_tmp, NULL,NULL,&time);
if (result < 0)
{
perror("select error");
continue;
}
if (FD_ISSET(0, &read_fd_tmp)) // 判断是否就绪
{
printf("nihao\n");
char buf[512] = {0};
read(0, buf, sizeof(buf)-1);
printf("input:%s ", buf);
}
else if (result == 0) // 判断超时
{
printf("超时\n");
continue;
}
else // 错误处理
{
printf("invaild fd\n");
continue;
}
}
return 0;
}
select的缺点(和epoll比较)
如果不知道epoll后面博客更新。
select监控的文件描述符有上限
每次调用都需要手动的设置文件描述符集合,使用非常不便
每次调用都要把文件描述符从用户态拷贝到内核态,开销比较大
当就绪的文件描述符好后,需要循环遍历来进行判断,效率不好
最后用select实现的一个简单的TCP服务器
select实现的服务器
如有误, 还望指导!谢谢!