【Linux】I/O复用

news2024/11/23 13:04:47

 概述

         I/O复用使得程序能同时监听多个文件描述符,看文件上是否有用户感兴趣的事件发生,提高程序性能。

使用I/O复用技术的情况:

客户端同时处理多个套接字

客户端同时处理用户输入和网络连接

同时监听多个端口

同时处理TCP和UDP请求

TCP服务器同时处理监听连接套接字

I/O复用的方法有三种:select、poll、epoll; 


select系统调用

select可以监听:用户感兴趣的文件描述符上的可读、可写、异常等。

需要包含头文件:#include <sys/select.h>

函数声明:

maxfd:被监听的文件描述符总数,它通常被设置为 select 监听的所有文件描述符中的最大值+1一共1024个位,只用检查maxfd+1(下标从0开始)位;

readfds、 writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件描述符集合

timeout:超时时间(毫秒)

int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct ti
meval *timeout);

 

文件描述符按位插入每一轮要重新填充描述符,文件描述符就绪了位为1,否则为0,需要查看位为1的

设置文件描述符集合的函数:

FD_ZERO(&fdset)清空

FD_SET(fd,&fdset)fd添加到集合置1.

FD_ISSET(fd,&fdset)//测试fdset的位fd是否被设置

用select达成I/O复用的服务器端

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/select.h>
#include<string.h>
#include<time.h>
//0:标准输入的文件描述符
#define STDIN 0 
int main()
{
    int fd = STDIN;//fd代表键盘输入的文件
    fd_set fdset;//创建一个文件描述符集合

    while(1)
    {
        FD_ZERO(&fdset);//清空成0000 0000
        FD_SET(fd,&fdset);//将描述符fd添加到描述符集合

        struct timeval tv = {5,0};//设置超时时间

        int n=select(fd+1,&fdset,NULL,NULL,&tv);//select方法I/O复用
        if(n==-1)//里面没有描述符
        {
            printf("select err\n");
        }
        else if(n==0)//超时了
        {
            printf("timeout\n");
        }
        else//添加了文件描述符
        {
            if(FD_ISSET(fd,&fdset))//判断是否有关注的fd事件
            {//有事件
                char buff[128]={0};
                read(fd,buff,127);//read127个写入的内容到buff里
                printf("read:%s\n",buff);
            }
        }
        
    }
}

poll系统调用

poll和select的本质是一样的:在一定时间内轮询一定数量的文件描述符,测试其是否就位。

但是它监测的文件描述符的个数比select多。

头文件:#include<poll.h>

poll(文件描述符上的事件,被监听事件集合fds的大小,超时时间)

int poll(struct pollfd* fds,nfds_t nfds,int timeout);

struct pollfd
{
    int fd;//文件描述符
    short events;//注册的事件,用户添加的感兴趣的事件
    short revents;//实际发生的事件,由内核填充
}

事件有以下几种(比select多) 

 

用poll达成I/O复用的服务器端

由于poll里面需要传一个结构体fds,需要将结构体初始化:

#define  MAXFD    10
void fds_init(struct pollfd fds[])
{
    for( int i = 0; i < MAXFD; i++)
    {
        fds[i].fd = -1;//文件描述符初始化成-1
        fds[i].events = 0;//用户想检测的事件
        fds[i].revents = 0;//内核返回的事件
    }
}

在文件描述符集合中添加、删除描述符:

select有FD_SET可以直接用,但是poll要自己写

//添加
void fds_add(struct pollfd fds[],int fd)
{
    for( int i = 0; i < MAXFD; i++)
    {
        if ( fds[i].fd == -1)//没有被处理的文件描述符
        {
            fds[i].fd = fd;
            fds[i].events = POLLIN;//POLLIN输入
            fds[i].revents = 0;
            break;
        }
    }
}
//删除
void fds_del(struct pollfd fds[],int fd)
{
    for( int i = 0; i < MAXFD; i++)
    {
        if ( fds[i].fd == fd)
        {
            fds[i].fd = -1;//置回初始值
            fds[i].events = 0;//无感兴趣事件
            fds[i].revents = 0;
            break;
        }
    }
}

处理文件描述符:

收到监听套接字文件描述符sockfd:listen()产生的sockfd 用 accept()处理

收到连接套接字文件描述符c:accept()产生的c 用recv()处理

在接收到套接字并其事件为用户感兴趣的时需要判断其为监听套接字还是连接套接字,分别调用。

//处理监听套接字
void accept_client(struct pollfd fds[], int sockfd)
{
    struct sockaddr_in caddr;
    int len = sizeof(caddr);
    int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//accept处理已完成监听队列中的内容
    if ( c < 0 )//接收失败
    {
        return;
    } 

    fds_add(fds,c);//将该链接套接字文件描述符添加到描述符集合
    printf("c=%d\n",c);//输出链接套接字文件描述符的值
}
//处理连接套接字
void recv_data(struct pollfd fds[], int c)
{
    char buff[128] = {0};
    int n = recv(c,buff,127,0);//把c里面的127个数据读到buff里
    if ( n <= 0 )//没有接受到数据
    {
        close(c);//关闭套接字
        fds_del(fds,c);//将该文件描述符从fds中删除
        printf("client close\n");
        return;
    }

    printf("buff(%d)=%s\n",c,buff);//接收到数据,输出
    send(c,"ok",2,0);//返回ok
}

创建套接字

int socket_init()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if ( sockfd == -1)
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if ( res == -1)
    {
        return -1;
    }

    res = listen(sockfd,5);
    if ( res == -1)
    {
        return -1;
    }

    return sockfd;
}

主函数

int main()
{
    //创建套接字
    int sockfd = socket_init();
    if ( sockfd == -1)
    {
        printf("socket err\n");
        exit(1);
    }

    //创建poll文件套接字集合fds
    struct pollfd fds[MAXFD];
    fds_init(fds);//初始化
    fds_add(fds,sockfd);//将套接字添加到套接字集合

    while( 1 )
    {
        int n = poll(fds,MAXFD,5000);//文件描述符,最多的个数,超时时间
        if ( n == -1)
        {
            printf("poll err\n");
        }
        else if ( n == 0 )
        {
            printf("time out\n");
        }
        else
        {
            for(int i = 0;i < MAXFD; i++ )//把所有文件描述符遍历一遍-轮询找发生事件的
            {
                if ( fds[i].revents & POLLIN )//是POLLIN事件
                {
                    if ( fds[i].fd == sockfd )//是监听套接字
                    {
                        accept_client(fds,fds[i].fd);
                    }
                    else//是连接套接字
                    {
                        recv_data(fds,fds[i].fd);
                    }
                }
            }
        }
    }
}

epoll系列系统调用

        epoll是Linux特有的I/O复用函数。它将用户关心的文件描述符放到一个内核事件表中。

内核事件表的底层是

头文件:#include<sys/epoll.h>

文件描述符创建:

int epoll_create(int size);

size提示内核,这个事件表要多大;

操作内核事件表

int epoll_ctl(int epfd,int op,int fd,struct epoll_event*event);
//          (内核事件表, 操作, 文件描述符,类型)
struct epoll_event
{
    __uint32_t events;//epoll事件
    epoll_data_t data;//文件描述符
}

EPOLL_CTL_ADD往事件表中注册fd上的事件

EPOLL_CTL_MOD修改fd上的注册事件

EPOLL_CTL_DEL删除fd上的注册事件

epoll的添加、删除: 

void epoll_add(int epfd, int fd)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN;

    if ( epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
    {
        printf("epoll add err\n");
    }
}

void epoll_del(int epfd, int fd)
{
    if( epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1)
    {
        printf("epoll del err\n");
    }
}

其他与poll的函数几乎一样(在poll前面加个e)

epoll_wait()

        epoll的主要接口,在一定时间内等待一组文件描述符上的事件

#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event*events,int maxevevts,int timeout);

当epoll_wait检测到事件,将所有就绪的事件从内核事件表epfd中复制到events指向的数组中,events数组用于输出检测到的就绪事件,不用轮询所有文件描述符,只用遍历这个包含就绪了的文件描述符的表,提升效率。

LT和ET模式

        epoll的两种模式:LT(平电触发)模式(默认),ET(边沿触发)模式。

LT模式当epoll_wait检测到事件并通知程序后,程序可以不立即处理事件,只要有事件没处理,epoll_wait就会一直通知程序。

ET模式当epoll_wait检测到事件并通知程序后,程序必须立即处理事件,它只会在检测到时通知一次,后续不会再通知这个事件。(降低重复触发次数,高效)

event.events |= RPOLLET;//fd启用ET模式

立即处理的方法

需要一次处理将所有数据读完:循环读数据,当:recv的返回值<0且

errno==EWOULDBLOCK||errno==EAGAIN表示数据全部读取完。

用errno需要 #include<errno.h>

三种复用的区别

 

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

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

相关文章

【JavaEE】文件操作(InputStream、OutputStream)

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 在Java中总会用到文件操作&#xff0c;比如从盘符读写文件&#xff0c;按字节和字符进行数据读写&#xff0c;那么你真的认识什么是文件路径吗&#xff1f;平时谈到的“文件”&#xff0c;…

mybatis实现CRUD详解(使用mapper映射文件实现增删改查)

mybatis实现CRUD详解(使用mapper映射文件实现增删改查) 创建maven项目&#xff1a;项目结构如图所示 准备数据库表&#xff1a; 准备pom.xml所需的依赖&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.…

快速弄懂RPC

快速弄懂RPC 常见的远程通信方式远程调用RPC协议RPC的运用场景和优势 常见的远程通信方式 基于REST架构的HTTP协议以及基于RPC协议的RPC框架。 远程调用 是指跨进程的功能调用。 跨进程可以理解为一个计算机节点的多个进程或者多个计算机节点的多个进程。 RPC协议 远程过…

C++string类

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了C中的string类以及类内的一些常用函数 Cstring类 文章…

k8s_3节点集群部署

背景 近期想在自己电脑上部署一套3节点K8s 作为自己平时的学习测试环境。 本来想看一下有没有比较便捷的部署方式如&#xff1a; rancherdesktop: https://docs.rancherdesktop.io/zh/next/getting-started/installation/ sealos: https://www.sealos.io/docs/getting-started…

【超算/先进计算学习】日报3

目录 今日已完成任务列表遇到的问题及解决方案任务完成详细笔记作业管理系统 slurm作业管理系统及常用用户命令节点状态查询/作业提交命令作业队列查询/作业取消命令任务练习 调试器 GDB实时调试core 文件调试 对自己的表现是否满意简述下次计划其他反馈 今日已完成任务列表 4-…

Spring的体系结构

Spring的体系结构 一、Spring简介1. Spring介绍1.1 为什么要学1.2 学什么1.3 怎么学 2. 初识Spring2.1 Spring家族2.2 Spring发展史 3. Spring体系结构3.1 Spring Framework系统架构图3.2 Spring Framework课程学习路线 4. Spring核心概念4.1 目前我们代码存在的问题4.2 核心概…

『Linux从入门到精通』第 ⑫ 期 -深入了解冯诺依曼体系结构与操作系统(Operator System)

文章目录 &#x1f490;专栏导读&#x1f490;文章导读&#x1f337;计算机之父——冯诺依曼&#x1f337;冯诺依曼体系结构&#x1f33a;木桶效应 &#x1f337;操作系统(Operator System)&#x1f33a;如何理解操作系统&#x1f33a;系统调用和库函数概念 &#x1f490;专栏导…

论文结构商讨

论文查重 pass软件 提出问题 1、由于选取的算法太多了&#xff0c;不知道是简单介绍&#xff0c;还是深入介绍 希望找到一个具体的模板 2、数据库要是用一个就在前面介绍&#xff0c;用不一样的就在每章里面分别介绍 4、数据集介绍放在那个具体位置&#xff1f; 如果可以画出…

蓝精灵事件来袭: 深受喜爱的蓝色角色来 Web3 啦!

起源于漫画并成为国际知名的卡通和电影明星的蓝色x小家伙正在进入 NFT 舞台。 作者&#xff1a;Coindesk——Toby Bochan 因漫画、卡通片和电影而闻名的热门动画系列“蓝精灵”正在展开新的冒险&#xff0c;由蓝精灵协会推出一系列新的非同质化通证&#xff08;NFT&#xff09;…

汽车ECU的内部构成与功能模块

摘要&#xff1a; ECU作为与外部通信的接口&#xff0c;为了通过微控制器运算实现符合实际状态的最佳控制&#xff0c;需要检测外部状态的输入手段及实际控制驱动的输出手段。 1. 常规功能模块 ① 电源&#xff1a;向ECU内的各模块提供稳定的电压&#xff08;5V、3V等&#xf…

sequelize + Nodejs + MySQL 的简单用法

How to Use Sequelize ORM in NodeJS - Tutorial 1 Sequlize 简介 Sequelize 是最流行的可以与 Nodejs 一起使用的一种关系数据库 ORM (Object-relational mapping 对象关系映射)&#xff0c;Mongoose 是 MongoDB 的 ORM. Sequelize 的作用&#xff0c;简单地说&#xff0c;就…

Netty编解码器,Netty自定义编解码器解决粘包拆包问题,Netty编解码器的执行过程详解

文章目录 一、编解码器概述1、编解码器概述2、编码器类关系图3、解码器类关系图 二、以编解码器为例理解入站出站1、Server端2、Client端3、编解码器3、执行查看结果4、注意事项 三、Netty其他内置编解码器1、ReplayingDecoder2、其他编码器3、内置编解码器处理粘包拆包问题 四…

[LeetCode复盘] LCCUP‘23春季赛 20230422

[LeetCode复盘] LCCUP23春季赛 20230422 一、总结二、 1. 补给马车1. 题目描述2. 思路分析3. 代码实现 三、2. 探险营地1. 题目描述2. 思路分析3. 代码实现 四、 3. 最强祝福力场1. 题目描述2. 思路分析3. 代码实现 五、 4. 传送卷轴1. 题目描述2. 思路分析3. 代码实现 六、 5…

REDIS03_AOF概述、工作流程、写回策略、正常异常流程、重写机制、配置文件详解

文章目录 ①. AOF - 概述作用②. AOF - 工作流程③. 缓冲区 - 写回策略④. 配置文件说明(6 VS 7)⑤. 正常、异常恢复⑥. AOF - 优劣势⑦. AOF - 重写机制⑧. AOF优化配置项详解⑨. RBD和AOF共存模式 ①. AOF - 概述作用 ①. 官网介绍 ②. 以日志的形式来记录每个写操作,将Red…

解决RabbitMQ的The channelMax limit is reached. Try later.

The channelMax limit is reached. Try later.顾名思义就是channel达到数量限制 查看源码得出 大概意思就是&#xff1a; 默认最大通道数&#xff1b;2047&#xff0c;因为它在服务器端是第2048个&#xff0c;每个连接用于协商和错误通信。 也可以在rabbitmq的管控台看出 总结…

单页面与路由

目录 &#xff08;一&#xff09;什么是SPA应用&#xff1f; &#xff08;二&#xff09;路由 &#xff08;1&#xff09;什么是路由&#xff1f; &#xff08;2&#xff09;路由的分类 &#xff08;3&#xff09;路由的安装和使用 &#xff08;三&#xff09;、路由的使…

微服务 - 搭建Consul集群服务,Consul配置中心

传统配置文件的弊端 静态化配置&#xff0c;例如env文件配置文件无法区分环境配置文件过于分散历史版本无法查看 配置中心如何解决的呢?配置中心的思路是把项目中的配置参数全部放在一个集中的地方来管理&#xff0c;并提供一套标准的接口&#xff0c;当各个服务需要获取配置…

d2l 使用attention的seq2seq

这一章节与前面写好的function关联太大&#xff0c;建议看书P291. 这章节主要讲述了添加attention的seq2seq,且只在decoder里面添加&#xff0c;所以全文都在讲这个decoter 目录 1.训练 2.预测 1.训练 #save class AttentionDecoder(d2l.Decoder):"""带有注…

HTTP与HTTPS相关介绍(详细)

HTTP与HTTPS相关介绍 HTTP与HTTPS简述HTTPS和HTTP的区别主要如下HTTPS的工作原理前言工作步骤总结 HTTPS的缺点SSL与TLSSSL&#xff1a;TLS&#xff1a;TLS和SSL的关系 对称加密与非对称加密对称加密非对称加密 HTTP与HTTPS简述 超文本传输协议&#xff08;Hyper Text [Transf…