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

发布于:2025-03-11 ⋅ 阅读:(23) ⋅ 点赞:(0)

🎁个人主页:我们的五年

🔍系列专栏:Linux网络编程

🌷追光的人,终会万丈光芒

🎉欢迎大家点赞👍评论📝收藏⭐文章

Linux网络系列文章 计算机网络(Linux网络编程)_我们的五年的博客-CSDN博客

目录

怎么理解IO?

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

高效IO

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

理解阻塞IO

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

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

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

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

同步通信和异步通信

阻塞和非阻塞

fcntl函数

函数原型

F_DUPFD功能

F_GETFL和F_SETFL功能

结束语


怎么理解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)。

张三在等待鱼上钩的这段时间,一直看着鱼漂,不做其他的事情。

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);
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手册中,也有说明。

#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函数

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

函数原型

#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中,返回的是新的文件描述符,后面可以设置新的文章描述符的最小值。如果这个值被占用,就是生成比这个值更大的了。

#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。

#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操作、线程同步与互斥、文件描述符管理等。理解这些概念对于编写高效、可靠的程序至关重要。希望本文能够为您的学习和实践提供有益的参考。如果您在实际编程中遇到任何问题,建议查阅相关文档或进一步研究具体案例。感谢您的阅读,祝您在编程的道路上不断进步!