Linux 下的IO模型

news2024/11/26 3:24:40

一:四种IO模

1.1:阻塞式IO(最简单,最常用,效率最低)

阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下(及系统默认状态),套接字建立后所处于的模式就是阻塞I/O 模式。
学习的读写函数在调用过程中会发生阻塞。相关函数如下:
•读操作中的read、recv、recvfrom
 读阻塞--》需要读缓冲区中有数据可读,读阻塞解除
•写操作中的write、send
写阻塞--》阻塞情况比较少,主要发生在写入的缓冲区的大小小于要写入的数据量的情况下,写操作不进行任何拷贝工作,将发生阻塞,一旦缓冲区有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。 
注意:sendto没有写阻塞

1.2:非阻塞式IO(可以处理多路IO,需要轮询)

1.2.1:非阻塞式IO的设置
1)通过函数参数设置

Recv函数最后一个参数写为0,为阻塞,写为MSG_DONTWAIT:表示非阻塞

1.2.2:通过fctnl函数设置 
int fcntl(int fd, int cmd, ... /* arg */ );
功能:获取/设置文件描述符属性    状态属性(O_RDONLY  O_NONBLOCK非阻塞)
参数:fd:文件描述符
      cmd:功能选择   
          状态属性: 
                  F_GETFL  :获取文件描述符原来的属性
                  F_SETFL  :设置文件描述符属性
    arg:根据cmd决定是否填充值   int
返回值:
     失败:-1
     成功:F_GETFL - 返回值的文件描述符号属性的值 int
           F_SETFL   0
  int flag;
    flag=fcntl(0,F_GETFL);//获取文件描述符原属性
    flag |= O_NONBLOCK;//添加非阻塞属性
     // flag &= ~O_NONBLOCK;//取消非阻塞属性
    fcntl(0,F_SETFL,flag);//将新属性设置回去  

二:信号驱动IO(异步IO)

特点:异步通知模式,需要底层驱动的支持

//将APP进程号告诉驱动程序
fcntl(fd, F_SETOWN, getpid());

//使能异步通知
int flag;
flag = fcntl(fd,F_GETFL);
flag|= O_ASYNC ;
fcntl(fd,F_SETFL,flag);

signal(SIGIO,handler)
 

例子:鼠标键盘事件:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
int fd;
void handler(int num) // 信号处理
{
    char buf[128]={};
    int ret = read(fd,buf,sizeof(buf)-1);
    buf[ret]='\0';
    printf("buf:%s\n",buf);
    
}
int main(int argc, char const *argv[])
{
    // 打开文件
    fd = open("/dev/input/mouse0", O_RDONLY);
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }
    // 将进程号告诉驱动
    fcntl(fd, F_SETOWN, getpid());
    // 开启异步通知
    int flag;
    flag = fcntl(fd,F_GETFL);
    flag |= O_ASYNC;
    fcntl(fd,F_SETFL,flag);
    // 收到信号,调用函数
    signal(SIGIO,handler);

    while (1)
    {
        printf("welcome to hqyj\n");
        sleep(1);
    }
    return 0;
}

三:IO多路复用

3.1:实现方式

3.1.1:select函数

 int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
   功能:select用于监测是哪个或哪些文件描述符产生事件;
   参数:nfds:    监测的最大文件描述个数
        (这里是个数,使用的时候注意,与文件中最后一次打开的文件
          描述符所对应的值的关系是什么?)
    readfds:  读事件集合; //读(用的多)
     writefds: 写事件集合;  //NULL表示不关心
     exceptfds:异常事件集合;  
     timeout:超时检测 
       如果不做超时检测:传 NULL 
       如果设置了超时检测时间:&tv
  返回值:
         <0 出错
        >0 表示有事件产生;
        ==0 表示超时时间已到;
  struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };
           
 void FD_CLR(int fd, fd_set *set);//将fd从表中清除
 int  FD_ISSET(int fd, fd_set *set);//判断fd是否在表中
 void FD_SET(int fd, fd_set *set);//将fd添加到表中
 void FD_ZERO(fd_set *set);//清空表
3.1.2:实现流程

3.1.3:select并发式服务器的实现
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>

char buf[1024] = {};

int main(int argc, char const *argv[])
{
    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket create err\n");
        return -1;
    }
    // 填充结构体
    struct sockaddr_in addr, caddr;
    addr.sin_family = AF_INET;   // IPV4
    addr.sin_port = htons(8881); // 主机字节序转换为网络字节序
    addr.sin_addr.s_addr = inet_addr("192.168.31.88");
    int len = sizeof(caddr);
    // 绑定
    int ret;
    if (ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind err\n");
        return -1;
    }
    // 监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err\n");
        return -1;
    }
    // 创建表
    fd_set readfds, tempfd;
    FD_ZERO(&readfds);
    // 讲关心的文件描述符添加到表中
    FD_SET(0, &readfds);
    FD_SET(sockfd, &readfds);
    int maxfd = sockfd;
    while (1)
    {
        tempfd = readfds;
        int ret = select(maxfd + 1, &tempfd, NULL, NULL, NULL);

        if (FD_ISSET(0, &tempfd))
        {
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf) - 1] == '\n')
                buf[strlen(buf) - 1] = '\0';
            printf("key:%s\n", buf);
            // 下面的文件描述符发生变化时,需要将数组里存储的内容重新发送
            for (int i = sockfd + 1; i <= maxfd; i++)
            {
                // 判断其有没有在表中
                if (FD_ISSET(i, &readfds))
                {
                    send(i, buf, sizeof(buf), 0);
                }
            }
        }
        if (FD_ISSET(sockfd, &tempfd))
        {
            // 任何客户端都可以 ,返回通信文件描述符
            int acceptfd = accept(sockfd, (struct sockaddr *)&addr, &len);
            if (acceptfd < 0)
            {
                perror("accept err\n");
                return -1;
            }
            // 来电显示
            printf("通信文件描述符:%d\t", acceptfd);
            printf("客户端端口:%d\t客户端ip:%s\n", ntohs(addr.sin_port), inet_ntoa(addr.sin_addr));
            // close(acceptfd);
            // 重新创表
            FD_SET(acceptfd, &readfds);
            // 更新文件描述符
            if (maxfd < acceptfd)
            {
                maxfd = acceptfd;
            }
        }
        for (int i = 4; i <= maxfd; i++)
        {
            if (FD_ISSET(i, &tempfd))
            {
                int rec = recv(i, buf, sizeof(buf), 0);
                if (rec < 0)
                {
                    perror("recv err\n");
                    return -1;
                }
                else if (rec == 0)
                {
                    printf("i==客户端退出:%d\n", i);
                    // break;
                    // 链接关闭之后,关闭其所在的文件描述符,用时
                    close(i);
                    FD_CLR(i, &readfds);
                    if (i == maxfd)
                    {
                        maxfd--;
                    }
                    exit(1);
                }
                else
                {
                    printf("%s\n", buf);
                    memset(buf, 0, sizeof(buf));
                }
            }
        }
    }
    // 关闭文件描述符
    close(sockfd);
    return 0;
}

select实现io多路复用的特点:

  1. 一个进程最多只能监听1024个文件描述符 (千级别)FD_SETFILE
  2. select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗CPU资源);
  3. select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低

 

3.2:poll函数

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
   参数:
   struct pollfd *fds
     关心的文件描述符数组struct pollfd fds[N];
   nfds:个数
   timeout: 超时检测
    毫秒级的:如果填1000,1秒
     如果-1,阻塞

 struct pollfd {
     int   fd;         /* 检测的文件描述符 */
     short events;     /* 检测事件 */
     short revents;    /* 调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员。只需要判断这个成员的值就可以确定是否产生事件 */
 };
 事件:     POLLIN :读事件
           POLLOUT : 写事件
           POLLERR:异常事件

3.2.1:poll函数与select函数的区别

流程

select

poll

1.建立一个文件描述符的表

fd_set线性表

struct pollfd fds[n]结构体数组

2.将关心的文件描述符加到表中

FD_SET(fd,&readfds)

结构体内容填充fds[m].fd= fd

fds[m].events=POLLIN

3. 然后调用一个函数。 select / poll

4. 当这些文件描述符中的一个或多个已准备好进行I/O操作的时候

该函数才返回(阻塞)

select

poll

5.判断

FD_ISSET

revents==POLLIN

6.相关操作

 3.2.2:函数实现
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>

char buf[1024] = {};

int main(int argc, char const *argv[])
{
    int fd = open("/dev/input/mouse0", O_RDONLY);
    if (fd < 0)
    {
        perror("open faild\n");
        return -1;
    }
    // 创建结构体数组
    struct pollfd fds[1024] = {};
    // 添加文件描述符
    fds[0].fd = 0;
    fds[0].events = POLLIN;

    fds[1].fd = fd;
    fds[1].events = POLLIN;

    int last = 1;
    
    while (1)
    {
        poll(fds,last+1, -1);
        for (int i = 0; i <= last; i++)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == 0)
                {
                    // scanf("%s", buf);
                    fgets(buf, sizeof(buf), stdin);
                    if (buf[strlen(buf) - 1] == '\n')
                    {
                        buf[strlen(buf) - 1] = '\0';
                    }

                    printf("。。。。。。:%s\n", buf);
                }
                if (fds[i].fd == fd)
                {
                    read(fd, buf, sizeof(buf));
                    printf(".......:%s\n", buf);
                }
            }
        }
    }
    return 0;
}


3.2.3:特点

 优化文件描述符个数的限制;(根据poll函数第一个参数来定,如果监听的事件为1个,则结构体数组元素个数为1,如果想监听100个,那么这个结构体数组的元素个数就为100,由程序员自己来决定)

poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低

poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

3.3:epoll函数 

特点:

  1. 监听的最大的文件描述符没有个数限制(理论上,取决与你自己的系统)
  2. 异步I/O,Epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符,不需要轮询,效率高
  3. epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2247611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux-Nginx反向代理

文章目录 反向代理负载均衡 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Linux专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月24日10点32分 反向代理 虚拟主机 1 为虚拟主机 3 提供代理服务 vi /etc/nginx/conf.d/vhost.confser…

DataGrip 连接 Redis、TongRDS

连接 Redis 或 TongRDS 有些旧版本 没有 redis 驱动用不了 1&#xff09;选择驱动 2&#xff09;添加连接信息 3&#xff09;测试连接 4&#xff09;保存连接 5&#xff09;使用案例

《数据结构》学习系列——图(中)

系列文章目录 目录 图的遍历深度优先遍历递归算法堆栈算法 广度优先搜索 拓扑排序定义定理算法思想伪代码 关键路径基本概念关键活动有关量数学公式伪代码时间复杂性 图的遍历 从给定连通图的某一顶点出发&#xff0c;沿着一些边访问遍图中所有的顶点&#xff0c;且使每个顶点…

CodeCache使用率告警分析

CodeCache 是 JVM 用于存储已编译的本地代码&#xff08;即 JIT 编译生成的代码&#xff09;的内存区域。如果 CodeCache 使用率持续较高&#xff0c;特别是大于 80%&#xff0c;可能会导致性能问题甚至应用运行异常。以下是详细分析&#xff1a; 一、CodeCache 使用率告警的意…

CSS:怎么把网站都变成灰色

当大家看到全站的内容都变成了灰色&#xff0c;包括按钮、图片等等。这时候我们可能会好奇这是怎么做到的呢&#xff1f; 有人会以为所有的内容都统一换了一个 CSS 样式&#xff0c;图片也全换成灰色的了&#xff0c;按钮等样式也统一换成了灰色样式。但你想想这个成本也太高了…

JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)

下面是是对dom操作的一个综合练习 下面代码是html的基本骨架&#xff08;没有任何的功能&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" c…

GitHub 开源项目 Puter :云端互联操作系统

每天面对着各种云盘和在线应用&#xff0c;我们常常会遇到这样的困扰。 文件分散在不同平台很难统一管理&#xff0c;付费订阅的软件越来越多&#xff0c;更不用说那些烦人的存储空间限制了。 最近在 GitHub 上发现的一个开源项目 Puter 彻底改变了我的在线办公方式。 让人惊…

鸿蒙进阶篇-状态管理之@Provide与@Consume

大家好&#xff0c;这里是鸿蒙开天组&#xff0c;今天我们来学习一下状态管理中的Provide与Consume。 一、概述 嘿&#xff01;大家还记得这张图吗&#xff1f;不记得也要记得哦&#xff0c;因为这张图里的东西&#xff0c;既是高频必考面试题&#xff0c;也是实际开发中&…

Python 使用 OpenCV 将 MP4 转换为 GIF图

以下是使用 Python 和 OpenCV 将 MP4 转换为 GIF 的示例代码&#xff1a; python import cv2 import imageiodef mp4_to_gif(mp4_path, gif_path, fps10, start_timeNone, end_timeNone):"""将MP4视频转换为GIF动图。:param mp4_path: 输入MP4视频的路径。:pa…

五天SpringCloud计划——DAY1之mybatis-plus的使用

一、引言 咱也不知道为啥SpringCloud课程会先教mybatis-plus的使用&#xff0c;但是教都教了&#xff0c;就学了吧&#xff0c;学完之后觉得mybatis-plus中的一些方法还是很好用了&#xff0c;本文作为我学习mybatis-plus的总结提升&#xff0c;希望大家看完之后也可以熟悉myba…

TCP为什么需要三次握手?两次握手或四次握手可以吗?

&#xff08;1&#xff09;三次握手可以保证双方具有接收和发送的能力 第一次握手服务端可以确认客户端的发送能力和服务端的接收能力是正常的&#xff1b;第二次握手客户端可以确认客户端和服务端的收发能力是正常的&#xff0c;但是服务端无法确认客户端的接收能力是正常的&…

Python 获取微博用户信息及作品(完整版)

在当今的社交媒体时代&#xff0c;微博作为一个热门的社交平台&#xff0c;蕴含着海量的用户信息和丰富多样的内容。今天&#xff0c;我将带大家深入了解一段 Python 代码&#xff0c;它能够帮助我们获取微博用户的基本信息以及下载其微博中的相关素材&#xff0c;比如图片等。…

vue面试题——描述一下vue

目录 1 vue是什么2 Vue的核心特性2.1 数据驱动&#xff08;MVVM&#xff09;2.2 组件化2.3 指令系统 3 Vue跟传统开发的区别 1 vue是什么 简单点说&#xff0c;vue就是一个用于创建用户界面的JavaScript框架&#xff0c;同时也是一个创建单页面应用的Web应用框架&#xff0c;Vu…

Large Spatial Model:End-to-end Unposed Images to Semantic 3D 论文解读

目录 一、概述 二、相关工作 1、SfM和可微神经表示 2、端到端的Image-to-3D 三、LSM 1、密集几何预测 2、2D信息特征提取 3、点特征融合 4、可微渲染 5、损失函数 四、实验 一、概述 该论文提出一种大型空间模型&#xff08;Larget Spatial Model,LSM&#xff09;…

A045-基于spring boot的个人博客系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

VMware17安装之VMware Workstation Pro 16升级到17详细教程

VMware17安装之VMware Workstation Pro 16升级到17详细教程 一、下载安装包二、开始安装三、升级成功 当前使用的是VMware Workstation 16 Pro版本&#xff0c;想用最新的17&#xff0c;但是又不想卸载原来的&#xff0c;所以想尝试下看看能不能直接升级&#xff0c;最终升级成…

nature communications论文 解读

题目《Transfer learning with graph neural networks for improved molecular property prediction in the multi-fidelity setting》 这篇文章主要讨论了如何在多保真数据环境&#xff08;multi-fidelity setting&#xff09;下&#xff0c;利用图神经网络&#xff08;GNNs&…

接口上传视频和oss直传视频到阿里云组件

接口视频上传 <template><div class"component-upload-video"><el-uploadclass"avatar-uploader":action"uploadImgUrl":on-progress"uploadVideoProcess":on-success"handleUploadSuccess":limit"lim…

深度学习图像视觉 RKNN Toolkit2 部署 RK3588S边缘端 过程全记录

深度学习图像视觉 RKNN Toolkit2 部署 RK3588S边缘端 过程全记录 认识RKNN Toolkit2 工程文件学习路线&#xff1a; Anaconda Miniconda安装.condarc 文件配置镜像源自定义conda虚拟环境路径创建Conda虚拟环境 本地训练环境本地转换环境安装 RKNN-Toolkit2&#xff1a;添加 lin…

07-SpringCloud-Gateway新一代网关

一、概述 1、Gateway介绍 官网&#xff1a;https://spring.io/projects/spring-cloud-gateway Spring Cloud Gateway组件的核心是一系列的过滤器&#xff0c;通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防…