Linux网络编程:epoll

1.IO多路转接---epoll

1.1.接口认识

epoll多路转接的实现是基于三个系统调用的,而这些系统调用底层是epoll模型的构建,和设置的结构体、数据结构之间的交互,我们需要一步步地进行epoll的学习!


epoll_create( )

如图:epoll_create是创建一个句柄, 也就是标识一个对象的值,而在操作系统层面,创建的这个句柄本质上是一个文件描述符,指向着epoll模型。也就是epoll_create的调用是创造一个epoll模型,那什么是epoll模型?

  •  我们知道多路转接事件管理器本质上是需要检测维护的套接字的关心事件是否就绪,所以天然的我们需要一个数据结构(这里为红黑树)来维护文件描述符和关心的事件,一个数据结构(队列)来维护就绪的事件。
  • 另外当检测的文件描述符中的事件就绪时,系统将对应的节点插入就绪队列中。这里我们可以看出:我们确定是否事件就绪,复杂度是O(1),获取到所有的就绪事件,复杂度为O(n)

这里我们也可以看到,epoll_create函数创建的epoll模型中,操作系统会构建一颗用来存储、查询文件描述符下关心事件的红黑树,和存储关心事件就绪的就绪队列,并且和形成回调函数,那么结合一下epoll_create创建的句柄。

因为Linux下一切皆文件,而这个句柄本质上就是一个文件描述符,指向的是epoll_file,然后内部有一个指针,这个指针指向的就是操作系统通过系统调用实现的epoll模型。软件实现就是再通过指针,指向红黑树和就绪队列!!!

一言以蔽之:epoll_create函数的作用就是告知操作系统创建一个epoll模型!!!并且返回一个epoll模型对应的文件描述符,调用成功返回对应的epoll句柄(文件描述符)


epoll_ctl()

在我们完成了epoll模型构建之后,我们只是实现了数据结构,并没有对文件套接字进行事件的关心,而epoll_ctl就是用来实现事件的关心(监听) 。

struct epoll_event结构如下:

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 */
} __EPOLL_PACKED;

所以epoll_ctl的作用:告知内核在哪个epoll模型中监听哪一个文件描述符的什么事件,值得一提的是如果调用成功返回0,失败返回-1


epoll_wait()

在我们完成了epoll模型的构建,和设置了关心的事件,接下来就是获取就绪的事件了。而epoll_wait的作用就是如此。

1.2.epoll多路转接原理

  1. 首先服务器启动时,通过epoll_create创建一个epoll模型,接着用epoll_ctl添加关心的事件,然后循环调用epoll_wait进行轮询
  2. 当数据从网络中被网卡获取时,通过硬件中断,最终被epoll_wait检测到事件就绪。
  3. 并且在底层这些添加到epoll的事件都会和网卡驱动构建回调关系,检测到数据就绪时,就会调用回调方法,构建就绪队列,这时就获取到了epoll_wait的返回值
  4. 这样子我们就跳过了IO的等待,直接进行IO的数据拷贝!!!

1.3. epoll如何进行多路转接

其他模块代码从epoll_server demo - Gitee.com 获取!!!

ps:这个代码模块中,我们对epoll接口的使用封装成了epoller对象!!!具体需要在epoll_object模块中对应1.1.代码接口

#include <iostream>
#include <memory>
#include "epoll_object.hpp"
#include "socket.hpp"
#include "Log.hpp"

const static int back_log = 32;

class EpollServer
{
    static const int max_events = 64;

private:
    void HandlerEvent(int event_num)
    {
        lg.LogMessage(Debug, "ready event num = %d\n", event_num);
        for (size_t i = 0; i < event_num; i++)
        {
            int sock_fd = _events[i].data.fd;
            uint32_t event = _events[i].events;
            // 可以用封装多一层打印event为字符串
            lg.LogMessage(Debug, "ready fd: %d, Event is: %u\n", sock_fd, event);

            if (event & EPOLLIN)
            {
                if (_listen_sock->GetSockFd() == sock_fd)
                {
                    std::string client_ip;
                    uint16_t client_port;

                    // 可以封装成Accepter函数
                    int fd = _listen_sock->AcceptConnection(&client_ip, &client_port);
                    if (fd < 0)
                    {
                        lg.LogMessage(Error, "accept failed, error is %s, code is %d\n", strerror(errno), errno);
                    }
                    // 将新增的文件描述符添加到关心事件中
                    _epoller->AddEvent(fd, EPOLLIN);
                    lg.LogMessage(Info, "accept client success, client[%s:%d]\n", client_ip.c_str(), client_port);
                }
                else
                {
                    char buffer[1024];
                    ssize_t n = recv(sock_fd, buffer, sizeof(buffer) - 1, 0);
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        std::cout << "client message# " << buffer << std::endl;

                        std::string message = "server message: ";
                        message+=buffer;
                        send(sock_fd, message.c_str(), message.size(), 0);
                    }
                    else
                    {
                        if (n == 0)
                        {
                            // 对端关闭
                            lg.LogMessage(Info, "client close fd...");
                        }
                        else
                        {
                            lg.LogMessage(Error, "read failed, error is %s, code is %d\n", strerror(errno), errno);
                        }
                        _epoller->DelEvent(sock_fd);
                        close(sock_fd);
                    }
                }
            }
        }
    }

public:
    EpollServer(int port) : _port(port), _isrunning(false), _listen_sock(new NetWork::TcpSocket) {}
    bool InitServer()
    {
        // 设置listen套接字
        _listen_sock->BuildListenSocketMethod(_port, back_log);
        lg.LogMessage(Info, "init listen_sock success, fd = %d\n", _listen_sock->GetSockFd());

        // 构建Epoll模型
        _epoller->InitEpoller();

        // 将listen套接字添加到为关心事件
        _epoller->AddEvent(_listen_sock->GetSockFd(), EPOLLIN);

        return true;
    }
    void Loop()
    {
        _isrunning = true;
        while (1)
        {
            int timeout = 2000;
            int num_event = _epoller->WaitEpoller(&_events, max_events, timeout);

            if (num_event > 0)
            {
                lg.LogMessage(Info, "events are ready...\n");
                HandlerEvent(num_event);
            }
            else if (num_event == 0)
            {
                lg.LogMessage(Info, "time out, events unready...\n");
            }
            else
            {
                lg.LogMessage(Error, "epoll wait failed\n");
            }
        }
    }
    ~EpollServer()
    {
    }

private:
    std::unique_ptr<NetWork::Socket> _listen_sock;
    std::unique_ptr<Epoll::Epoller> _epoller;
    bool _isrunning;
    int _port;

    struct epoll_event _events[max_events];
};

epoll的使用逻辑:(这一部分结合一下gitee的epoll_object.hpp这里节省篇幅)

  1. 首先我们调用epoll_create函数获取当前进程对应的epfd,这时在底层我们构建了一棵添加关心事件的红黑树,我们进行节点操作的复杂度近似为O(logn),与此同时我们也实现了回调函数和就绪队列的形成
  2. 当我们添加事件是,我们通过epoll_ctl函数,接着传入相应的参数,这时我们就添加了红黑树节点
  3. 此时调用epoll_wait函数等待时间的就绪
  4. 最终当我们在网络中获取到数据,触发硬件中断,接着发生回调函数,进而将事件就绪的红黑树节点插入到就绪队列中
  5. 从epoll_wait函数中获取到就绪事件个数,接着通过外部传入的epoll_event结构体数组开始解读!!!

解读epoll_server:

  1.  在变量的设置上,我们依旧需要一个维护文件描述符的数据结构,但是epoll中给了我们一个原生的epoll_event结构体,所以我们就实现一个epoll_event的结构体数组即可。另外我们封装了一个epoller的对象,让编程更加简洁!!!
  2. 首先,我们在初始化服务器时,天然的需要初始化epoller对象,与此同时需要添加监听套接字到关心的事件
  3. 接着我们在loop循环中,需要对此时的epoll_event的结构体数组进行检测事件是否就绪,当事件就绪时我们转入HandlerEvent函数。
  4. 在HandlerEvent函数中,从epoll_wait函数中获取就绪事件个数,此时就对这几个进行读取,fd再进行相应的操作。值得一提的是:如果是普通就绪事件(不是套接字就绪),当事件结束后我们需要关闭这个文件描述符。

1.4.epoll的工作模式

工作模式本质上是epoll提供给用户的一种通知策略!!!具体来说就是关心的事件就绪时,epoll_wait函数是不断地进行通知,还是只通知一次(就算数据没有取完)!!!

1.4.1.水平触发Level Triggered

LT模式:底层只要有数据,epoll就需要一直通知的策略

LT模式就像是在家吃饭,家里做好了饭了,你妈妈叫你吃饭,但是这时你在打游戏,你说等等吃,或者是随便夹了一点菜来吃,然后不久后你妈妈又叫你吃饭,不断的叫你吃饭,尽管你在打游戏,但是只要饭没吃完她就叫你吃饭。

很显然,LT模型需要不断的通知,而这个通知是需要消耗系统资源的,所以可能会出现效率低的问题。(除非我们一次通知就取完了所有数据,不让有多次通知的机会)

1.4.2.边缘触发Edge Triggered 

ET模式:底层有数据,通知一次就不再通知,直到下次数据发生变化(收到新数据)时,才通知。

而ET模型,就是家里做好午饭了,你妈妈叫你吃饭,你在打游戏,你说等等吃或者随便加了一点菜吃,但是你妈妈就通知你一次,后面她就懒得理你,直到晚上做了饭之后,她就再次通知你吃饭。

ET模式更加高效:

  • 通知策略时,每一次的通知都是有效的,没有无效的通知。
  • 另外读取数据时倒逼上层,取数据时需要将本轮的数据全部获取。底层上,给发送方通告一个更大的窗口,发送方的滑动窗口就变得更大。进而从概率上提高了双方的通信效率(在通信允许下)。

面试题:为什么ET模式,必须以非阻塞状态进行IO

因为ET模式需要读取完本轮的所有数据,而单次的recv函数不能保证数据读完,所以我们需要循环调用recv,确保数据全部读完。当数据读完时,就会出现IO的阻塞,这时为了ET模式的高效,我们就需要以非阻塞状态进行IO操作。(体现在epoll的设置上!!!)

看到这里,我们也知道了epoll事件管理器的ET模式,需要将文件描述符设置为非阻塞状态。另外我们实现了一个基于Reactor框架的epoll事件管理器的ET模式的服务器demo,大家可以在gitee中通过注释进行学习!!!Linux_code: 存放Linux中的代码 - Gitee.com

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

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

相关文章

从短期到长期,不同保存期限的红酒管理技巧

在葡萄酒的世界中&#xff0c;保存与管理的技巧对于确保葡萄酒的品质和口感至关重要。特别是对于云仓酒庄雷盛红酒&#xff0c;这种多类型红酒&#xff0c;更需要我们掌握一定的保存管理技巧。这篇文章将详细探讨从短期到长期&#xff0c;不同保存期限的云仓酒庄雷盛红酒的管理…

如何高效使用大型语言模型 LLMs 初学者版本 简单易上手

第一条也是最重要的一条规则是 永远不要要求LLM提供你无法自己验证的信息, 或让它完成你无法验证其正确性的任务。 唯一例外的情况是那些无关紧要的任务&#xff0c; 例如&#xff0c;让大型语言模型提供公寓装修灵感之类的是可以的 。 首先请看两个范例 不佳示范&#xff1a…

上海交通大学、中科大 开源镜像站停止 Docker Hub 仓库镜像支持后的可用替代源

上海交通大学 Linux 用户组发布公告&#xff1a; 即时起中止对 Docker Hub 仓库的镜像。Docker 相关工具默认会自动处理失效镜像的回退&#xff0c;如果对官方源有访问困难问题&#xff0c;建议尝试使用其他仍在服务的镜像源。 源加速地址 有网友表示百度的 Docker Hub 加速器…

创新实训2024.06.17日志:大模型微调总结

前段时间其实我们已经部署了大模型&#xff0c;并开放了对外的web接口。不过由于之前某几轮微调实验的大模型在对话时会有异常表现&#xff08;例如响应难以被理解&#xff09;&#xff0c;因此我在项目上线后&#xff0c;监控了数据库里存储的对话记录。确定了最近一段时间部署…

微服务开发与实战Day10 - Redis面试篇

一、Redis主从集群 1. 搭建主从集群 1.1 主从集群结构 单节点Redis的并发能力是有限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 如图所示&#xff0c;集群中有一个master节点、两个slave节点&#xff08;现在叫re…

Vector | Graph:蚂蚁首个开源Graph RAG框架设计解读

作者&#xff1a;范志东 检索增强生成&#xff08;RAG&#xff1a;Retrieval Augmented Generation&#xff09;技术旨在把信息检索与大模型结合&#xff0c;以缓解大模型推理“幻觉”的问题。近来关于RAG的研究如火如荼&#xff0c;支持RAG的开源框架也层出不穷&#xff0c;并…

AI导航网

文章目录 1、[AI导航网](https://www.ainav.cn/) 1、AI导航网 https://www.ainav.cn/

GenICam标准(二)

系列文章目录 GenICam标准&#xff08;一&#xff09; GenICam标准&#xff08;二&#xff09; GenICam标准&#xff08;三&#xff09; GenICam标准&#xff08;四&#xff09; GenICam标准&#xff08;五&#xff09; GenICam标准&#xff08;六&#xff09; 文章目录 系列文…

【蜂窝物联】物联网智能控制器助力各种自动化控制领域科学管控

【蜂窝物联】4G远程温湿度传感器科学管理利器&#xff0c;应用无处不在 2024-06-17 14:09 发布于&#xff1a;福建省 随着信息化的不断推进&#xff0c;对各行各业都是一次现代化升级的契机&#xff0c;比如工厂的温湿度监测工作&#xff0c;完全可以由无线温湿度监控方案…

【Spine学习10】之 创建新骨骼时,自动绑定图片和插槽的快捷方式

两天没更新了。 遇到一些难解的难题 用的版本是破解版 不知道为啥现在的教程非常地快 明明有些细节很重要还略过讲 所以创建骨骼这里 基本创建是都会 可是骨骼一多 实际工作中的重命名也太麻烦了 。 这就需要学习快捷创建方式&#xff1a; <将对应图片自动绑定到新骨骼上并…

Vue55-TodoList案例-本地存储

一、TodoList案例-本地存储 此时&#xff0c;修改对象里面的属性&#xff0c;watch监视不到&#xff01; 需要深度监视&#xff0c;就不能用简写形式&#xff01; 二、jeecg-boot中的本地存储 jeecg-boot中&#xff0c;浏览器的本地存储&#xff0c;存储的是token&#xff01;…

TC3xx A\B SWAP机制的小细节(1)

目录 1.汽车OTA背景 1.1 汽车为什么需要OTA 1.2 汽车OTA概念 2. MCU的硬件A\B Swap机制 3.小结 1.汽车OTA背景 1.1 汽车为什么需要OTA 谈到英飞凌TC3xx的A\B SWAP硬件机制&#xff0c;我们首先要搞懂它的应用场景--OTA。 在手机或者电脑上&#xff0c;我们几乎每天都可…

JavaEE进阶----SpringBoot快速入门

文章目录 前言一、了解Maven1.1 Maven功能- 项⽬构建- 管理依赖 1.2Maven仓库 二、第一个SpringBoot项目总结 前言 Spring Boot是一个用于构建快速、简单和可扩展的生产级应用程序的框架。它基于Spring框架&#xff0c;提供了开发微服务和独立的应用程序所需的一切。 一、了解…

一、开发环境安装 Avalonia

1、概述 官网中是这么介绍Avalonia的&#xff0c;Avalonia是一个强大的框架&#xff0c;使开发人员能够使用.NET创建跨平台应用程序。它使用自己的渲染引擎绘制UI控件&#xff0c;确保在Windows、macOS、Linux、Android、iOS和WebAssembly等不同平台上具有一致的外观和行为。这…

2024年8款最受欢迎的开源看板系统

开源看板系统有哪些&#xff1f;本文将盘点国内外主流的8款看板系统&#xff1a;PingCode、Kanboard、Worktile、Wekan、OpenProject、TAIga、Focalboard。 今天想和大家探讨的是开源看板系统。作为一个热衷于项目管理和效率提升的爱好者&#xff0c;我在这方面也是小有研究。开…

基于DPU的云原生裸金属服务快速部署及存储解决方案

1. 背景介绍 1.1. 业务背景 在云原生技术迅速发展的当下&#xff0c;容器技术因其轻量级、可移植性和快速部署的特性而成为应用部署的主流选择&#xff0c;但裸金属服务器依然有其独特的价值和应用场景&#xff0c;是云原生架构中不可或缺的一部分。 裸金属服务器是一种高级…

抛光粉尘可爆性检测 打磨粉尘喷砂粉尘爆炸下限测试

抛光粉尘可爆性检测 抛光粉尘的可爆性检测是一种安全性能测试&#xff0c;用于确定加工过程中产生的粉尘在特定条件下是否会爆炸&#xff0c;从而对生产安全构成威胁。如果粉尘具有可爆性&#xff0c;那么在生产环境中就需要采取相应的防爆措施。粉尘爆炸的条件通常包括粉尘本身…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 字符串筛选排序(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

【深度学习】智能手写数字识别系统

文章目录 一&#xff0e;实验课题背景说明1.1实验目的1.2实验环境1.2.1安装PyTorch1.2.2安装其他必要的库 二&#xff0e;模型说明2.1模型概述2.2模型结构 三&#xff0e;数据说明3.1 输入数据3.1.1输入数据特征3.1.2输入数据维度3.1.3输入数据预处理 3.2 数据格式3.2.1输出数据…

qt 实现模拟实际物体带速度的移动(水平、垂直、斜角度)——————附带完整代码

文章目录 0 效果1 原理1.1 图片旋转1.2 物体按照现实中的实际距离带真实速度移动 2 完整实现2.1 将车辆按钮封装为一个类&#xff1a;2.2 调用方法 3 完整代码参考 0 效果 实现后的效果如下 可以显示属性&#xff08;继承自QToolButton&#xff09;: 鼠标悬浮显示文字 按钮…