28-LINUX--I/O复用-epoll

news2024/11/26 14:27:37

一.epoll概述

        epoll 是 Linux 特有的 I/O 复用函数。它在实现和使用上与 select、poll 有很大差异。首
先,epoll 使用一组函数来完成任务,而不是单个函数。其次,epoll 把用户关心的文件描述
符上的事件放在内核里的一个事件表中。从而无需像 select 和 poll 那样每次调用都要重复传
入文件描述符或事件集。但 epoll 需要使用一个额外的文件描述符,来唯一标识内核中的这
个事件表。epoll 相关的函数如下:
        ◼ epoll_create()用于创建内核事件表
        ◼ epoll_ctl()用于操作内核事件表,增加,删除,修改内核事件表
        ◼ epoll_wait()用于在一段超时时间内等待一组文件描述符上的事件;监听事件描述符,获取就绪的事件描述符,执行相应处理

1.epoll接口

 #include <sys/epoll.h>

 int epoll_create(int size);
 /*
 epoll_create()成功返回内核事件表的文件描述符,失败返回-1
 size 参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。
 */

 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 /*
 epoll_ctl()成功返回 0,失败返回-1
 epfd 参数指定要操作的内核事件表的文件描述符
 fd 参数指定要操作的文件描述符
 op 参数指定操作类型:
 EPOLL_CTL_ADD 往内核事件表中注册 fd 上的事件
 EPOLL_CTL_MOD 修改 fd 上的注册事件
 EPOLL_CTL_DEL 删除 fd 上的注册事件
 event 参数指定事件,它是 epoll_event 结构指针类型,epoll_event 的定义如下:
 struct epoll_event
 {
     _uint32_t events; // epoll 事件
     epoll_data_t data; // 用户数据
 };
     其中,events 成员描述事件类型,epoll 支持的事件类型与 poll 基本相同,表示
epoll 事件的宏是在 poll 对应的宏前加上‘E’,比如 epoll 的数据可读事件是
EPOLLIN。但是 epoll 有两个额外的事件类型--EPOLLET 和 EPOLLONESHOT。
data 成员用于存储用户数据,是一个联合体,其定义如下:
 typedef union epoll_data
 {
     void *ptr;
     int fd;
     uint32_t u32;
     uint64_t u64;
 }epoll_data_t;
 其中 fd 成员使用的最多,它指定事件所从属的目标文件描述符。
 */

 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

/*
 epoll_wait()成功返回就绪的文件描述符的个数,失败返回-1,超时返回 0
 epfd 参数指定要操作的内核事件表的文件描述符
 events 参数是一个用户数组,这个数组仅仅在 epoll_wait 返回时保存内核检测到
的所有就绪事件,而不像 select 和 poll 的数组参数那样既用于传入用户注册的事
件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件
描述符的效率。
 maxevents 参数指定用户数组的大小,即指定最多监听多少个事件,它必须大于0
 timeout 参数指定超时时间,单位为毫秒,如果 timeout 为 0,则 epoll_wait 会立即
返回,如果 timeout 为-1,则 epoll_wait 会一直阻塞,直到有事件就绪。
 */

2.epoll的两种模式

        epoll 对文件描述符有两种操作模式:LT(Level Trigger,电平触发)模式和 ET(Edge
Trigger,边沿触发)模式。LT 模式是默认的工作模式。当往 epoll 内核事件表中注册一个文
件描述符上的 EPOLLET 事件时,epoll 将以高效的 ET 模式来操作该文件描述符。

2.1 LT模式

        当服务器端不能完全接受客户端发送的数据时,如果服务器端无法一次性处理完,服务器回持续处理提醒;

         对于 LT 模式操作的文件描述符,当 epoll_wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用 epoll_wait 时,还会再次向应用程序通告此事件,直到该事件被处理。

2.2 ET模式

        事件就绪后,只提醒一次,指导下次事件就绪再次提醒,指导内存缓冲区的数据被完全处理
        对于 ET 模式操作的文件描述符,当 epoll_wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的 epoll_wait 调用将不再向应用程序通知这一事件。所以 ET 模式在很大程度上降低了同一个 epoll 事件被重复触发的次数,因此效率比 LT 模式高。

二.epoll代码

1.epoll ——LT代码

epoll.c
#include<stdio.h>      // 标准输入输出库
#include<stdlib.h>     // 标准库,提供一些通用函数
#include<string.h>    // 字符串操作库
#include<unistd.h>    // UNIX 标准函数库
#include<sys/socket.h> // 套接字相关函数
#include<netinet/in.h> // 网络接口的头文件,提供一些网络编程需要的宏和数据结构
#include<arpa/inet.h>  // 提供inet_addr等函数,用于将点分十进制IP地址转换为网络字节顺序
#include<sys/epoll.h>  // 用于epoll相关的函数声明

#define MAXFD 10       // 定义最大文件描述符数量

// 初始化socket并绑定到端口
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; // 地址族,使用IPv4
    saddr.sin_port = htons(6000); // 端口号,使用网络字节顺序
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址

    int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 绑定地址
    if(res == -1) // 如果绑定失败
    {
        printf("bind err\n");
        return -1;
    }
    if(listen(sockfd, 5) == -1) // 开始监听,设置队列长度为5
    {
        return -1;
    }
    return sockfd; // 返回套接字描述符
}

// 向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 ctl add err\n");
    }
}

// 从epoll事件表中删除描述符
void epoll_del(int epfd, int fd)
{
    if(epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) // 删除描述符
    {
        printf("epoll ctl del err\n");
    }
}

// 接受客户端连接请求
void accept_cli(int sockfd, int epfd)
{
    int c = accept(sockfd, NULL, NULL); // 接受连接
    if(c < 0)
    {
        return;
    }
    printf("accept c=%d\n", c);
    epoll_add(epfd, c); // 将新的连接添加到epoll事件表
}

// 接收客户端发送的数据
void recv_data(int c, int epfd)
{
    char buff[128] = {0}; // 定义接收缓冲区
    int n = recv(c, buff, 127, 0); // 接收数据
    if(n <= 0) // 如果接收到的数据小于等于0
    {
        printf("cli close=%d\n", c);
        epoll_del(epfd, c); // 从epoll事件表中删除该描述符
        close(c); // 关闭连接
        return;
    }
    printf("buff(%d)=%s\n", c, buff); // 打印接收到的数据
    send(c, "ok", 2, 0); // 向客户端发送确认消息
}

int main()
{
    int sockfd = socket_init(); // 初始化socket
    if(sockfd == -1)
    {
        exit(1); // 如果初始化失败,退出程序
    }

    int epfd = epoll_create(MAXFD); // 创建epoll实例
    if(epfd == -1)
    {
        exit(1); // 如果创建失败,退出程序
    }

    epoll_add(epfd, sockfd); // 将服务器监听的套接字添加到epoll事件表

    struct epoll_event evs[MAXFD]; // 定义事件数组
    while(1) // 无限循环,等待事件
    {
        int n = epoll_wait(epfd, evs, MAXFD, 5000); // 等待事件,超时时间5000ms
        if(n == -1) // 如果等待失败
        {
            printf("epoll wait err\n");
        }
        else if(n == 0) // 如果超时没有事件发生
        {
            printf("time out\n");
        }
        else // 如果有事件发生
        {
            for(int i = 0; i < n; i++) // 遍历所有事件
            {
                if(evs[i].events & EPOLLIN) // 如果事件是读事件
                {
                    if(evs[i].data.fd == sockfd) // 如果是监听套接字
                    {
                        accept_cli(sockfd, epfd); // 接受新的连接
                    }
                    else // 如果是已连接的客户端
                    {
                        recv_data(evs[i].data.fd, epfd); // 接收数据
                    }
                }
            }
        }
    }
}

2.epoll_ET代码

epoll_et.c
#include <stdio.h>      // 标准输入输出库
#include <stdlib.h>     // 标准库,提供动态内存分配等
#include <string.h>    // 字符串操作库
#include <unistd.h>    // UNIX标准函数库,提供close函数等
#include <sys/socket.h>// 套接字库
#include <netinet/in.h> // 网络头文件,提供IPv4地址格式
#include <arpa/inet.h> // 网络地址转换库
#include <sys/epoll.h> // epoll库
#include <errno.h>     // 错误号库
#include <fcntl.h>     // 文件控制库

#define MAXFD 10       // 定义最大的文件描述符数量

// 设置非阻塞函数
void setnonblock(int fd) {
    int oldfl = fcntl(fd, F_GETFL); // 获取文件描述符的原有属性
    int newfl = oldfl | O_NONBLOCK; // 设置非阻塞属性
    if (fcntl(fd, F_SETFL, newfl) == -1) {
        printf("fcntl err\n"); // 打印错误信息
    }
}

// 初始化socket函数
int socket_init() {
    // ... 省略的代码 ...
}

// 向epoll内核事件表添加文件描述符
void epoll_add(int epfd, int fd) {
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLET; // 设置为ET模式,并关注读事件

    setnonblock(fd); // 设置文件描述符为非阻塞
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        printf("epoll ctl add err\n"); // 打印错误信息
    }
}

// 从epoll内核事件表删除文件描述符
void epoll_del(int epfd, int fd) {
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) {
        printf("epoll ctl del err\n"); // 打印错误信息
    }
}

// 接受客户端连接并添加到epoll事件表
void accept_cli(int sockfd, int epfd) {
    // ... 省略的代码 ...
}

// 接收客户端数据
void recv_data(int c, int epfd) {
    while (1) {
        char buff[128] = {0};
        int n = recv(c, buff, sizeof(buff), 0); // 接收数据

        if (n == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                send(c, "ok", 2, 0); // 发送确认消息
            } else {
                printf("recv err\n"); // 打印错误信息
            }
            break;
        } else if (n == 0) {
            printf("close(%d)\n", c); // 打印关闭连接信息
            epoll_del(epfd, c); // 从epoll事件表删除
            break;
        } else {
            printf("buff(%d)=%s\n", c, buff); // 打印接收到的数据
        }
    }
}

// 主函数
int main() {
    int sockfd = socket_init(); // 初始化socket
    if (sockfd == -1) {
        exit(1); // 如果初始化失败,退出程序
    }

    int epfd = epoll_create(MAXFD); // 创建epoll实例
    if (epfd == -1) {
        exit(1); // 如果创建失败,退出程序
    }

    epoll_add(epfd, sockfd); // 将监听的socket添加到epoll事件表

    struct epoll_event evs[MAXFD]; // 定义epoll事件数组
    while (1) { // 无限循环等待事件
        int n = epoll_wait(epfd, evs, MAXFD, 5000); // 等待事件,超时时间5000ms
        if (n == -1) {
            printf("epoll wait err\n"); // 打印错误信息
        } else if (n == 0) {
            printf("time out\n"); // 打印超时信息
        } else {
            for (int i = 0; i < n; i++) { // 遍历所有触发的事件
                if (evs[i].events & EPOLLIN) { // 如果事件类型为读事件
                    if (evs[i].data.fd == sockfd) {
                        accept_cli(sockfd, epfd); // 接受新的客户端连接
                    } else {
                        recv_data(evs[i].data.fd, epfd); // 接收数据
                    }
                }
            }
        }
    }
}

三.select、poll 、epoll三者之间的区别

1.select、poll、epoll 区别

1.1. 支持一个进程所能打开的最大连接数

        select:单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。

        poll:poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的

        epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接

1.5.3. 消息传递方式

select:内核需要将消息传递到用户空间,都需要内核拷贝动作
poll:同上
epoll:epoll通过内核和用户空间共享一块内存来实现的。

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

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

相关文章

计算机组成原理之指令格式

1、指令的定义 零地址指令&#xff1a; 1、不需要操作数&#xff0c;如空操作、停机、关中断等指令。 2、堆栈计算机&#xff0c;两个操作数隐藏在栈顶和此栈顶&#xff0c;取两个操作数&#xff0c;并运算的结果后重新压回栈顶。 一地址指令&#xff1a; 二、三地址指令 四…

C# WPF入门学习主线篇(十六)—— Grid布局容器

C# WPF入门学习主线篇&#xff08;十六&#xff09;—— Grid布局容器 欢迎来到C# WPF入门学习系列的第十六篇。在前几篇文章中&#xff0c;我们已经探讨了 Canvas、StackPanel、WrapPanel 和 DockPanel 布局容器及其使用方法。本篇博客将介绍另一种功能强大且灵活的布局容器—…

Spring AI 第二讲 之 Chat Model API 第四节Amazon Bedrock

Amazon Bedrock是一项托管服务&#xff0c;通过统一的应用程序接口提供来自不同人工智能提供商的基础模型。 Spring AI 通过实现 Spring 接口 ChatModel、StreamingChatModel 和 EmbeddingModel&#xff0c;支持亚马逊 Bedrock 提供的所有聊天和嵌入式 AI 模型。 此外&#xf…

【Python报错】已解决TypeError: ufunc ‘isnan’ not supported for the input types

成功解决“TypeError: ufunc ‘isnan’ not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ‘‘safe’’”错误的全面指南 在使用NumPy等科学计算库时&#xff0c;我们经常会遇到各种各样…

pytorch-数据增强

目录 1. Flip翻转2. Rotate旋转3. scale缩放4. crop裁剪5. 总结6. 完整代码 1. Flip翻转 上图中做了随机水平翻转和随机垂直翻转&#xff0c;翻转完成后转化成tensor 2. Rotate旋转 上图中作了2次旋转第一次旋转角度在-15<0<15范围内&#xff0c;随机出一个角度&#xf…

创建google cloud storage notification 的权限问题

问题 根据google 的文档&#xff1a; https://cloud.google.com/storage/docs/reporting-changes#command-line 明确表示&#xff0c; 要创建storage notificaiton &#xff0c; 创建者(or service account) 只需要bucket 和 pubsub admin roles 但是实际上我在公司尝试为1个…

【AI 高效问答系统】机器阅读理解实战内容

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…

【spark】spark列转行操作(json格式)

前言&#xff1a;一般我们列转行都是使用concat_ws函数或者concat函数&#xff0c;但是concat一般都是用于字符串的拼接&#xff0c;后续处理数据时并不方便。 需求&#xff1a;将两列数据按照设备id进行分组&#xff0c;每个设备有多个时间点位和对应值&#xff0c;将其一一对…

企业网页制作

随着互联网的普及&#xff0c;企业网站已成为企业展示自己形象、吸引潜在客户、开拓新市场的重要方式。而企业网页制作则是构建企业网站的基础工作&#xff0c;它的质量和效率对于企业网站的成败至关重要。 首先&#xff0c;企业网页制作需要根据企业的特点和需求进行规划。在网…

Springboot使用webupload大文件分片上传(包含前后端源码)

Springboot使用webupload大文件分片上传&#xff08;包含源码&#xff09; 1. 实现效果1.1 分片上传效果图1.2 分片上传技术介绍 2. 分片上传前端实现2.1 什么是WebUploader&#xff1f;功能特点接口说明事件APIHook 机制 2.2 前端代码实现2.2.1&#xff08;不推荐&#xff09;…

ssm汽车在线销售系统

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

python中使用 Matplotlib 的 GridSpec 来实现更复杂的布局控制

matplotlib.gridspec 是 Matplotlib 库中的一个模块&#xff0c;用于创建复杂的子图布局。GridSpec 提供了更精细的控制&#xff0c;允许你定义不同大小和位置的子图。下面是对 GridSpec 的详细介绍和一些常见用法示例&#xff1a; 1. 基本用法 GridSpec 类似于表格布局&…

R语言数据分析16-针对芬兰污染指数的分析与考察

1. 研究背景及意义 近年来&#xff0c;随着我国科技和经济高速发展&#xff0c;人们生活质量也随之显著提高。但是&#xff0c; 环境污染问题也日趋严重&#xff0c;给人们的生活质量和社会生产的各个方面都造成了许多不 利的影响。空气污染作为环境污染主要方面&#xff0c;更…

FCN-语义分割中的全卷积网络

FCN-语义分割中的全卷积网络 语义分割 语义分割是计算机视觉中的关键任务之一&#xff0c;现实中&#xff0c;越来越多的应用场景需要从影像中推理出相关的知识或语义&#xff08;即由具体到抽象的过程&#xff09;。作为计算机视觉的核心问题&#xff0c;语义分割对于场景理…

QT C++(QT控件 QPushButton,QRadioButton,QCheckBox)

文章目录 1. QPushButton 普通按钮2. QRadioButton 单选按钮3. QCheckBox 复选按钮 1. QPushButton 普通按钮 QPushButton中的重要属性 text&#xff1a;按钮中的文本icon&#xff1a;按钮的图标iconSize&#xff1a;按钮中图标的尺寸shortCut&#xff1a;按钮对应的快捷键&a…

关于烫烫烫和屯屯屯

微较的msvc编译器&#xff0c;调试模式下为了方便检测内存的非法访问&#xff0c;对于不同的内存做了初始化&#xff0c; 未初始化栈&#xff1a; 0xCCCCCCCC 未初始化堆&#xff1a; 0xCDCDCDCD 已释放的堆&#xff1a; 0xDDDDDDDD 0xCCCC解释为GB2312字符即是烫&#xff…

“深入探讨Java中的对象拷贝:浅拷贝与深拷贝的差异与应用“

前言&#xff1a;在Java编程中&#xff0c;深拷贝&#xff08;Deep Copy&#xff09;与浅拷贝&#xff08;Shallow Copy&#xff09;是两个非常重要的概念。它们涉及到对象在内存中的复制方式&#xff0c;对于理解对象的引用、内存管理以及数据安全都至关重要。 ✨✨✨这里是秋…

AI视频教程下载:如何用ChatGPT来求职找工作?

这是一个关于使用ChatGPT找工作的课程&#xff0c;作者分享了自己的求职经验和技巧&#xff0c;介绍了如何使用人工智能来改进个人资料和简历&#xff0c;以及如何研究公司和面试。通过细节处理职业目标、分享个人兴趣和技能、寻求导师和专业发展机会&#xff0c;以及在行业内建…

【K8s源码分析(三)】-K8s调度器调度周期介绍

本文首发在个人博客上&#xff0c;欢迎来踩&#xff01; 本次分析参考的K8s版本是v1.27.0。 K8s的整体调度框架如下图所示。 调度框架顶层函数 K8s调度器调度的核心函数schedulerone在pkg/scheduler/schedule_one.go:62&#xff0c;如下&#xff0c;这里将一些解释写在了注…

CTF Show MISC做题笔记

MISCX 30 题目压缩包为misc2.rar,其中包含三个文件:misc1.zip, flag.txt, hint.txt。其中后两个文件是加密的。 先解压出misc1.zip, 发现其中包含两个文件&#xff1a;misc.png和music.doc。其中后面文件是加密的。 解压出misc.png,发现图片尾部有消息&#xff1a;flag{flag…