【Linux网络】IO模型{再识 IO/IO模型/阻塞IO vs 非阻塞IO/同步IO vs 异步IO}


硬件设备性能/读写速率,严格影响应用层的设置,所以了解IO很有必要!

小知识:

Windows/Linux对标准输入终止读取按键不同 Linux是ctrl+d

1.再识 IO

调用系统接口 write、read ,本质是把数据从用户层/语言级别缓冲区拷贝给操作系统/内核缓冲区,(写入到 OS 的发送缓冲区中 / 从 OS 的接收缓冲区中读取数据)–=-- 本质是拷贝函数!

调用 write 的时候只有当发送缓冲区中有足够的空间才能进行拷贝,当发送缓冲区没有空间了,此时 write 只能阻塞等待,不能继续拷贝。
调用 read 的时候,只有当接收缓冲区有数据才能进行读取拷贝,当接收缓冲区没有数据了,此时 read 也只能阻塞等待。
IO 的过程被分为两个部分:等待数据就绪和拷贝数据。

在 IO 的过程中,要进行拷贝,必须先判断条件成立 --=-- 读写事件是否就绪。

高效的 IO

在单位时间内/一次读|写过程,等待占时的比重越小,IO 的效率越高!几乎所有的提高IO效率的策略,都是以这个为依据!

2.IO模型【钓鱼举例】

计算机在应用中不可避免的操作是IO,随着时间的推移,人们在探索IO模型的路上不断改进,试图寻求更高效/更实用的IO策略!

2.1-2.4为同步IO:【参与IO,等待方式不同】

2.1阻塞 IO:鱼竿落下,老实等待

在内核将数据准备好之前,系统调用会一直等待。**所有的套接字/read/write/recv,默认都是阻塞方式。**阻塞IO是最常见的IO模型。
在这里插入图片描述

2.2非阻塞IO:鱼竿落下,游戏电影,时不时观察

非阻塞 IO 就是,如果内核未将数据准备好,系统调用直接返回,并返回 EWOULDBLOCK 错误码。

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

2.3信号驱动 IO:鱼竿挂铃铛,有鱼会通知

内核将数据准备好的时候,使用 SIGIO 信号通知应用程序进行 IO 操作。
在这里插入图片描述

2.4多路转接/复用:100个鱼竿下水,轮询。

在这里插入图片描述

阻塞IO
在这里插入图片描述

IO 多路转接看起来和阻塞 IO 类似,核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态。

2.5异步IO:老板(用户)让秘书(OS)钓鱼,有鱼电话通知老板【不参与钓鱼/IO,只是发起IO,最后拿结果】

由内核在数据拷贝完成时,通知应用程序。和信号驱动 IO 的区别在于,信号驱动是告诉应用程序何时可以开始拷贝数据。而异步IO是在数据拷贝完成时才通知应用程序。
在这里插入图片描述
任何 IO 过程中,都包含两个步骤:等待 + 拷贝。在实际的应用场景中,等待占比时间一般较大。让 IO 更高效==》让等待的时间尽量少。
异步IO的实现逻辑较同步来说相对混乱,有些新技术逐渐取代这种混乱:协程。

协程(Coroutines):

协程是一种比线程更轻量级的并发模型,它允许在单个线程中执行多个任务,并且能够在任务之间高效地切换。协程可以与异步IO结合使用,以实现非阻塞的IO操作。许多现代编程语言都支持协程,如Python、Go等。

3.阻塞IO vs 非阻塞IO

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

阻塞调用:调用结果返回之前,当前进程/线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用:在不能立刻得到结果之前,该进程不会被挂起而是立即返回。
非阻塞IO在等待的时候可以做其他事情,它们之间等的方式不一样,导致非阻塞IO在效率上稍微高一点。(等待的时候可以做其他事情,等待占比减少,效率提高,但是拷贝动作耗时相同)。

open:打开的时候就可以设置为非阻塞–通过flags参数。只不过fcntl比较通用!

在这里插入图片描述

回顾fd

fd:文件描述符–数组下标–指向文件结构体–文件结构体含flag字段

回顾缓冲区知识

在这里插入图片描述
这两种都能让信息在执行这行代码时就输出到显示器(行刷新) 只不过第一种能让打印后的光标紧跟信息,更美观。

int fcntl(int fd, int cmd, … /* arg */ );

recv()三个参数是和 read() 一样,最后一个参数 flag 设为 0 默认是阻塞等待。将这个参数设为 MSG_DONTWAIT,变为非阻塞IO

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
通用做法:使用 fcntl() 接口。文件描述符是一个数组下标,所有的网络通信、文件等,都是读写文件描述符,每一个文件描述符指向的是内核中的文件对象,文件对象有关于这个文件的 flags 标记位。通过 fcntl() 接口来设置一个文件描述符的属性!–=-- 设置其文件对象中的 flags 标志位,告诉内核这个指定的文件描述符以非阻塞的方式来操作。系统接口如下:
在这里插入图片描述
按照指定的 cmd 对指定的文件描述符进行可变参数部分的设置。
传入的 cmd 的值不同,后面追加的参数也不相同。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)

详细了解

Linux中的fcntl接口是一个功能强大的系统调用,它提供了对文件描述符(file descriptor)进行底层控制的接口。通过fcntl,开发者可以精细地管理文件或套接字的行为,实现多种控制功能。以下是关于fcntl接口的简要叙述:

  1. 函数原型
    在C语言中,fcntl函数的原型定义在<fcntl.h>头文件中,其基本形式如下:

c
#include <fcntl.h>
int fcntl(int fd, int cmd, …);
fd:是要操作的文件描述符。
cmd:是控制操作的命令,决定了fcntl函数执行的具体操作类型。
…:是与cmd相关联的可选参数,根据cmd的不同,参数的数量和类型也会有所不同。
2. 常见操作
fcntl支持多种操作,以下是一些常见的操作类型:

F_GETFL:获取文件描述符的状态标志,如是否设置了非阻塞模式等。
F_SETFL:设置文件描述符的状态标志,如将文件描述符设置为非阻塞模式。
F_GETFD:获取文件描述符的标志,如是否设置了FD_CLOEXEC标志(表示在exec调用时关闭该文件描述符)。
F_SETFD:设置文件描述符的标志。
F_GETLK、F_SETLK、F_SETLKW:用于获取、设置或阻塞地设置文件锁。这些操作允许进程对文件的特定部分进行加锁,以防止其他进程同时访问。
3. 非阻塞IO
在网络编程或文件操作中,非阻塞IO是一种重要的技术。通过fcntl的F_SETFL命令和O_NONBLOCK标志,可以轻松地将文件描述符设置为非阻塞模式。在非阻塞模式下,如果IO操作不能立即完成,系统调用会立即返回一个错误(如EAGAIN或EWOULDBLOCK),而不是让调用线程等待。

  1. 文件锁
    fcntl还提供了文件锁的功能,通过F_GETLK、F_SETLK和F_SETLKW命令,进程可以对文件的特定部分进行加锁,以防止其他进程同时读写该部分。这对于实现进程间的同步和互斥访问非常有用。

  2. 示例
    以下是一个使用fcntl将文件描述符设置为非阻塞模式的简单示例:

c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main() {
int fd, flags;
fd = open(“file.txt”, O_RDWR);
if (fd == -1) {
perror(“open”);
exit(EXIT_FAILURE);
}

// 获取当前文件描述符标志  
if ((flags = fcntl(fd, F_GETFL)) == -1) {  
    perror("fcntl - F_GETFL");  
    close(fd);  
    exit(EXIT_FAILURE);  
}  

// 设置非阻塞标志  
flags |= O_NONBLOCK;  
if (fcntl(fd, F_SETFL, flags) == -1) {  
    perror("fcntl - F_SETFL");  
    close(fd);  
    exit(EXIT_FAILURE);  
}  

printf("文件描述符已设置为非阻塞模式。\n");  
close(fd);  
return 0;  

}
6. 总结
fcntl接口是Linux系统中一个非常重要的系统调用,它提供了对文件描述符的精细控制,包括状态标志的设置、文件锁的获取与释放等。通过fcntl,开发者可以更加灵活地处理文件和网络IO操作,提高程序的性能和可靠性。

代码一:阻塞式等待

在这里插入图片描述

在这里插入图片描述

代码二:非阻塞等待

在这里插入图片描述

由于设置对stdin设置了非阻塞等待 所以一旦调用read就会直接返回 由于read没有调用成功(那一瞬间用户没有输入数据)所以read返回-1。

在这里插入图片描述

修改:读取错误的原因

在这里插入图片描述
由于设置对stdin设置了非阻塞等待 所以一旦调用read就会直接返回 由于read没有调用成功(那一瞬间用户没有输入数据)所以read返回-1。

在这里插入图片描述

修改:n<0后不break
在这里插入图片描述

在这里插入图片描述

由于设置对stdin设置了非阻塞等待 所以一旦调用read就会直接返回 由于read没有调用成功(那一瞬间用户没有输入数据)所以read返回-1。如果不break,会继续执行while的printf和fflush语句,接着调用read,同样的没有输入,故有这样的输出结果。并且此时输入数据是可以被读取的!
在这里插入图片描述

区分真的出错还是没有数据:errno

在这里插入图片描述

在这里插入图片描述

完整代码

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstdio>
#include <cerrno>
#include <cstring>

using namespace std;

void SetNonBlock(int fd)
{
    int cur_flag = fcntl(fd, F_GETFL);
    if (cur_flag < 0)
    {
        perror("fcntl");
        return;
    }
    fcntl(fd, F_SETFL, cur_flag | O_NONBLOCK);
    cout << " set fd " << fd << " nonblock done" << endl;
}

int main()
{
    char buffer[1024];
    SetNonBlock(0);
    sleep(1);
    while (true)
    {
        printf("Please Enter# ");
        fflush(stdout);
        sleep(1);

        ssize_t n = read(0, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n - 1] = 0;
            cout << "echo : " << buffer << endl;
        }
        else if (n == 0)
        {
            cout << "read done" << endl;
            break;
        }
        else
        {
            // 设置为非阻塞 底层fd数据没有就绪 recv/read/write/send返回值以出错形式返回
            if (errno == EWOULDBLOCK)
            {
                cout << "0 fd data not ready, try again!" << endl;
                sleep(1);
                // do_other_thing();
            }
            else
            {
                // real 读取错误
                cerr << "read error, n = " << n << "errno code: "
                     << errno << ", error str: " << strerror(errno) << endl;
            }
            // TODO 信号中断IO?
        }
    }

    return 0;
}

在这里插入图片描述

4.同步IO vs 异步IO

同步和异步关注:消息通信机制。
同步IO:发出一个调用,在没有得到结果之前,该调用不返回。一旦调用返回,就得到返回值了;由调用者主动等待这个调用的结果–=–参与了IO中的等待或者拷贝的过程,就是同步IO;
异步IO:调用在发出之后,这个调用就直接返回了,没有返回结果;当一个异步过程调用发出后,调用者不会立刻得到结果;在调用发出后,被调用者通过状态、通知来通知调用者,或者通过回调函数处理这个调用。异步IO不参与IO,只是发起IO,最后拿结果。

跟线程同步/异步无关 回顾线程同步/异步

同步并不是“同时进行”的意思,而是“协同步调”–协调指令运行的先后顺序。

线程资源竞争而导致的数据不一致问题,解决这个问题,==》线程同步机制:让原先异步的操作依次有序地执行。线程同步机制:互斥锁Mutex/读写锁Readers-Writer Lock/信号量Semaphore/条件变量Conditional Variables

锁是一种线程同步机制:

在这里插入图片描述
锁带来的问题:死锁(在考虑并发程度上单锁单临界区)/频繁上锁解锁效率低(尽可能的降低锁的范围)。
在讲多进程多线程的时候, 也提到同步和互斥. 这里的同步通信和进程之间的同步是完全不想干的概
念.
进程/线程同步也是进程/线程之间直接的制约关系。是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系. 尤其是在访问临界资源的时候.

5.其他高级IO

非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射IO(mmap),这些统称为高级IO

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿猿收手吧!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值