首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【Linux网络】I/O 世界的技术之旅:探索五种模型与 fcntl 函数的魅力

【Linux网络】I/O 世界的技术之旅:探索五种模型与 fcntl 函数的魅力

作者头像
用户11396661
发布2025-03-11 09:19:51
发布2025-03-11 09:19:51
1670
举报
文章被收录于专栏:C++开发C++开发

怎么理解IO?

IO就是input,output,参照物是计算机本身,是计算机系统内部和外部设备进行交互的过程。

input,output参考的对象计算机本身,input就是相对计算机来说就是向计算机输入,output就是相对计算机来说是向外部设备输出

IO执行的行为差不多就是等待+拷贝。

IO=等待+拷贝(等待是主要矛盾)

等待外部设备就绪,当外部设备准备好了以后,通过CPU的针脚发送中断信号告知操作系统。操作系统转入内核态,进行拷贝工作。

所以IO基本上就是等待+拷贝这个两个动作。

高效IO

上面说的IO=等待+拷贝

在大多数情况下,时间都浪费在等待上面,因为和等待相比,拷贝要花的时间比等待的时间少的多。

高效的IO就是等待时间变少,拷贝的时间的比重增加。

第一种IO模型:阻塞IO模型

理解阻塞IO

阻塞IO相当于当上层进行系统调用,当外部设备没有准备好的时候,就会一直在等待数据准备好,如果没有准备好,就会一直在等待,不会做任何事情。准备好了以后,就把数据从内核拷贝到用户空间。然后向上层返回成功标识符

在钓鱼的例子中: 张三去钓鱼,大部分时间在等待鱼上钩(是外界的准备条件,和张三没有关系,也就是说外部设备多久准备好是和计算机无关的)(Like us,we are not meet again,meeting is something l can't determine)。 张三在等待鱼上钩的这段时间,一直看着鱼漂,不做其他的事情。

代码语言:javascript
复制
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
代码语言:javascript
复制
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

第二种IO模型:非阻塞IO模型

非阻塞IO和阻塞IO最大额区别就是,非阻塞IO在没有数据的时候,直接进行返回,不会等待,并且返回EWOULDBLOCK错误码。

非阻塞IO减少

在man手册中,也有说明。

代码语言:javascript
复制
#include <iostream>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

void SetNoBlock(int fd)
{
    int info=fcntl(fd,F_GETFL);
    if(info<0)
    {
        perror("fcntl error");
        return;
    }
    fcntl(fd,F_SETFL,info|O_NONBLOCK);
    return;
}

int main()
{
    SetNoBlock(0);
    while(1)
    {
        sleep(3);
        char buff[1024]={0};
        ssize_t n=read(0,buff,sizeof(buff)-1);
        if(n<0)
        {
            perror("read error");
            
            continue;
        }

        buff[n]=0;
        cout<<buff<<endl;

    }
    return 0;
}

在钓鱼例子中: 李四在把鱼竿抛出去以后,就去干了一会其他的事情,然后就看一下鱼漂有没有动,看一下有没有鱼,然后发现没有鱼,又去干其他的事情。过了一会又来看有没有鱼,又没有鱼,又去干其他的事情。一直这么循环(轮询),到有鱼的时候,就起竿钓上鱼。


第三种IO模型:信号驱动IO模型

内核将数据准备好的时候, 使用 SIGIO 信号通知应用程序进行 IO操作。

和非阻塞IO的区别就是,不会去循环检测(不会去做无用工),而是真正有数据的时候,会发SIGIO信号,这样再次IO的时候,就会成功了。

在钓鱼例子中: 王五不再和李四一样,时不时就去看一下鱼漂有没有动,而是在鱼漂上直接挂了一个铃铛🛎🛎🛎,当鱼来了的时候,数据有的时候,就去起竿。


第四种IO模型:IO多路转接模型

select,poll,epoll都是多路转接,后面就有文章进行详细讲解。

它通过允许单个线程同时监控多个文件描述符的I/O状态,避免了为每个I/O操作创建单独线程所带来的资源消耗和上下文切换开销。

select:适用于小规模并发场景,但效率较低,因为它需要遍历所有文件描述符。 poll:类似于select,但没有文件描述符数量的限制,适用于更大的并发量。 epoll(仅限Linux):通过事件驱动机制高效地处理大量文件描述符,性能优于select和poll。 kqueue(仅限BSD系统):类似于epoll,支持高效的事件通知机制。

看下面的图好像和阻塞IO没有区别,但是重点就是多路转接可以一次等待几个文件,等待几个文件的状态(一切皆文件嘛)。

在钓鱼例子中: 赵六拿了很多的鱼竿, 一个人就管理多个鱼竿,调起鱼的概率更大,也就是拷贝的时间的比重增加,就是高效的IO。


第五种IO模型:异步IO模型

上面的四种都是同步IO。参与了等待和拷贝的任意一个动作就是同步IO。

他们参与了等待或者拷贝这两个动作的其中一个或者两个。在阻塞IO中,参与了等待和拷贝,在非阻塞IO中,最起码参与了拷贝,所以也是同步IO。多路转接管理多个文件,也可以设置为非阻塞多路转接。

异步IO就直接返回,等待+拷贝都让系统来做。弄好以后就通过事件,信号等告知已经处理好了。

在钓鱼例子中: 田七钓鱼,但是不自己调,不自己等待,也不自己起竿(拷贝过程),全交给他的朋友。当鱼钓好了,就打电话通知田七,这就是异步IO。


同步通信和异步通信

同步通信就是要参与等待和拷贝的过程,参与了一个就是同步。

异步通信是也要进行拷贝和等待,但是两个过程都交给系统去做了。调用者不需要关心这两个过程就可以了。等系统处理好,就可以通知调用者。

另外,线程同步和线程互斥不是这样的概念.

线程同步和线程互斥是为了保护临界资源,保证资源的一致性。


阻塞和非阻塞

阻塞调用是指,在调用成功之前,线程会被挂起,在得到结果以后才会被返回。

而非阻塞直接进行返回,该调用不会阻塞线程。


fcntl函数

一个文件描述符,默认是阻塞的。

函数原型

代码语言:javascript
复制
#include <unistd.h>
#include <fcntl.h>

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

cmd是命令,是要操作的类型。主要的操作类型有:

1.获取,设置文件状态信息:cmd=F_GETFL,F_SETFL。

2.复制现有的描述符,cmd=F_DUPFD。

3.获取,设置文件描述符标识,cmd=F_GETFD,F_SETFD。

4.获取,设置异步IO所有权,cmd=F_GETOWN,F_SETOWN。

5.获取、设置记录锁,cmd=F_GETLK,F_SETLK,F_SETLKW。

cmd

功能

F_GETFL,F_SETFL

获取,设置文件状态信息

F_DUPFD

复制现有的描述符

F_GETFD,F_SETFD

获取,设置文件描述符标识

F_GETOWN,F_SETOWN

获取,设置异步IO所有权

F_GETLK,F_SETLK,F_SETLKW

获取、设置记录锁

根据cmd的不同,可能需要传递额外的参数,比如set类的,就要加入文件状态信息。比如O_NONBLOCK,就是设置非阻塞信息。

F_DUPFD功能

在F_DUPFD中,返回的是新的文件描述符,后面可以设置新的文章描述符的最小值。如果这个值被占用,就是生成比这个值更大的了。

代码语言:javascript
复制
#include <iostream>
#include <unistd.h>
#include <fcntl.h>


using namespace std;

int main() {
    int fd = open("example.txt", O_RDONLY|O_CREAT);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    int new_fd = fcntl(fd, F_DUPFD, 20); // 复制文件描述符,新描述符值大于等于10
    if (new_fd == -1) {
        perror("fcntl");
        close(fd);
        return 1;
    }


    printf("Original fd: %d, New fd: %d\n", fd, new_fd);
    cout<<"fd:"<<fcntl(fd,F_GETFL)<<",New fd:"<<fcntl(new_fd,F_GETFL)<<endl;
    close(new_fd);
    close(fd);
    return 0;
}

F_GETFL和F_SETFL功能

使用 F_GETFLF_SETFL 命令可以获取和设置文件状态标志。文件状态标志包括 O_APPENDO_NONBLOCK

非阻塞:O_NONBLOCK。

代码语言:javascript
复制
#include <iostream>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

void SetNoBlock(int fd)
{
    int info=fcntl(fd,F_GETFL);
    if(info<0)
    {
        perror("fcntl error");
        return;
    }
    fcntl(fd,F_SETFL,info|O_NONBLOCK);
    return;
}

int main()
{
    SetNoBlock(0);
    while(1)
    {
        sleep(3);
        char buff[1024]={0};
        ssize_t n=read(0,buff,sizeof(buff)-1);
        if(n<0)
        {
            perror("read error");
            
            continue;
        }

        buff[n]=0;
        cout<<buff<<endl;

    }
    return 0;
}

结束语

至此,我们已经深入探讨了计算机科学中的诸多重要概念,包括I/O操作、线程同步与互斥、文件描述符管理等。理解这些概念对于编写高效、可靠的程序至关重要。希望本文能够为您的学习和实践提供有益的参考。如果您在实际编程中遇到任何问题,建议查阅相关文档或进一步研究具体案例。感谢您的阅读,祝您在编程的道路上不断进步!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 怎么理解IO?
    • IO=等待+拷贝(等待是主要矛盾)
    • 高效IO
  • 第一种IO模型:阻塞IO模型
    • 理解阻塞IO
  • 第二种IO模型:非阻塞IO模型
  • 第三种IO模型:信号驱动IO模型
  • 第四种IO模型:IO多路转接模型
  • 第五种IO模型:异步IO模型
  • 同步通信和异步通信
  • 阻塞和非阻塞
  • fcntl函数
    • 函数原型
    • F_DUPFD功能
    • F_GETFL和F_SETFL功能
  • 结束语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档