Linux 下的IO模型

一:四种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不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度可观察性:它是什么,为什么我们需要它?

随着混合云基础设施成为现代企业的支柱&#xff0c;威胁形势的发展速度比以往任何时候都快。 越来越老练的攻击者、隐藏的漏洞和复杂的监管要求使 IT 和安全团队更难确保在这个复杂的环境中提供强大的保护。 2024 年 Gigamon 混合云安全调查显示&#xff0c;安全和 IT 领导者…

【附代码原理】偏正态分布的数据处理方法

文章目录 相关教程基本信息数学定义参数的影响Python 实现安装 scipy生成和绘制偏正态分布的数据 解释应用 右偏&#xff08;即长尾在右侧&#xff09;的正态分布1. 对数变换 (Log Transformation)2. 平方根变换 (Square Root Transformation)3. Box-Cox 变换注意事项 左偏&…

【STM32】MPU6050简介

文章目录 MPU6050简介MPU6050关键块带有16位ADC和信号调理的三轴MEMS陀螺仪具有16位ADC和信号调理的三轴MEMS加速度计I2C串行通信接口 MPU6050对应的数据手册&#xff1a;MPU6050 陀螺仪加速度计 链接: https://pan.baidu.com/s/13nwEhGvsfxx0euR2hMHsyw?pwdv2i6 提取码: v2i6…

一篇快速上手 Axios,一个基于 Promise 的网络请求库(涉及原理实现)

Axios 1. 介绍1.1 什么是 Axios&#xff1f;1.2 axios 和 ajax 的区别 2. 安装使用3. Axios 基本使用3.1 Axios 发送请求3.2 其他方式发送请求3.3 响应结构3.4 Request Config3.5 默认配置3.6 创建实例对象发送请求 3.7 拦截器3.8 取消请求 4. 模拟 Axios4.1 axios 对象创建过程…

趋势洞察|AI 能否带动裸金属 K8s 强势崛起?

随着容器技术的不断成熟&#xff0c;不少企业在开展私有化容器平台建设时&#xff0c;首要考虑的问题就是容器的部署环境——是采用虚拟机还是物理机运行容器&#xff1f;在往期“虚拟化 vs. 裸金属*”系列文章中&#xff0c;我们分别对比了容器部署在虚拟化平台和物理机上的架…

Unity-添加世界坐标系辅助线

如果你想在场景中更直观地显示世界坐标系&#xff0c;可以通过编写一个简单的脚本来实现。下面是一个基本的示例脚本&#xff0c;它会在场景中绘制出世界坐标系的三个轴&#xff1a; using UnityEngine;public class WorldAxesIndicator : MonoBehaviour {public float length…

决策树分类算法【sklearn/决策树分裂指标/鸢尾花分类实战】

决策树分类算法 1. 什么是决策树&#xff1f;2. DecisionTreeClassifier的使用&#xff08;sklearn&#xff09;2.1 算例介绍2.2 构建决策树并实现可视化 3. 决策树分裂指标3.1 信息熵&#xff08;ID3&#xff09;3.2 信息增益3.3 基尼指数&#xff08;CART&#xff09; 4. 代码…

5分钟轻松搭建Immich图片管理软件并实现公网远程传输照片

文章目录 前言1.关于Immich2.安装Docker3.本地部署Immich4.Immich体验5.安装cpolar内网穿透6.创建远程链接公网地址7.使用固定公网地址远程访问 前言 本篇文章介绍如何在本地搭建lmmich图片管理软件&#xff0c;并结合cpolar内网穿透实现公网远程访问到局域网内的lmmich&#…

React和Next.js的相关内容

React–前端框架 React 是一个用于构建用户界面的 JAVASCRIPT 库。 React 主要用于构建 UI&#xff0c;很多人认为 React 是 MVC 中的 V&#xff08;视图&#xff09;。 React 起源于 Facebook 的内部项目&#xff0c;用来架设 Instagram 的网站&#xff0c;并于 2013 年 5 …

【LeetCode热题100】队列+宽搜

这篇博客是关于队列宽搜的几道题&#xff0c;主要包括N叉树的层序遍历、二叉树的锯齿形层序遍历、二叉树最大宽度、在每个数行中找最大值。 class Solution { public:vector<vector<int>> levelOrder(Node* root) {vector<vector<int>> ret;if(!root) …

丹摩征文活动|基于丹摩算力的可图(Kolors)的部署与使用

Kolors是一个以生成图像为目标的人工智能系统&#xff0c;可能采用了类似于OpenAI的DALLE、MidJourney等文本生成图像的技术。通过自然语言处理&#xff08;NLP&#xff09;和计算机视觉&#xff08;CV&#xff09;相结合&#xff0c;Kolors能够根据用户提供的文本描述生成符合…

【PTA】【数据库】【SQL命令】编程题1

数据库SQL命令测试题1 10-1 显示教工编号以02开头的教师信息 作者 冰冰 单位 广东东软学院 显示教工编号以02开头的教师信息 提示&#xff1a;请使用SELECT语句作答。 表结构: CREATE TABLE teacher ( TId CHAR(5) NOT NULL, -- 教师工号&#xff0c;主键 DId CHAR(2) …

Dockerhub镜像加速

一、背景 dockerhub由于被封锁和站点处于国外的原因&#xff0c;docker pull拉取镜像非常慢&#xff0c;有时候直接都无法拉取。严重妨碍了我们的学习进度以及日常使用。 总结了一些proxy代理的镜像站点&#xff0c;配置之后速度会有明显提升&#xff0c;大家可以参考使用。 二…

Linux: C语言解析域名

在上一篇博客 Linux: C语言发起 DNS 查询报文 中&#xff0c;自己构造 DNS 查询报文&#xff0c;发出去&#xff0c;接收响应&#xff0c;以二进制形式把响应的数据写入文件并进行分析。文章的最后留下一个悬念&#xff0c;就是写代码解析 DNS answer section 部分。本文来完成…

Tri Mode Ethernet MAC IP核详解

本文对 Vivado 的三速 MAC IP 核&#xff08;Tri Mode Ethernet MAC&#xff0c;TEMAC&#xff09;进行介绍。 在自行实现三速以太网 MAC 控制器时&#xff0c;GMII/RGMII 接口可以通过 IDDR、ODDR 原语实现&#xff0c;然而实际使用中自己实现的模块性能不是很稳定&#xff08…

CENTOS7 升级gcc版本

升级gcc版本 CentOS下升级gcc版本有两个途径&#xff0c;一个是添加其他源进行自动升级&#xff0c;一个是手动编译升级&#xff0c;这里先顺便讲下自动升级的两个办法&#xff1a; a. 添加Fedora源 在 /etc/yum.repos.d 目录中添加文件 FedoraRepo.repo &#xff0c;并输入…

VMware虚拟机(Ubuntu或centOS)共享宿主机网络资源

VMware虚拟机(Ubuntu或centOS)共享宿主机网络资源 由于需要在 Linux 环境下进行一些测试工作&#xff0c;于是决定使用 VMware 虚拟化软件来安装 Ubuntu 24.04 .1操作系统。考虑到测试过程中需要访问 Github &#xff0c;要使用Docker拉去镜像等外部网络资源&#xff0c;因此产…

学习日记_20241123_聚类方法(高斯混合模型)续

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

15.C++STL 2(string类的使用,6000字详解)

⭐本篇重点&#xff1a;string类的使用 ⭐本篇代码&#xff1a;c学习/05.string类的学习 橘子真甜/c-learning-of-yzc - 码云 - 开源中国 (gitee.com) 目录 一. C/C字符与string类 二. STL中的string类的使用 2.1 string类常见的构造与赋值 2.2 string对象的数据容量操作 …

神经网络(系统性学习一):入门篇——简介、发展历程、应用领域、基本概念、超参数调优、网络类型分类

相关文章&#xff1a; 神经网络中常用的激活函数 神经网络简介 神经网络&#xff08;Neural Networks&#xff09;是受生物神经系统启发而设计的数学模型&#xff0c;用于模拟人类大脑处理信息的方式。它由大量的节点&#xff08;或称为“神经元”&#xff09;组成&#xff0…