【Linux】从零开始使用多路转接IO --- epoll

在这里插入图片描述

当你偶尔发现语言变得无力时,
不妨安静下来,
让沉默替你发声。
--- 里则林 ---

从零开始认识多路转接

  • 1 epoll的作用和定位
  • 2 epoll 的接口
  • 3 epoll工作原理
  • 4 实现epollserverV1

1 epoll的作用和定位

之前提过的多路转接方案select和poll 都有致命缺点:底层都是暴力的遍历,效率不高!
对此,诞生出了epoll这个更好的方案!

按照 man 手册的说法: 是为处理大批量句柄而作了改进的 poll。它是在 2.5.44 内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)。它几乎具备了之前所说的一切优点, 被公认为 Linux2.6 下性能最好的多路 I/O 就绪通知方法.

2 epoll 的接口

epoll的相关接口有三个:

epoll_create

EPOLL_CREATE(2)                Linux Programmer's Manual                                                           EPOLL_CREATE(2)

NAME
       epoll_create, epoll_create1 - open an epoll file descriptor

SYNOPSIS
       #include <sys/epoll.h>

       int epoll_create(int size);
       int epoll_create1(int flags);

epoll_create接口只有一个参数,其功能是在内核创建一个epoll模型!这个模型我们后面详细谈。这个size我们只有设置为一个大于零的数即可。创建成功之后会给我们返回一个文件描述符,现在我们还理解不了,后续讲解。

epoll_ctl

EPOLL_CTL(2)                                                              Linux Programmer's Manual                                                              EPOLL_CTL(2)

NAME
       epoll_ctl - control interface for an epoll file descriptor

SYNOPSIS
       #include <sys/epoll.h>

       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

DESCRIPTION
       This  system  call  is used to add, modify, or remove entries in the interest list of the epoll(7) instance referred to by the file descriptor epfd.  It requests that
       the operation op be performed for the target file descriptor, fd.

epoll_ctl有四个参数:

  1. int epfd:这个就是通过epoll_create获得的文件描述符
  2. int op:这个是操作选项,我们这个函数共用三种选项:EPOLL_CTL_ADD增加 EPOLL_CTL_MOD 修改EPOLL_CTL_DEL删除。
  3. int fd:对这个文件描述符进行操作。
  4. struct epoll_event * event:这时一个结构体,类似struct pollfd,但内部更加复杂:
    typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;
    
           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
    
    
    其中的events位图就可以设置读事件,写事件…注意这里没有返回事件!

epoll_wait

EPOLL_WAIT(2)                                                             Linux Programmer's Manual                                                             EPOLL_WAIT(2)

NAME
       epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor

SYNOPSIS
       #include <sys/epoll.h>

       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
       int epoll_pwait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout,
                      const sigset_t *sigmask);

DESCRIPTION
       The epoll_wait() system call waits for events on the epoll(7) instance referred to by the file descriptor epfd.  The buffer pointed to by events is used to return in‐
       formation from the ready list about file descriptors in the interest list that have some events available.  Up to maxevents are returned by  epoll_wait().   The  max‐
       events argument must be greater than zero.

epoll_wait有四个参数:

  1. int epfd:这个就是通过epoll_create获得的文件描述符。
  2. *struct epoll_event events :这是一个数组,向内核输入一个缓冲区,想让内核提供这个数组将就绪事件返回来!
  3. ** int maxevents**:数组的元素个数。
  4. int timeout:等价于poll接口的timeout,以毫秒为单位!
  5. 返回值等价于poll!

总而言之:epoll将传入与传出分成了两个接口来进行

3 epoll工作原理

对于epoll更深入的理解我们需要从底层进行讲解:

数据到达主机时,数据首先会到达物理层,那么操作系统如何知道网卡里有数据呢?通过硬件中断!通过针脚中断,就可以通知操作系统!从而数据链路层从网络层读取数据!

当我们使用epoll时,系统内部会建立一个红黑树,这个红黑树创建时是空树。红黑树的节点字段主要存在:文件描述符fd , 事件位图 events ,左右指针,节点颜色...,这个树标识了用户想让OS关心的文件操作符fd以及其对应事件!epoll_ctl接口中的op就是对应的增添修改删除红黑树节点!注意:这个红黑树的键值是fd!

其中还有一个就绪队列,这是一个双向链表,每个节点与红黑树中的节点类似。当网卡中有数据了,网卡通过硬件中断把数据交给网络协议栈。OS可以知道每个文件描述符对应的输入输出缓冲区状态,当回红黑树节点对应fd的EPOLLIN事件等就绪,那么OS就把这个fd的事件放入就绪队列。这个就绪队列就是储存就绪事件的数据结构,当用户调用epoll_wait时,就通过就绪队列进行检测哪个fd对应事件就绪了!将事件依次严格按照顺序放入struct epoll_event *events数组中!

这个检测就绪事件的算法的时间复杂度就是O(1)!只需要判断就绪队列是否为空就可以!而将就绪事件获取的时间复杂度是O(n)!

这就是epoll模型!!!
在这里插入图片描述

而这个epoll模型是可以打开多个的,就和打开多个文件一样。当我们打开多个epoll模型时,那么操作系统如何管理这些epoll模型呢?

在内核中有一个eventpoll,这个是描述epoll模型的结构体,其中就有rbr红黑树与rdllist就绪队列。那为什么创建epoll模型之后会返回一个文件描述符呢?

在内核中有无数个task_struct进程结构体,每个进程都有一张文件描述符表struct files_struct,这个表的元素就指向文件结构体struct file文件结构体中就有一个指针指向epoll模型。那么在进程中想要找到epoll模型就可以通过文件描述符表找到epoll模型!

我们来谈一个十分巧妙的设计。在epoll模型中,存在红黑树和就绪队列。每个节点都有对应的文件描述符。在之前所学的数据结构中,我们每个数据结构的节点都是独属于自身的,比如二叉树的节点不可能是链表的节点。
但是在epoll模型中,一个节点是可以属于多个数据结构的!我们来看是如何实现的:

  1. 首先,有这样一个链表节点listnode,其中只包含左右指针。
  2. 然后在task_struct中,就可以存在listnode link,那么每一个task_struct就可以通过这个link进行连接起来的。
  3. 但是,这个指向的只是下一个task_struct结构体中的link,那么怎么才能访问task_struct全部的数据呢?
  4. 可以先计算这个link在task_struct的偏移量,通过将0地址强制类型转换,得到里面link的地址,就知道了偏移量!然后通过task_struct中link里的指针减去偏移量,我们就得到了task_struct的起始地址,再进行类型转换我们就得到了task_struct!
  5. 同样的,task_struct还可以存在二叉树节点link2 , 队列节点link3,就都可以通过这种方式进行链接,并且是一个节点属于了多个数据结构中!!!

这是十分巧妙的设计!!!而epoll模型中的epitem结构体就是这样设计的!一个节点既属于红黑树,也属于就绪队列!

在这里插入图片描述
其中epitem还有一个status变量,表示其是否被激活。可以判断是否在红黑树或者就绪队列中!
下面我们开始编写v1版本的epollserver

4 实现epollserverV1

下面我们来实现epollserver:

成员变量需要以下:

  1. 端口号_port :用于创建listen套接字
  2. 套接字socket :_listensock监听套接字,使用TCP进行通信。
  3. 文件描述符_epfd :epoll模型的文件操作符,是使用epoll系列接口的必要参数。
  4. epoll_event revs[] 数组:从epoll模型中获取就绪事件的结构体数组。

根据成员变量,进行构造,创建套接字,创建epoll模型。
初始化函数中,建立struct epoll_event ev设置其中的 fd 与events位图;先将_listensock套接字fd添加到epoll中 通过epoll_ctl进行ADD操作。

#pragma once

#include <string>
#include <iostream>
#include <memory>
#include <sys/epoll.h>

#include "Log.hpp"
#include "Socket.hpp"

using namespace log_ns;
using namespace socket_ns;

class EpollServer
{
private:
    const static int gnum = 1024;
    const static int size = 128;

public:
    EpollServer(uint16_t port) : _port(port),
                                 _listensock(std::make_unique<TcpSocket>())
    {
        // 建立监听套接字
        _listensock->BuildListenSocket(port);
        // 建立epoll模型
        _epollfd = ::epoll_create(size);
        if (_epollfd < 0)
        {
            // 创建失败
            LOG(FATAL, "epoll_create failed!\n");
            exit(1);
        }
    }
    void InitServer()
    {
        // 将监听套接字放入epoll模型
        struct epoll_event ev;
        ev.data.fd = _listensock->GetSockfd();
        ev.events = EPOLLIN;
        // 放入
        int n = ::epoll_ctl(_epollfd, EPOLL_CTL_ADD, _listensock->GetSockfd(), &ev);
        // 根据返回值判断
        if (n < 0)
        {
            // 发生错误
            LOG(FATAL, "epoll_ctl failed ,errno :%d", errno);
            exit(1);
        }
    }

    void Accepter()
    {
    }
    void HandlerIO(int fd)
    {
        // 普通fdIO 就绪
    }
    void HandlerEvent(int n)
    {
    }
    void Loop()
    {
    }

    ~EpollServer()
    {
        // 关闭epoll模型
        if (_epollfd > 0)
            close(_epollfd);
        // 关闭监听套接字
        _listensock->Close();
    }

private:
    // 端口号
    uint16_t _port;
    // 套接字
    std::unique_ptr<Socket> _listensock;
    // epoll模型描述符
    int _epollfd;
    // 文件描述符
    struct epoll_event revs[gnum];
};

Loop 循环函数,设置timeout 调用epoll_wait接口进行等待事件就绪 ,将就绪的事件放入到revs数组中。根据返回值进行判断结果:

void Loop()
    {
        int timeout = 2000;
        while (true)
        {
            // 进行等待
            int n = ::epoll_wait(_epollfd, revs, gnum, timeout);
            // 判断结果
            switch (n)
            {
            case 0:
                LOG(INFO, "epoll timeout...\n");
                break;
            case -1:
                LOG(ERROR, "epoll error\n");
                break;
            default:
                LOG(INFO, "haved event happened! , n :%d\n", n);
                // 处理事件
                HandlerEvent(n);
                break;
            }
        }
    }

HandlerEvent处理事件,将数组中的n个事件全部处理遍历一遍, 根据就绪的文件描述符种类进行区分判断 (设计一个简单的接口可以通过事件级返回事件种类);读事件就绪 我们进行处理

  • _listensock套接字事件获取连接 Accepter 将新的fd加入到epoll模型 打印客户端信息
  • 普通fd 事件HandlerIO 进行读取recv ;读取失败的话要从epoll删除后再close ,处理后Send回去。
    std::string PrintEvent(uint32_t revents)
    {
        std::string ret;
        if (revents & EPOLLIN)
            ret += "EPOLLIN";
        if (revents & EPOLLOUT)
            ret += "| EPOLLOUT";
        return ret;
    }
    void Accepter()
    {
        // 获取_listensock的新fd
        InetAddr addr;
        int sockfd = _listensock->Accepter(&addr);
        if (sockfd < 0)
        {
            LOG(ERROR, "Accepter error\n");
            exit(1);
        }
        // 成功获取连接
        LOG(INFO, "成功获取连接 ,客户端: %s\n", addr.AddrStr().c_str());
        // 将连接添加到epoll模型中
        struct epoll_event ev;
        ev.data.fd = sockfd;
        ev.events = EPOLLIN;
        int n = ::epoll_ctl(_epollfd, EPOLL_CTL_ADD, sockfd, &ev);
        // 根据返回值判断
        if (n < 0)
        {
            // 发生错误
            LOG(FATAL, "epoll_ctl failed ,errno :%d", errno);
            exit(1);
        }
    }
    void HandlerIO(int fd)
    {
        // 普通fdIO 就绪
        char buffer[4096];
        int n = ::recv(fd, buffer, sizeof(buffer), 0);
        if (n > 0)
        {
            // 读取到了数据
            buffer[n] = 0;
            std::string echo_str = "[client say]#";
            echo_str += buffer;
            std::cout << echo_str << std::endl;
            // 返回一个报文
            std::string content = "<html><body><h1>hello bite</h1></body></html>";
            std::string ret_str = "HTTP/1.0 200 OK\r\n";
            ret_str += "Content-Type: text/html\r\n";
            ret_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";
            ret_str += content;
            // echo_str += buffer;
            ::send(fd, ret_str.c_str(), ret_str.size(), 0); // 临时方案
        }
        else if (n == 0)
        {
            // 此时fd退出了
            LOG(INFO, "fd:%d quit!\n", fd);
            //先对epoll中的节点进行删除,因为epoll中的节点必须是合法fd ,不能进行close
            ::epoll_ctl(_epollfd , EPOLL_CTL_DEL , fd , nullptr);
            ::close(fd);
            
        }
        else
        {
            LOG(ERROR, "recv error! errno:%d\n", errno);
            ::epoll_ctl(_epollfd , EPOLL_CTL_DEL , fd , nullptr);
            ::close(fd);
        }
    }
    void HandlerEvent(int n)
    {
        // 处理事件
        for (int i = 0; i < n; i++)
        {
            int fd = revs[i].data.fd;
            uint32_t revents = revs[i].events;
            LOG(INFO, "fd:%d , %s事件就绪\n", fd, PrintEvent(revents).c_str());
            // 判断fd类型
            if (fd == _listensock->GetSockfd())
            {
                // 进行Accepter
                Accepter();
            }
            // 普通fd
            else
            {
                HandlerIO(fd);
            }
        }
    }

这样我们就成功的完成了epollserver的基础服务,来看效果:
在这里插入图片描述
非常好!!!

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

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

相关文章

利用 Feather 格式加速数据科学工作流:Pandas 中的最佳实践

利用 Feather 格式加速数据科学工作流&#xff1a;Pandas 中的最佳实践 在数据科学中&#xff0c;高效的数据存储和传输对于保持分析流程的流畅性至关重要。传统的 CSV 格式虽然通用&#xff0c;但在处理大规模数据集时速度较慢&#xff0c;特别是在反复读取和写入时。幸运的是…

[极客大挑战 2019]BabySQL 1

[极客大挑战 2019]BabySQL 1 审题 还是SQL注入和之前的是一个系列的。 知识点 联合注入&#xff0c;双写绕过 解题 输入万能密码 发现回显中没有or&#xff0c;猜测是使用正则过滤了or。 尝试双写绕过 登录成功 使用联合查询&#xff0c;本题中过滤了from&#xff0c;w…

全面解析:大数据技术及其应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 全面解析&#xff1a;大数据技术及其应用 全面解析&#xff1a;大数据技术及其应用 全面解析&#xff1a;大数据技术及其应用 大…

七次课掌握 Photoshop:基础与入门

Photoshop 是 Adobe 公司开发的功能强大的图像处理软件&#xff0c;被广泛应用于平面设计、网页设计、摄影后期处理、UI 设计等多个领域。 ◆ ◆ ◆ Photoshop 中的核心概念 一、像素 像素&#xff08;Pixel&#xff09;是组成数字图像的基本单位&#xff0c;如同组成人体的细…

G2 基于生成对抗网络(GAN)人脸图像生成

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 基于生成对抗网络&#xff08;GAN&#xff09;人脸图像生成 这周将构建并训练一个生成对抗网络&#xff08;GAN&#xff09;来生成人脸图像。 GAN 原理概述 …

N-155基于springboot,vue宿舍管理系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 项目采用前后端分离 前端技术&#xff1a;vue3element-plus 服务端技术&#xff1a;springbootmybatis-plus 本项目分为学生、宿舍管理…

友思特应用 | FantoVision边缘计算:多模态传感+AI算法=新型非接触式医疗设备

导读 基于多模态传感技术和先进人工智能技术可有效提升乳腺癌检测的精准性、性价比和效率。友思特 FantoVision 边缘计算机 则为其生物组织数据的高效传输和实时分析提供了坚实基础。 乳腺癌的新型医疗检测方式 乳腺癌是女性面临的最令人担忧的健康问题之一&#xff0c;早期发…

【热门主题】000029 ECMAScript:现代编程的基石

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

5G时代已来:我们该如何迎接超高速网络?

内容概要 随着5G技术的普及&#xff0c;我们的生活似乎变得更加“科幻”了。想象一下&#xff0c;未来的智能家居将不仅仅是能够听你说“开灯”&#xff1b;它们可能会主动询问你今天心情如何&#xff0c;甚至会推荐你一杯“维他命C芒果榨汁”&#xff0c;帮助你抵御夏天的炎热…

Navigating Net 算法简介

0. Inro \textbf{0. Inro} 0. Inro 1️⃣一些要用到的符号 ( U , dist ⁡ ) (U, \operatorname{dist}) (U,dist)为基础度量空间&#xff0c; S ⊆ U S \subseteq U S⊆U为包含 n ≥ 2 n \geq 2 n≥2个对象的 Input \text{Input} Input​ h ⌈ log ⁡ 2 diam ⁡ ( S ) ⌉ h\lef…

Java项目实战II基于Java+Spring Boot+MySQL的网上摄影工作室(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着互联网…

【Android 系统中使用CallStack类来追踪获取和操作调用栈信息】

Android系统CallStack类的使用 定义使用方法使用场景注意事项应用举例 定义 在 Android 系统中&#xff0c;CallStack 类是一个用于获取和操作调用栈信息的工具类。这个类通常用于调试和日志记录&#xff0c;以帮助开发者了解函数调用的顺序和位置。以下是您提供的代码片段的解…

IBM服务器修改IMM的IP方法

服务器设备&#xff1a;IBM x3550 M4 Server IMM默认IP地址&#xff1a;192.168.70.125 用户名&#xff1a;USERID 密码&#xff1a;PASSW0RD&#xff08;注意是零0&#xff09; 1.服务器开机按F1进入BIOS界面 2.进入System Settings 3.进入Integrated Management Module 4.…

【数据分享】1901-2023年我国省市县镇四级的逐年最高气温数据(免费获取/Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月最高气温栅格数据和Excel和Shp格式的省市县镇四级逐月最高气温数据&#xff0c;原始的逐月最高气温栅格数据来源于彭守璋学者在国家青藏高原科学数据中心平台上分享的数据&#xff01;基于逐月数据我们采用求年平均值的方法得到逐年最高…

【前端】Vue3实现图片标点

前言 公司的业务要求可以在图片的位置上面进行标点&#xff0c;然后在现场对汽车桌椅可以实现按照标点进行质量检测。 技术栈 Vue3&#xff1a;https://cn.vuejs.org/index.htmlAnt Design Vue4.x&#xff1a;https://www.antdv.com/docs/vue/introduce-cn 图像标点 将画布…

FP7209M太阳能升压恒流一体测试板,带短路保护功能,软启动时间可调,应用于太阳能吸塑灯箱 商场便利店户外门头侧挂招牌广告牌led灯箱

太阳能灯箱用于城市主要街道、停车场、宾馆、旅游区、等夜间人群活动较多的公共场所照明的设备 太阳能广告灯箱凭借独特的设计理念为广告行业开辟一个全新的领域。不仅具有广告原有的宣传作用&#xff0c;还点亮了都市&#xff0c;小区的景观环境。在不需要架电线&#xff0c;电…

JS渗透(安全)

JS逆向 基本了解 作用域&#xff1a; 相关数据值 调用堆栈&#xff1a; 由下到上就是代码的执行顺序 常见分析调试流程&#xff1a; 1、代码全局搜索 2、文件流程断点 3、代码标签断点 4、XHR提交断点 某通js逆向结合burp插件jsEncrypter 申通快递会员中心-登录 查看登录包…

Imperva 数据库与安全解决方案

Imperva是网络安全解决方案的专业提供商&#xff0c;能够在云端和本地对业务关键数据和应用程序提供保护。公司成立于 2002 年&#xff0c;拥有稳定的发展和成功历史并于 2014 年实现产值1.64亿美元&#xff0c;公司的3700多位客户及300个合作伙伴分布于全球各地的90多个国家。…

工业网络监控中的IP保护与软件授权革新

未来的智能工厂离不开稳定而高效的通信网络&#xff0c;这些网络在支撑生产流程的同时&#xff0c;也面临着复杂的管理与安全挑战。PROCENTEC推出了一系列硬件和软件产品&#xff0c;如Atlas、Mercury和Osiris&#xff0c;以提供全面的网络监控和故障排除能力。然而&#xff0c…

基于springboot+vue实现的网上预约挂号管理系统 (源码+L文+ppt)4-104

结合现有六和医院网上预约挂号管理系统的特点&#xff0c;应用新技术&#xff0c;构建了六和医院网上预约挂号管理系统。首先从需求出发&#xff0c;对目前传统的六和医院网上预约挂号管理进行了详细的了解和分析。根据需求分析结果&#xff0c;对系统进行了设计&#xff0c;并…