Linux多路IO复用:epoll

news2024/12/23 5:30:24

1. epoll

        epoll是为克服select、poll每次监听都需要在用户、内核空间反复拷贝,以及需要用户程序自己遍历发现有变化的文件描述符的缺点的多路IO复用技术。

epoll原理

创建内核空间的红黑树;

将需要监听的文件描述符上树;

内核监听红黑树上文件描述符的变化;

返回有变化的文件描述符。

epoll优点

        ① 无需在用户、内核空间反复拷贝数据;

        ② 内核返回发生变化的文件描述符,无需用户遍历所有文件描述符。


2. epoll API

(1)epoll_create 创建红黑树

#include<sys/epoll.h>

int epoll_create(int size);
/*
功能:
    创建内核中的epoll红黑树;
参数:
    size:监听的文件描述符上限,kernel 2.6版本后写1即可,会自动扩展。
返回值:
    成功:返回红黑树的句柄(相当于操作树的入口)。
    失败:-1,会设置errno。
*/

(2)epoll_ctl 上树、下树、修改节点的监听事件

#include<sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
/*
功能:
    上树、下树、修改节点。
参数:
    epfd:红黑树的句柄,epoll_create的返回值。
    op:对文件描述符fd的操作
        EPOLL_CTL_ADD:将文件描述符fd上树
        EPOLL_CTL_MOD:修改文件描述符fd的事件
        EPOLL_CTL_DEL:将文件描述符fd下树
    fd:op要操作的文件描述符
    event:用于对特定的文件描述符事件进行设置。
返回值:
    成功:
    失败:
*/

struct epoll_event {
    uint32_t events;    // 监听的事件
    epoll_data_t data;  // 需要监听的文件描述符(共用体中的fd)
}
/*
参数 events:
    EPOLLIN:读事件
    EPOLLOUT:写事件
*/

typedef union epoll_data {
    void* ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

(2)epoll_wait 监听

#include<sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
/*
功能:
    监听红黑树上文件描述符的变化;
参数:
    epfd:红黑树的句柄(epoll_create的返回值);
    events:接收发送变化的文件描述符的数组地址
    maxevents:数组元素的个数
    timeout:
        > 0:监听超时时间(多久监听一次);
        0:无文件描述符变化则立即返回;
        -1:阻塞监听到有文件描述符变化才返回
返回值:
    成功:0表示没有文件描述符发生变化;否则返回发生变化的文件描述符的个数
    失败:-1,调用错误,会设置errno
*/

3. epoll使用示例

(1)监听管道

        子进程每3s向管道写数据,父进程使用epoll监听管道,有数据可读则读出管道中的数据。

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/epoll.h>


int main(int argc, const char* argv[]) {

    int fd[2];

    pipe(fd);

    pid_t pid;
    pid = fork();

    if (0 == pid) { // 子进程
        close(fd[0]);
        char buf[5];
        char ch = 'a';
        while (1) {
            memset(buf, ch++, 5);
            write(fd[1], buf, 5);
            sleep(3);
        }
    } else {    // 父进程
        close(fd[1]);

        // 创建红黑树
        int epfd = epoll_create(1);

        // 上树
        struct epoll_event ev;
        ev.data.fd = fd[0];
        ev.events = EPOLLIN;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd[0], &ev); // 将fd[0]上树,同时使用ev结构体来设置监听fd[0]的读事件。

        // 监听
        struct epoll_event evs[1]; // 接收从内核返回的有变化的文件描述符的数组。
        while (1) {
            int n = epoll_wait(epfd, evs, 1, -1);
            if (1 == n) {
                char buf[64] = "";
                n = read(fd[0], buf, 64);
                if (n <= 0) {
                    printf("子进程关闭了写端");
                    close(fd[0]);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd[0], &ev); // 下树
                    break;
                } else {
                    printf("读到子进程写的内容:%s\n", buf);
                }
            }
        }
    }
    return 0;
}

运行结果:

(2)epoll实现简单并发服务器示例:

#include<stdio.h>
#include<sys/epoll.h>
#include"wrap.h"

int main(int argc, const char* argv[]) {

    // 1.创建socket,绑定
    int lfd = tcp4bind(8888, NULL);

    // 2.监听
    Listen(lfd, 128);

    // 3.创建树
    int epfd = epoll_create(1);

    // 5.将lfd上树
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

    // 4.while epoll_wait监听
    struct epoll_event evs[1024];
    while (1) {
        int n = epoll_wait(epfd, evs, 1024, -1); // 阻塞监听到有文件描述符变化才返回
        if (n < 0) {  // 调用出错
            perror("epoll_wait");
            break;
        } else if (0 == n) {
            continue;
        } else {  // 有文件描述符变化
            for (int i = 0;i < n;i++) {
                // 若lfd有读事件
                if (evs[i].data.fd == lfd && evs[i].events & EPOLLIN) {
                    struct sockaddr_in cliaddr;
                    char ip[16] = "";
                    socklen_t len = sizeof(cliaddr);
                    int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len); // 提取
                    printf("新连接到来:IP = %s, port = %d\n",
                        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
                        ntohs(cliaddr.sin_port));

                    ev.data.fd = cfd;
                    ev.events = EPOLLIN;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); // cfd上树;监听cfd的读事件

                } else if (evs[i].events & EPOLLIN) { // 若cfd有读事件
                    char buf[1024] = "";
                    int n = read(evs[i].data.fd, buf, 1024);
                    if (n < 0) { // 出错
                        perror("read");
                        close(evs[i].data.fd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下树
                    } else if (0 == n) {  // 客户端关闭
                        printf("客户端关闭.\n");
                        close(evs[i].data.fd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下树
                    } else {
                        write(STDOUT_FILENO, buf, 1024); / printf依赖于字符串终止符'\0',而write输出指定长度的字符串。
                    }
                }
            }
        }
    }
    return 0;
}

运行结果:


4.  epoll的两种工作方式

epoll有两种工作方式:水平触发(LT)、边缘触发(ET)。

(1)水平触发

        如监听文件描述符的读缓冲区时,只要读缓冲区有数据就会触发epoll_wait。例如读缓冲区有数据,只要没读干净,就会触发epoll_wait。

        如监听文件描述符的写缓冲区时,只要可写就会触发epoll_wait,因此监听写缓冲区时推荐使用边缘触发。

(2)边缘触发

        如监听文件描述符的读缓冲区时,读缓冲区有数据到来才会触发epoll_wait;与水平触发不一样,缓冲区数据没读干净且无数据到来,则下次不会再触发epoll_wait,因此要求一次性将读缓冲区数据读干净

        如监听文件描述符的写缓冲区时,写缓冲区数据从有到无才会触发epoll_wait。

epoll默认工作方式为水平触发,但推荐使用边缘触发,以减少epoll_wait系统调用次数


5. epoll的边缘触发使用示例

使用边缘触发,主要两点:1. 监听的事件加上边缘触发的属性;2. 只要触发就一次性将事情处理完。

1. 监听的事件加上边缘触发的属性

无需将监听的文件描述符设置为边缘触发,而是将与客户端通信的文件描述符设置为边缘触发,需将上面的 “epoll实现简单并发服务器示例” 代码第44行:

ev.events = EPOLLIN;

 改为如下,即加上边缘触发的属性。

ev.events = EPOLLIN | EPOLLET;

2. 只要触发就一次性将事情处理完

        以读事件为例,将上面的 “epoll实现简单并发服务器示例” 一次性读取字节数由1024B变为4B,则大多数情况下无法一次read调用就读完缓冲区中所有数据因此循环读取缓冲区,直至读完

        由于是循环读取,直至读完,因此文件描述符cfd需要设置为非阻塞,否则循环到最后一次无数据可读时,read函数将阻塞,无法返回继续监听。

        而水平触发时,结合上面代码,read是阻塞的,但通常不会阻塞住。因为是只要有数据可读就会触发epoll_wait,无数据就不会触发,因此不会阻塞住。

上述 "epoll实现简单并发服务器示例" 改为边缘触发:

#include<stdio.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include"wrap.h"

int main(int argc, const char* argv[]) {

    // 1.创建socket,绑定
    int lfd = tcp4bind(8888, NULL);

    // 2.监听
    Listen(lfd, 128);

    // 3.创建树
    int epfd = epoll_create(1);

    // 5.将lfd上树
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

    // 4.while epoll_wait监听
    struct epoll_event evs[1024];
    while (1) {
        int n = epoll_wait(epfd, evs, 1024, -1); // 阻塞监听到有文件描述符变化才返回
        if (n < 0) {  // 调用出错
            perror("epoll_wait");
            break;
        } else if (0 == n) {
            continue;
        } else {  // 有文件描述符变化
            for (int i = 0;i < n;i++) {
                // 若lfd有读事件
                if (evs[i].data.fd == lfd && evs[i].events & EPOLLIN) {
                    struct sockaddr_in cliaddr;
                    char ip[16] = "";
                    socklen_t len = sizeof(cliaddr);
                    int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len); // 提取

                    /* 设置cfd非阻塞 */
                    int flag = fcntl(cfd, F_GETFL);
                    flag |= O_NONBLOCK;
                    fcntl(cfd, F_SETFL, flag);

                    printf("新连接到来:IP = %s, port = %d\n",
                        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
                        ntohs(cliaddr.sin_port));

                    ev.data.fd = cfd;
                    ev.events = EPOLLIN | EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); // cfd上树;监听cfd的读事件

                } else if (evs[i].events & EPOLLIN) { // 若cfd有读事件
                    while (1) { // 循环读取
                        char buf[4] = "";
                        /*缓冲区无数据时,以阻塞的方式读取,则会阻塞等待;若以非阻塞的方式读取,则返回-1,并且设置errno为EAGAIN*/
                        int n = read(evs[i].data.fd, buf, 4);
                        if (n < 0) { // 出错
                            if (EAGAIN == errno) { // 缓冲区被读干净,则继续下一次监听
                                break;
                            }
                            perror("read");
                            close(evs[i].data.fd);
                            epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下树
                            break;
                        } else if (0 == n) {  // 客户端关闭
                            printf("客户端关闭.\n");
                            close(evs[i].data.fd);
                            epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下树
                            break;
                        } else {
                            write(STDOUT_FILENO, buf, 4); // printf依赖于字符串终止符'\0',而write输出指定长度的字符串。
                        }
                    }
                }
            }
        }
    }
    return 0;
}

运行结果:

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

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

相关文章

实验室设备管理系统

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 应用背景 为方便实验室进行设备管理&#xff0c;某大学拟开发实验室设备管理系统 来管理所有实验室里的各种设备。系统可实现管理员登录&#xff0c;查看现有的所有设备&#xff0c; 增加设备等功能。 开发环境 Mac OS PyCha…

JAVA:Springboot 装配数据库Hikari和Druid连接池

1、JDBC Java数据库连接&#xff08;Java Database Connectivity&#xff0c;简称JDBC&#xff09;是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口&#xff0c;提供了诸如查询和更新数据库中数据的方法。 JDBC API主要位于JDK中的java.sql包中&#xff08;之后…

出现小红书点赞多粉丝少的情况,原因在哪里

刚开始运营自己账号的小伙伴们有时发现明明笔记点赞数量不少&#xff0c;但偏偏账号粉丝数量就是一直不怎么涨&#xff0c;像这样小红书点赞多粉丝少是怎么回事呢?今天就来说说&#xff0c;小红书该如何吸引用户关注。 一、小红书点赞多粉丝少是怎么回事 一般来说&#xff0c;…

从MIMIC学习组织自己的数据

从MIMIC学习组织自己的数据 相对于SEER数据库&#xff08;我们得到的是几乎可以直接进行分析的数据&#xff09;&#xff0c;MIMIC 数据库在数据采集后虽然经过了一定的处理&#xff0c;但是保留了数据库原始的样貌&#xff0c;所以我们除了对MIMIC数据进行分析外&#xff0c;…

推荐算法之DeepFM

论文&#xff1a;DeepFM: A Factorization-Machine based Neural Network for CTR Prediction Github&#xff1a;https://github.com/ChenglongChen/tensorflow-DeepFM https://github.com/shenweichen/DeepCTR IJCAI2017 本文将深度神经网络dnn和因式分解机Factorization-M…

程序中各种异常报错,对于JVM调优记录

一&#xff1a;GC overhead limit exceeded 数据量过大&#xff1a;当应用程序处理大量的数据时&#xff0c;会占用大量的内存和计算资源。如果内存资源不足&#xff0c;则可能会在垃圾回收过程中出现 GC overhead limit exceeded 错误 程序代码有问题&#xff1a;如果 应用程…

Vmware虚拟机问题解决方案

Vmware虚拟机问题解决方案 1. 运行虚拟机系统蓝屏 可能的原因有两个: 1). 虚拟机所在磁盘的空间不足 ; -------> 清理磁盘空间 。 2). 操作系统版本高, 需要适配新版本的Vmware ; ------> 卸载Vmware15版本, 安装Vmware16版本 。 2. 卸载Vmware步骤 1). 卸载已经安…

商用密码产品认证中的随机数(一)

1 商密认证中的随机数介绍 如果说密钥的安全是密码产品的基石&#xff0c;那随机数的安全就是密钥安全的基石。密码产品设计和商用密码产品认证中&#xff0c;随机数的合规性也是需要重点关注的环节。 随机数的合规性主要包括&#xff1a; 随机数的来源合规。如果是自行设计的…

一点通路由模拟实验8

首先先设置hostA和hostB和hostC的ip 其次设置路由接口的各个ip 路由A 像这样的&#xff0c;再设置路由B 唯一要记住的是&#xff0c;时钟只要设置一个就行 就是clock rate 6400&#xff0c;之后开启路由&#xff1a;ip routing 然后就是查看路由表了&#xff08;路由A&#…

Lecture 12(Preparation):Reinforcement Learning

目录 What is RL? (Three steps in ML) Policy Gradient Actor-Critic Reward Shaping No Reward: Learning from Demonstration It is challenging to label data in some tasks. 例如下围棋时&#xff0c;下一步下在哪个位置最好是不太好确定的&#xff0c;此时可以考虑…

无线传感器网络路由优化中的能量均衡LEACH改进算法(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 由于簇的规模和簇头选择对WSN总能耗影响较大&#xff1a;一方面&#xff0c;当簇的规模较小时&#xff0c;易导致WSN能量消耗不…

2021年NOC大赛编程马拉松赛道图形化中高组模拟卷,包含答案

目录 单选题: 判断题: 下载文档打印做题: 2021年NOC大赛编程马拉松赛道图形化中高组模拟卷 单选题: 1.雪球不小心误入了图灵学院旁边山林中的一个洞穴,一直都没有出来,禾木、桃子和小核桃打算进去找她,洞穴里漆黑一片,三人走着走着,不知怎么也走散了。如下图所示,…

加速开放计算产业化,OCTC五大原则瞄准需求痛点

回顾计算产业过去十余载的历程&#xff0c;开放计算始终是一个绕不开的核心焦点。 始于2011年Facebook发起的数据中心硬件开源项目--开放计算项目&#xff08;简称&#xff1a;OCP&#xff09;&#xff0c;开放计算犹如星星之火&#xff0c;不仅迅速形成燎原之势&#xff0c;更…

windows安装GO语言环境

GO语言版本 Windows 平台和 Mac 平台推荐下载可执行文件版&#xff0c;Linux 平台下载压缩文件版。 版本&#xff1a;1.16.8 出现上面这个界面就说明已经安装好了 查看GO版本 可以打开终端窗口&#xff0c;输入go version命令&#xff0c;查看安装的 Go 版本 C:\Users\8617…

python数据可视化开发(5):webAPI百度地图轻量驾车路线规划距离与直线距离计算

webAPI百度地图轻量驾车路线规划规划 一、驾车路线规划说明1.接口说明API服务地址请求参数返回参数 二、python核心代码1.轻量路线规划代码封装2.批量读取起始点信息 三、直线距离计算 轻量级路线规划服务&#xff08;又名DirectionLite API &#xff09;是一套REST风格的Web服…

带你玩转数据结构-单链表(适合初学者的文章,讲解的很仔细哦)

前言: &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:讲解数据结构中链表的知识,;链表的分类,c语言实现单链…

界面控件Telerik UI for WinForms使用指南 - 数据绑定 填充(二)

Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件&#xff0c;所有的UI for WinForms控件都具有完整的主题支持&#xff0c;可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。 Telerik UI for WinForms组件为可视化任何类…

使用【SD-WEBUI】插件生成同一张图包含多个人物:分区域的提示词

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;潜变量成对&#xff08;Latent Couple&#xff09;&#xff08;1.1&#xff09;可自组LoRA&#xff08;Composable LoRA&#xff09; &#xff08;二&#xff09;分区扩散&#xff08;Multi Diffusion&#…

测试用例常见设计方法

1.基于需求的设计方法 主要从以下方面进行思考&#xff1a; 2.等价类法 将输入的数据等价划分成几个类&#xff0c;从每个类里面选出一个测试用例&#xff0c;如果这个测试用例通过&#xff0c;说明这一个类的测试用例都通过 有效等价类&#xff1a;满足输入数据要求的类 无…

rosbag相关进阶操作

一些很好用的网站 时间戳在线转换网页 旋转矩阵、四元数、绕轴旋转、欧拉角在线转换网页 四元数、欧拉角可视化在线转换网页 一、按时间截取bag 使用如下代码&#xff1a; rosbag filter 原始包名.bag 截取后的包名.bag "t.to_sec() > 开始时间 and t.to_sec() <…