I/O多路复用

简介:

I/O 多路复用(I/O 多路转接)使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的系统调用主要有 select poll epoll

select

主旨思想:

1. 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
2. 调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O
操作时,该函数才返回。
        a. 这个函数是阻塞
        b. 函数对文件描述符的检测的操作是由内核完成
3. 在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作
// sizeof(fd_set) = 128字节 1024位
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);
    - 参数:
            // + 1是为了确保 select() 函数能够正确处理所有要监视的文件描述符 
            - nfds : 委托内核检测的最大文件描述符的值 + 1 

            - readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
                    - 一般检测读操作
                    - 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区
                    - 是一个传入传出参数

            - writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性
                    - 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写)

            - exceptfds : 检测发生异常的文件描述符的集合

            - timeout : 设置的超时时间
                struct timeval {
                    long tv_sec; /* seconds */
                    long tv_usec; /* microseconds */
                };
                - NULL : 永久阻塞,直到检测到了文件描述符有变化
                - tv_sec = 0 tv_usec = 0, 不阻塞
                - tv_sec > 0 tv_usec > 0, 阻塞对应的时间,至少一个大于0

    - 返回值 :
            - -1 : 失败
            - > 0(n) : 检测的集合中有n个文件描述符发生了变化

// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);

// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);

// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);

// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);

上述函数中的两个 fd_set  类型的三个参数,我们在调用 select() 函数之前,需要根据需要手动设置要监视的文件描述符所对应的位为 1。然后 select() 函数会检测这些位,没有该功能就置为0,select() 函数的返回值表示剩余多少个文件描述符为 1,即表示有多少个文件描述符还需要被处理。

注意:标志位从3开始,因为0,1,2被系统占用了。

select()工作过程分析:

对上图过程的描述:

一开始,设置要检测的文件描述符为1,然后调用select()检测并将用户态的文件描述符集合拷贝到内核中。然后,A,B发来数据,遍历内核文件描述符集,将没有该功能的标记位置为0。遍历完成后,将内核的文件描述符集合拷贝到用户态中。最后再去遍历一遍用户态的文件描述符集进行通信即可。通过FD_ISSET判断标记为是否为1,假如有客户端断开连接,就调用FD_CLR清除掉标记位。

测试代码:

服务端:

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>

int main() {

    // 创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 监听
    listen(lfd, 8);

    // 创建一个fd_set的集合,存放的是需要检测的文件描述符
    fd_set rdset, tmp;// 交给内核修改的集合是tmp
    FD_ZERO(&rdset);
    FD_SET(lfd, &rdset);
    int maxfd = lfd;

    while(1) {

        tmp = rdset;// 防止下次检测更改提前设置好的集合

        // 调用select系统函数,让内核帮检测哪些文件描述符有数据
        int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
        if(ret == -1) {
            perror("select");
            exit(-1);
        } else if(ret == 0) {// 实际上不会执行该分支,因为NULL是永久阻塞,直到检测到数据,所有只能执行>0的分支
            continue;
        } else if(ret > 0) {
            // 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
            if(FD_ISSET(lfd, &tmp)) {
                // 表示有新的客户端连接进来了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                // 将新的文件描述符加入到集合中
                FD_SET(cfd, &rdset);

                // 更新最大的文件描述符
                maxfd = maxfd > cfd ? maxfd : cfd;
            }

            // 监听的文件描述符肯定排在前面,通信从lfd + 1开始遍历
            for(int i = lfd + 1; i <= maxfd; i++) {
                if(FD_ISSET(i, &tmp)) {
                    // 说明这个文件描述符对应的客户端发来了数据
                    char buf[1024] = {0};
                    int len = read(i, buf, sizeof(buf));
                    if(len == -1) {
                        perror("read");
                        exit(-1);
                    } else if(len == 0) {
                        printf("client closed...\n");
                        close(i);
                        FD_CLR(i, &rdset);// 断开连接需要清空检测集合
                    } else if(len > 0) {
                        printf("read buf = %s\n", buf);
                        write(i, buf, strlen(buf) + 1);// 回写数据
                    }
                }
            }

        }

    }
    close(lfd);
    return 0;
}

客户端:

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

    // 创建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(9999);

    // 连接服务器
    int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }

    int num = 0;
    while(1) {
        char sendBuf[1024] = {0};
        sprintf(sendBuf, "send data %d", num++);
        write(fd, sendBuf, strlen(sendBuf) + 1);

        // 接收
        int len = read(fd, sendBuf, sizeof(sendBuf));
        if(len == -1) {
            perror("read");
            return -1;
        }else if(len > 0) {
            printf("read buf = %s\n", sendBuf);
        } else {
            printf("服务器已经断开连接...\n");
            break;
        }
        // sleep(1);
        usleep(1000);
    }

    close(fd);

    return 0;
}

select()传递检测事件类型:

1. 仅监视可读事件:在这种情况下,主要是监视对方是否有发送数据。当对方发送数据时,文件描述符会变得可读,这样我们就可以从文件描述符中读取数据。这种情况通常用于实现输入操作,即我们希望从文件描述符中读取数据。

2. 仅监视可写事件:在这种情况下,主要是监视对方是否可以接收数据。当对方可以接收数据时,文件描述符会变得可写,这样我们就可以向文件描述符中写入数据。这种情况通常用于实现输出操作,即我们希望向文件描述符中写入数据。

综上所述,仅监视可读事件主要是用于接收数据,而仅监视可写事件主要是用于发送数据。这两种情况下,都可以通过 `select()` 函数来实现对文件描述符的监视,以便在合适的时候进行读取或写入操作。同时监视可读和可写事件意味着我们希望监视对方是否可以接收和发送数据

3. 同时监视可读和可写事件

同时监视可读和可写事件意味着我们对文件描述符的读写状态都感兴趣,但并不意味着对方必须同时满足发送和接收数据。

具体来说,当我们同时监视可读和可写事件时,文件描述符的就绪状态会取决于对方的状态和操作。有以下几种可能的情况:

1. 对方可以发送数据但无法接收数据:此时文件描述符会变得可写,但不可读。

2. 对方可以接收数据但无法发送数据:此时文件描述符会变得可读,但不可写。

3. 对方同时可以发送和接收数据:此时文件描述符会同时变得可读和可写。

因此,同时监视可读和可写事件并不要求对方必须同时满足发送和接收数据的条件,而是表示我们对文件描述符的读写状态都感兴趣,可以根据需要进行相应的操作。监视文件描述符的可读和可写事件主要是为了了解对方的状态,以便根据需要进行相应的操作,而并不意味着服务端必须立即进行读取或写入操作。服务端仍然可以根据自己的逻辑和需求自由地处理连接

 select()多路复用:

 

select()的缺点: 

 

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

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

相关文章

查询排序(2)

Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 1.选择部门 30 中的所有员工 SQL> select *2 from emp3 where deptno 30;EMPNO ENAME JOB MGR HIREDATE SAL COMM …

《动手学深度学习(PyTorch版)》笔记2

Chapter2 Preliminaries 2.1 Automatic Differentiation 让计算机实现微分功能&#xff0c; 有以下四种方式&#xff1a; - 手工计算出微分&#xff0c; 然后编码进代码 - 数值微分 (numerical differentiation) - 符号微分 (symbolic differentiation) - 自动微分&#xff0…

搜维尔科技:【简报】元宇宙数字人赛道,《莉思菱娜》

个性有些古灵精怪时儿安静时而吵闹&#xff0c;虽然以人类寿命来算已经200多岁但在 吸血鬼中还只是个小毛头&#xff0c;从中学开始喜欢打扮偏爱黑白灰色系的服装喜欢时 尚圈&#xff0c;立志想成为美妆或时尚网红不过目前还是学生&#xff0c;脸上的浅色血迹是纹身 贴纸&#…

Javat集合之Lis---(ArrayList和LinkedList)

文章目录 一、 List概述1.1概念1.2list体系结构图1.3 通用方法测试代码 二、List的特点三、遍历方式foreachfor循环迭代器 四、ArrayListArrayList概述概念数据结构 ArrayList的特点 ArrayList去重字符串去重对象去重 五、LinkedListLinkedList概述概念数据结构LinkedList的特点…

一键轻松,免费创造:QuickQR带你体验AI二维码的轻松生成!

当今时代&#xff0c;将信息快速转变为可扫描图案&#xff0c;以简化人们的生活和工作方式&#xff0c;二维码技术展现了它强大的功能。特别是在分享链接、联系信息或进行支付时&#xff0c;二维码已成为现代社会一个不可或缺的部分。本文将探讨生成AI二维码的一种工具&#xf…

线性表--队列

1.什么是队列&#xff1f; 队列是只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先 进先出FIFO(First In First Out) &#xff1b; 入队列&#xff1a;进行插入操作的一端称为队尾&#xff1b; 出队列&#xff1a;进行…

幻兽帕鲁服务器搭建,包教包会

服务器搭建 幻兽帕鲁服务器搭建&#xff0c;包教包会&#xff0c;不会评论区评论手把手帮忙搭建 一、steamCMD安装 1、安装screen&#xff1a; yum install screen -y 2、切换用户&#xff1a; su -ls /bin/bash steam 3、切换至steam用户目录&#xff1a; cd ~ 4、下载ste…

如何在docker容器中安装Elasticsearch中的IK分词器

目录 &#xff08;1&#xff09;准备IK分词器的压缩包 &#xff08;2&#xff09;进入docker容器 &#xff08;3&#xff09;移动ik分词器到指定文件夹 &#xff08;4&#xff09;解压分词器压缩包 &#xff08;5&#xff09;测试IK分词器是否安装成功 &#xff08;1&#…

Redis核心技术与实战【学习笔记】 - 3.Redis服务高可靠

1.数据同步&#xff1a;主从库如何实现数据一致&#xff1f; 前面我们学习了 AOF 和 RDB&#xff0c;如果 Redis 发生了宕机&#xff0c;它们可以分别通过回放日志和重新读入 RDB 文件的方式恢复数据&#xff0c;从而保证尽量较少丢失数据&#xff0c;提升可靠性。 不过&…

vue3 + antd 封装动态表单组件(二)

传送带&#xff1a; vue3 antd 封装动态表单组件&#xff08;一&#xff09; 前置条件&#xff1a; vue版本 v3.3.11 ant-design-vue版本 v4.1.1 vue3 antd 封装动态表单组件&#xff08;一&#xff09;是基础版本&#xff0c;但是并不好用&#xff0c; 因为需要配置很多表…

VR拍摄+制作

1.VR制作需要的图片宽高是2:1&#xff0c;需要360✖️180的图片&#xff0c;拍摄设备主要有两种&#xff1a; 1&#xff09;通过鱼眼相机拍摄&#xff0c;拍摄一组图片&#xff0c;然后通过PTGui来合成(拍摄复杂) 2&#xff09;全景相机&#xff0c;一键拍摄直接就能合成需要的…

Android颜色选择器

Android颜色选择器&#xff0c;弹框提示选择颜色。效果如图。点击或者滑动圆环和底部横向渐变色调整颜色&#xff0c;中间圆圈的颜色就是最终选中的颜色。点击圆圈确认颜色。 使用 //颜色选择Dialogprivate void showColorPickDialog(int position, int colorInt){ColorPickerD…

数据结构(绪论+算法的基本概念)

文章目录 一、绪论1.1、数据结构的基本概念1.2、数据结构三要素1.2.1、逻辑结构1.2.2、数据的运算1.2.3、物理结构&#xff08;存储结构&#xff09;1.2.4、数据类型和抽象数据类型 二、算法的基本概念2.1、算法的特性2.2、“好”算法的特质2.2.1、算法时间复杂度2.2.2、算法空…

【Linux】:线程安全的单例模式

线程安全的单例模式 一.STL和智能指针的安全二.单例模式1.基本概念2.懒汉和饿汉的实现方式 三.常见的其它锁四.读者写者模型 一.STL和智能指针的安全 1.STL中的容器是否是线程安全的? 不是. 原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性…

[SwiftUI]系统弹窗和自定义弹窗

一、系统弹窗 在 SwiftUI 中&#xff0c;.alert 是一个修饰符&#xff0c;用于在某些条件下显示一个警告对话框。Alert 可以配置标题、消息和一系列的按钮。每个按钮可以是默认样式、取消样式&#xff0c;或者是破坏性的样式&#xff0c;它们分别对应不同的用户操作。 1.Aler…

Spring 的存储和获取Bean

文章目录 获取 Spring 上下文对象的方式存储 Bean 对象的方式类注解配置扫描路径&#xff08;必须&#xff09;Controller&#xff08;控制器存储&#xff09;Service&#xff08;服务&#xff09;Repository&#xff08;持久层&#xff09;Component&#xff08;工具&#xff…

【Spring】Spring简介、IOC、DI

目录 Spring简介 Spring Framework五大功能模块 IOC容器 IOC思想 IOC容器在Spring中的实现 基于XML管理bean 配置bean 获取bean 依赖注入之setter注入 依赖注入之构造器注入 特殊值处理 字面量赋值 null值 xml实体 CDATA节 为类类型属性赋值 为数组类型属性赋值 为集合类型属性…

JavaScript 学习笔记(JS进阶 Day1)

「写在前面」 本文为 b 站黑马程序员 pink 老师 JavaScript 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. JavaScript 学习笔记&#xff08;Day1&#xff09; 2. JavaSc…

适用于 Windows 11 的 12 个最佳免费 PDF 编辑器

除了绘图等基本功能外&#xff0c;一些适用于 Windows 11 的免费 PDF 编辑器还具有 AI、OCR 识别和书签等高级功能。 我们的列表包含易于立即下载的 PDF 编辑软件工具。 这些工具不仅可以帮助转换 PDF、编辑、上传、删除、裁剪、分割、提取等。 PDF 是指便携式文档格式&…

单片机学习笔记---独立按键控制LED亮灭

直接进入正题&#xff01; 今天开始我们要学习一个新的模块&#xff1a;独立按键&#xff01; 先说独立按键的内部结构&#xff1a; 它相当于一种电子开关&#xff0c;按下时开关接通&#xff0c;松开时开关断开&#xff0c;实现原理是通过轻触按键内部的金属弹片受力弹动来实…