五种IO模型、IO多路复用之select用法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/GangStudyIT/article/details/81202242

五种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实现的服务器

如有误, 还望指导!谢谢!

猜你喜欢

转载自blog.csdn.net/GangStudyIT/article/details/81202242