五种IO模型

我们在进行IO的时候,分为两个过程等待资源就绪+拷贝。
因此IO=等+拷贝。
那么所谓的高效IO,就是降低等的占比

五种IO模型

我先以生活中钓鱼的例子来形容五种IO模型。

来钓鱼的人会有各种不同的行为。

首先是我们的张三,这是个非常专注的钓鱼者,他会全神贯注盯着鱼竿,期间不做任何事。

随之而来的是李四,李四则是在钓鱼的时候刷刷视频,时不时看看鱼竿有没有动静。

王五钓鱼又不同于他们,他将一个铃铛系在鱼竿上,然后就专心干自己的事情不去理会鱼竿,只有铃铛响了他才会去收杆。

赵六则是方圆十里的首富,他带来一百根鱼竿一起钓鱼,不断来回跑看看哪根鱼竿上钓了。

最后是田七,田七则不享受钓鱼的过程,只想要鱼,因此他不参与钓鱼的过程,让自己的手下去钓鱼。

我们将上述钓鱼过程中的人看成系统调用,鱼竿就是socket,湖是系统内部,🐟相当于数据,鱼漂浮动就是数据就绪,钓就是发生拷贝。

那么上述五种钓鱼行为对应五种钓鱼模型。

张三:阻塞IO

李四:非阻塞IO

王五:信号驱动IO

赵六:多路复用/多路转接IO

田七:异步IO

事实上,阻塞IO才是最常见的IO,占90%以上。因为他十分简单。所有的套接字,默认都是阻塞方式。

上述IO效率最高的自然就是多路复用IO。

同步通信vs异步通信

同步通信synchronouscommunication

异步通信asynchronous communication

我们将上述五种IO模型中的前四种称为同步IO,最后一种称为异步IO。

这时候就有人提出异议了:"诶,前面信号部分不是说过信号是异步的吗,那为什么信号驱动IO不是异步的?"

这里就要明确一下,这两个异步和同步不是一个概念。

通信中的同步和异步关注的是消息通信机制:

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就得到返回值了;换句话说,就是由调用者主动等待这个调用的结果;
  • 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

而我们进程、线程中的同步讲的是两种执行流的执行呈现一定顺序性。

非阻塞IO

我们这里浅浅讲一下非阻塞IO怎么实现。

我们正常打开open打开文件是阻塞方法打开的。注意到我们的open其实是可以传三个参数的:

我们传入O_NONBLOCK 或 O_NDELAY就能以非阻塞方式打开文件。

但是这样有违我们正常的编写习惯,这里还有个能将阻塞方法打开的文件描述符改为非阻塞的替代方案:

fcntl 函数有5种功能:

• 复制一个现有的描述符(cmd=F_DUPFD).

• 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

• 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

• 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

• 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

我们此处只是用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞.

如此我们就能实现一个将文件描述符设置为非阻塞的函数:

然后我们再编写一下主函数非阻塞轮询逻辑,还记得这个在我们进程等待的时候也用过非阻塞轮询的方法。

再次之前我们要知道read如果在非阻塞轮询时,没有读到数据会返回什么:

是的,读到数据就会出错返回。

那怎么区分真的出错还是没读到数据呢?

我们可以根据他设置的错误码不同,如果是没读到数据设置的错误码是:

EAGAIN 或EWOULDBLOCK。其实这两是一个值:

除此之外,read还有可能因为信号中断,这时错误码会设置为:

那么对这些特殊情况稍微处理一下之后:

cpp 复制代码
int main()
{
    char buffer[1024];
    SetNonBlock(0);
    while(true)
    {
        printf("Enter# ");
        fflush(stdout);
        ssize_t n = ::read(0, buffer, sizeof(buffer)-1);
        if(n > 0)
        {
            buffer[n] = 0;
            printf("echo# %s", buffer);
        }
        else if(n == 0)  // ctrl + d
        {
            printf("read done\n");
            break;
        }
        else
        {
            if(errno == EWOULDBLOCK)
            {
                sleep(1);
                std::cout << "底层数据没有就绪,开始轮询检测" << std::endl;
                std::cout << "可以做其他事情" << std::endl;
                continue;
            }
            else if(errno == EINTR)
            {
                continue;
            }
            else
            {
                perror("read");
                break;
            }
        }
    }

    return 0;
}

尝试运行:

非常完美!

但是非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询.这对CPU来说是较大的浪费,一般只有特定场景下才使用.

相关推荐
大连好光景4 小时前
socket.socket模块--网络通信
网络·python·网络协议
掘根4 小时前
【消息队列项目】服务器实现
运维·服务器
一只旭宝4 小时前
Linux专题十:I/O 复用进阶(LT/ET 模式)同步,异步阻塞,以及 libevent 库核心知识点
linux·服务器·网络
菩提小狗4 小时前
第1天:基础入门-操作系统&名词&文件下载&反弹SHELL&防火墙绕过|小迪安全笔记|网络安全|
网络·笔记·学习·安全·web安全
wniuniu_4 小时前
ceph修改
网络·ceph
想做后端的小C4 小时前
Linux:期末考点
linux·运维·服务器
jimy14 小时前
本地下载vscode server安装包(tar.gz)然后上传至服务器开发机
服务器·ide·vscode
代码游侠4 小时前
复习——网络测试工具
linux·开发语言·网络·笔记·学习·测试工具
qq_406176144 小时前
JavaScript的同步与异步
前端·网络·tcp/ip·ajax·okhttp