Linux多路转接之epoll

news2024/11/17 14:46:23

文章目录

  • 一、select方案和poll方案还存在的缺陷
  • 二、epoll的认识
    • 1.epoll的基本认识
    • 2.epoll的原理
    • 3.epoll函数接口
  • 三、编写epoll服务器
  • 四、epoll工作方式
    • 1.LT模式
    • 2.ET模式

一、select方案和poll方案还存在的缺陷

多路转接方案一开始是select方案,但是select方案缺点比较多,所以在select的基础上poll方案做了改进,使用起来比select确实好太多了。但poll方案依旧存在缺陷。

  1. select方案和poll方案都是基于对多个文件描述符进行遍历检测来识别事件的,当客户端连接数量多的时候,一定会引起遍历周期的增加,也就会导致效率相应地下降。
  2. 站在用户使用的角度,select方案和poll方案都需要用户自己维护一个数组结构,select方案要维护一个文件描述符的数组,poll方案也要维护一个结构体的数组,这样会让使用成本稍微比较大。

二、epoll的认识

1.epoll的基本认识

epoll是多路转接方案中,比select方案和poll方案更进一步改进的方案。epoll方案有3个相关的系统调用接口,分别是epoll_createepoll_ctlepoll_wait

2.epoll的原理

当网络有信息到来时,在硬件层面首先收到信息的是网卡设备,网卡收到信息之后会通过中断机制告诉CPU有数据到来了,CPU通过操作系统的内核拷贝函数将网卡的数据拷贝到内存。该拷贝函数允许传递一个回调函数,在创建epoll模型的时候,epoll会针对特定的一个或者多个文件描述符,设定对应的回调机制。当文件描述符的缓冲区中有数据到来的时候,就会进行回调。有了回调机制,epoll底层就不需要每次都对所有的文件描述符进行轮询检测,而是当有数据到来的时候会调用对应的回调函数,自动地会通知epoll有数据到来了。

在创建epoll模型的时候,操作系统还会为epoll创建一棵红黑树,这棵红黑树的节点里有两个很重要的结构,分别是int fdint events,这个结构代表了用户想要关心的哪一个文件描述符上面的什么事件。这一棵红黑树就是用来保存用户输入给了操作系统哪些文件描述符的哪些事件。

除此之外,操作系统还会为epoll模型维护一条就绪队列,这个队列上的节点也有两个很重要的结构,分别是int fdint revents,这个结构代表了操作系统想要告诉用户,在红黑树中哪些文件描述符上面的哪些事件已经就绪了。

现在epoll模型已经具有了回调机制、红黑树和就绪队列,我们再来理一下这整一个过程:当用户告诉操作系统它想关心哪个文件描述符的哪个事件是否就绪时,操作系统会为用户创建一个红黑树节点,填充对应的文件描述符和事件的节点信息,然后将该节点插入到红黑树当中,并且在底层注册回调信息。当网卡有数据到来时,网卡会通过中断机制给CPU发送光电信号,告诉CPU有数据到来了,CPU会让操作系统调用网卡驱动层的内核拷贝函数,将网卡内的数据拷贝上来,该函数拷贝完数据之后会调用回调函数,调用回调函数之后就会在红黑树中查找对应的节点,获取到就绪事件是什么以及就绪事件的文件描述符是什么,然后构建一个队列节点,填充文件描述符和就绪事件的节点信息,最后将该节点插入到就绪队列当中。自此就完成了epoll等待事件就绪的全过程。

在这里插入图片描述

所以,回调机制、红黑树、就绪队列统称为epoll模型。

3.epoll函数接口

epoll_create:
epoll_create函数用来帮我们创建一个epoll模型,在底层创建一棵空的红黑树和一个空的就绪队列,并建立回调机制。成功创建会返回一个文件描述符,失败了返回-1并且设置错误码。

int epoll_create(int size);

自从Linux2.6.8之后,epoll_create函数的size参数是被忽略的。创建成功返回的文件描述符在使用完之后必须用close函数关闭。

epoll_ctl:
epoll_ctl函数可以将指定的文件描述符以及该文件描述符需要等待的事件添加到创建好的epoll模型中,或者也可以对其进行修改和删除。简单来说该函数就是帮我们将需要等待的事件告诉内核,对应的相当于select的输入参数,和pollfd的events功能。底层其实就是在epoll模型的红黑树中增删改节点。

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

在调用epoll_create成功之后会返回一个文件描述符,epoll_ctl函数的epfd参数就是填入该文件描述符。

op代表的是要执行的动作是什么,EPOLL_CTL_ADD代表注册新的文件描述符到epfd中,简单理解就是向epoll模型的红黑树中插入一个节点。EPOLL_CTL_MOD代表修改已经注册的文件描述符的监听事件,对应的就是向epoll模型的红黑树中修改一个节点。EPOLL_CTL_DEL代表的是从epfd中删除一个文件描述符,对应的就是从epoll模型的红黑树中删除一个节点。

fd代表的就是要操作的文件描述符。

event是一个结构体指针,该结构体具体内容如下图所示,用来填充文件描述符以及该文件描述符需要关心的事件信息。
在这里插入图片描述

epoll_wait:
epoll_wait函数可以帮我们从epoll模型中提取已经就绪的事件。简单来说这个函数解决的是内核告诉用户哪些事件已经就绪了,对应的相当于select的输出参数,和pollfd的revents功能。底层其实就是在epoll模型的就绪队列中拿出节点,获取节点中的文件描述符和就绪事件。

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

events也是一个结构体指针,实质上该参数要传入一个结构体数组,这个数组里存放的就是就绪队列中已经就绪的文件描述符及其对应的事件。

maxevents填入的是events的元素个数。

timeout填入的是等待时间,单位是毫秒。

三、编写epoll服务器

我们利用TCP套接字的接口,编写一个基于epoll多路转接方案的服务器,做一个简单的建立连接读取客户端数据的demo代码,演示一下epoll方案各种函数接口的使用。

Sock.hpp:

#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <cerrno>
#include <cassert>

class Sock
{
public:
    static const int gbacklog = 20;

    static int Socket()
    {
        int listenSock = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock < 0)
        {
            exit(1);
        }
        int opt = 1;
        setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        return listenSock;
    }
    static void Bind(int socket, uint16_t port)
    {
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(socket, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            exit(2);
        }
    }
    static void Listen(int socket)
    {
        if (listen(socket, gbacklog) < 0)
        {
            exit(3);
        }
    }

    static int Accept(int socket, std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);

        int serviceSock = accept(socket, (struct sockaddr *)&peer, &len);
        if (serviceSock < 0)
        {
            // 获取链接失败
            return -1;
        }
        if(clientport) *clientport = ntohs(peer.sin_port);
        if(clientip) *clientip = inet_ntoa(peer.sin_addr);
        return serviceSock;
    }
};

EpollServer.hpp:

#pragma once

#include <iostream>
#include <string>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Sock.hpp"

using namespace std;

class EpollServer
{
public:
    EpollServer(uint16_t port)
        : _port(port), _listen_sock(-1), _epfd(-1)
    {
    }

    ~EpollServer()
    {
        if (_listen_sock != -1)
        {
            close(_listen_sock);
        }
        if (_epfd != -1)
        {
            close(_epfd);
        }
    }

    void init()
    {
        // 获取监听套接字
        _listen_sock = Sock::Socket();

        // bind网络信息
        Sock::Bind(_listen_sock, _port);

        // 设置监听状态
        Sock::Listen(_listen_sock);

        // 创建epoll模型
        // 该函数参数是随便填的,新的Linux内核已经忽略了该参数
        // 这里是为了兼容老内核
        _epfd = epoll_create(128);
        if (_epfd < 0)
        {
            cerr << "epoll_create error" << endl;
            exit(4);
        }
    }

    void start()
    {
        struct epoll_event event;
        event.events = EPOLLIN;
        event.data.fd = _listen_sock;
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listen_sock, &event);

        struct epoll_event revents[_num];
        int timeout = 10000;
        while (true)
        {
            int fds_num = epoll_wait(_epfd, revents, _num, timeout);
            switch (fds_num)
            {
            case 0:
                cout << "time out..." << endl;
                break;
            case -1:
                cerr << "epoll_wait error..." << endl;
                break;
            default:
                handlerEvents(revents, fds_num);
                break;
            }
        }
    }

private:
    void handlerEvents(struct epoll_event* revents, int num)
    {
        for(int i = 0; i < num; i++)
        {
            int fd = revents[i].data.fd;
            int event = revents[i].events;
            // 如果是监听套接字的事件就绪,则开始获取连接
            if(fd == _listen_sock)
            {
                string client_ip;
                uint16_t client_port;
                int sock = Sock::Accept(_listen_sock, &client_ip, &client_port);
                if(sock > 0)
                {
                    struct epoll_event event;
                    event.events = EPOLLIN;
                    event.data.fd = sock;
                    int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sock, &event);
                }
            }
            else
            {
                char buffer[1024];
                ssize_t readRes = read(fd, buffer, sizeof(buffer) - 1);
                if(readRes > 0)
                {
                    buffer[readRes] = '\0';
                    cout << buffer << endl;
                }
                else if(readRes == 0)
                {
                    cout << "客户端已经关闭了" << endl;
                    int n = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
                    close(fd);
                }
                else
                {
                    cerr << "read error" << endl;
                    int n = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
                    close(fd);
                }
            }
        }
    }

private:
    int _listen_sock;
    int _epfd;
    uint16_t _port;
    int _num = 256;
};

EpollServer.cc:

#include <iostream>
#include "EpollServer.hpp"

using namespace std;

int main()
{
    EpollServer server(8080);
    server.init();
    server.start();
    return 0;
}

四、epoll工作方式

1.LT模式

LT模式称为水平触发模式。水平触发指的是当我们让epoll等待的事件就绪时,操作系统会通知上层用户事件已经就绪了,如果此时上层用户没有理会操作系统这个提醒,还没有去拷贝数据,操作系统就会一直提醒用户,数据到来了快去取数据吧。所以水平触发强调的是当条件满足时就会一直触发提醒机制。

多路转接的三种方案,select方案、poll方案和epoll方案都是默认采用LT模式的,即只要底层有数据,操作系统就会一直通知你。这个场景是可以模拟出来的,当我们的服务器创建好监听套接字并且设置为监听状态时,就在等待连接的到来。一旦我们这时候连接上服务器,操作系统会一直给我们提醒,我们可以让连接到来的时候服务器输出一条消息代表操作系统的提醒,展示一下这个场景:

当我们启动服务器,在等待连接时,如果没有连接到来,epoll会帮我们等待,等待超时会提示:

在这里插入图片描述
此时我们可以用telnet工具连接我们的服务器,一旦连接到来了,就会看到服务器疯狂打印连接到来的提示:

在这里插入图片描述

LT模式可以让上层用户不用着急着把数据立马读走,即使暂时不读取数据,也不用担心底层不通知我们而导致数据丢失的问题。

2.ET模式

ET模式称为边缘触发。边缘触发指的是当我们等待事件就绪时,操作系统会通知上层用户事件就绪了,如果上层用户没有理会操作系统的提醒,操作系统也就不理会了,不会再进行第二次第三次的提醒了。等到下一个新事件又就绪的时候,操作系统又会来提醒一次。所以边缘触发强调的是当每一次发生变化的时候就会触发一次提醒机制。

ET模式倒逼着上层用户一旦接收到了通知,就必须将自己收到的数据从内核中全部读取完成,否则可能会有数据丢失的风险。

既然ET模式倒逼着我们必须在有数据到来的时候,接到通知就将数据全部读取上来,那么我们如何保证数据是否被全部读取上来呢?这就需要我们循环去调用read函数或者recv函数进行读取,直到读取出错,也就是说读取不到数据了,那就说明数据已经被读取完了。那么问题又来了,如果我们在读取的时候是阻塞式的读取,最后一次读取时一定就是没有数据可以被读取的时候,那么进程就会阻塞在read函数那里,直到有新的数据到来才会继续执行。所以这是不行的,ET模式下所有的文件描述符必须处于非阻塞模式。

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

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

相关文章

基于segment anything model(SAM)相关性研究的各个方向论文/项目汇总

目录 简介anything项目整理AnyObjectAnyGenerationAny3DAnyModelAnyTaskAnyX 论文汇总AnyObejctAnyGenerationAnyModelAnyTask 简介 有关anything相关的主流任务: 2d检测相关&#xff08;AnyObject&#xff09;, 3d检测相关&#xff08;Any3D&#xff09;,AI生成相关&#xff…

栈和队列OJ题:LeetCode--225.用队列实现栈

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;今天给大家带来的是LeetCode--225.用队列实现栈 数 据 结 构 专 栏&#xff1a;数据结构 个 人 主 页 &#xff1a;stackY、 LeetCode 专 栏 &#xff1a;LeetCode刷题训练营 LeetCode--225.用队列实现栈&#xff…

软件测试工程师如何提高自己的竞争力?

案例一来自我们的资深功能测试工程师招聘。当时&#xff0c;有一位拥有近 9 年测试经验的资深测试候选人&#xff0c;我对他的简历还是比较满意的&#xff0c;所以就安排了面谈。但是&#xff0c;在聊的过程中我很快发现&#xff0c;这位候选人绝大多数的测试经验积累都“强”绑…

精彩回顾 | 2022(第二届)超级CSO年度评选颁奖盛典

2023年5月13日&#xff0c;2022&#xff08;第二届&#xff09;超级CSO年度评选颁奖盛典在上海举行&#xff0c;来自全国各地近200位来宾、业界专家、企业代表、合作伙伴以及CSO/CISO共同出席。本次盛典得到了包括中国网络安全审查技术与认证中心&#xff08;CCRC&#xff09;、…

什么是即时 AI ?有哪些应用场景

什么是即时 AI &#xff1f; 即时 AI 是全球首款通过自然语言描述&#xff0c;快速生成可编辑的 UI 设计稿的设计工具。 输入文字描述后&#xff0c;即可一次性生成4张 包含矢量图层和图标、支持二次编辑、分层结构清晰 UI 设计稿。 即时 AI 目前已面向全部用户免费开放&#…

无效数据处理攻略: 如何从源头开始预防无效数据带来的风险

数据处理在现代社会中变得越来越重要&#xff0c;而对于数据的可靠性和准确性&#xff0c;我们始终非常关注。然而&#xff0c;即使在对数据进行了精心管理的情况下&#xff0c;无效数据依然可能存在&#xff0c;并可能对数据分析和决策带来不良影响。因此&#xff0c;处理无效…

[Windows驱动开发]-BlackBone实现内存读取的三种方式

文章目录 &#x1f6eb; 导读需求开发环境 升级优化&#xff08;vs2019&#xff09;相关地址Blackbone工程中的lib库添加Blackbone工程修改tools工程修改 旧文章整理&#xff08;vs2017&#xff09;功能描述内存读取-BlackBone库的集成内存读取-检测参数内存读取-ReadProcessMe…

【 数据处理系统 】(草稿)

文章目录 第3章 总体设计3.1 系统设计目标和原则3.2 系统架构设计3.3 数据采集模块设计3.4 数据预处理模块设计3.4.1 业务数据预处理模块设计3.4.2 日志数据预处理模块设计 3.5 数据存储设计3.6 数据仓库设计3.7 可视化模块设计 第4章 详细设计与实现4.1 数据采集模块4.1.1 数据…

一、11.C内存分配/堆栈

C内存分配/堆栈 01.C内存分配❤️ #include <stdio.h>const int g_A = 10; //常量区 int g_B = 20; //数据段 static<

【小菜鸡刷题记】----双指针篇

【小菜鸡刷题记】----双指针篇 剑指 Offer 18. 删除链表的节点剑指 Offer 22. 链表中倒数第k个节点剑指 Offer 25. 合并两个排序的链表剑指 Offer 52. 两个链表的第一个公共节点剑指 Offer 21. 调整数组顺序使奇数位于偶数前面剑指 Offer 57. 和为s的两个数字剑指 Offer 57 - I…

《斯坦福数据挖掘教程·第三版》读书笔记(英文版) Chapter 6 Frequent Itemsets

来源&#xff1a;《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT Chapter 6 Frequent Itemsets The market-basket model of data is used to describe a common form of many-many relationship between two kinds of objects. On the one hand, we have items, and on…

YOLOv8 独家原创改进:独家首发最新原创EfficiCLNMS改进点,改进有效可以直接当做自己的原创改进点来写,新的增强预测帧

💡该教程为属于《芒果书》📚系列,包含大量的原创首发改进方式, 所有文章都是全网首发原创改进内容🚀 💡本篇文章为YOLOv8改进:独家首发最新EfficiCL-NMS改进点,新的增强预测帧率。 💡对自己数据集改进有效的话,可以直接当做自己的原创改进点来写!!!改进点先到…

小白量化《穿云箭集群量化》(7) 巡航导弹策略

小白量化《穿云箭集群量化》&#xff08;7&#xff09; 巡航导弹策略 量化交易策略比较有名的是网格策略&#xff0c;网格策略的缺点是对网格定义不容易&#xff0c;另外通过网格穿越交易也不是最优价格。 穿云箭量化平台提供了巡航导弹策略&#xff0c;可以利用巡航导弹技术自…

无效数据大揭秘——你不知道的那些坑!

进行数据管理时&#xff0c;无效数据可能会对生产力和决策质量造成严重的影响。如何发现和处理无效数据变得愈发重要。一起来唠唠各位大佬是如何处理的&#xff1f; ⭐ 什么是无效数据&#xff1f;⭐ 如何处理无效数据&#xff1f;⭐ 如何减少无效数据&#xff1f;⭐ 无效数据管…

Python入门(十一)while循环(一)

while循环&#xff08;一&#xff09; 1.简介2.使用while循环3.让用户选择何时退出4.使用标志5.使用break退出循环6.在循环中使用continue7.避免无限循环 作者&#xff1a;xiou 1.简介 for循环用于针对集合中的每个元素都执行一个代码块&#xff0c;而while循环则不断运行&am…

css3:精灵图sprite的使用

文章目录 精灵图sprite简介原理优缺点实例通过精灵图实现一个导航栏 精灵图sprite 简介 CSS精灵技术&#xff08;也称CSS Sprites、CSS雪碧&#xff09;&#xff0c;简单来说就是从一张有各种小图标的大图上截取下来一个小图标来使用。 正因为只要加载一张大图片&#xff0c;…

Restful路径下编写controller层及其增删改查

前置&#xff1a;需要先创建好项目&#xff0c;并且使用mabtis根据数据表生成好代码 mybatis plus自动生成代码&#xff08;代码生成器&#xff09;_wa1ttinG的博客-CSDN博客 一、controller层定义 controller层就是和用户打交道&#xff0c;直接与前端进行交互。可调用service…

安全中级1-nginx_host与php处理不同绕过

一、nginx配置证书 1.生成一个ssl.key密钥 openssl genrsa -des3 -out ssl.key 2096 2.创建一个key的目录,并将ssl.key放入到key目录下 mkdir key mv ssl.key key/ cd key 3.将ssl.key修改为xxx.key mv ssl.key xxx.key 4.创建ssl.key密钥 openssl rsa -in xxx.key -out ssl.…

【计算机组成原理】实验二

文章目录 实验二 运算器实验一、实验目的二、实验原理三、运算器功能编码四、设置初始状态任务一 算术运算任务二 逻辑运算任务三 移位运算任务四 进位控制与零标志 实验二 运算器实验 一、实验目的 完成算术、逻辑、移位运算实验&#xff0c;熟悉ALU运算类型的控制位运用。…