04 | Swoole 源码分析之 epoll 多路复用模块

首发原文链接:Swoole 源码分析之 epoll 多路复用模块

大家好,我是码农先森。

引言

在传统的IO模型中,每个IO操作都需要创建一个单独的线程或进程来处理,这样的操作会导致系统资源的大量消耗和管理开销。

而IO多路复用技术通过使用少量的线程或进程同时监视多个IO事件,能够更高效地处理大量的IO操作,从而提高系统的性能和资源利用率。

在IO多路复用的技术中尤其突出的是 epoll 技术,它是解决 C10K 问题的利器。

Swoole 中的多路复用

多路复用技术可以说是贯穿了整个 Swoole,同时也是 Swoole 为什么是高性能通信框架的根本原因。

Swoole 最重要的协程模块就是利用的 IO 多路复用事件循环技术,这也是与 Go 语言中协程不同的本质原因。

下面我们来一起看下 Swoole 中是如何实现 epoll 多路复用技术的。

这是创建 eoll 实例的方法,其中的 Reactor 是一个线程对象。

// 创建一个 epoll 实例,为其分配事件数组,并设置相关的 reactor 对象属性
// swoole-src/src/reactor/epoll.cc:71
ReactorEpoll::ReactorEpoll(Reactor *_reactor, int max_events) : ReactorImpl(_reactor) {// 创建一个 epoll 实例epfd_ = epoll_create(512);// 检查 epoll 创建是否成功if (!ready()) {swoole_sys_warning("epoll_create failed");return;}// epoll_event 结构体数组分配内存// 用于存储注册到 epoll 实例上的事件events_ = new struct epoll_event[max_events];// 设置最大事件数量reactor_->max_event_num = max_events;// native_handle 设置为 epoll 实例reactor_->native_handle = epfd_;
}

这个方法是向 epoll 事件循环中添加一个客户端的连接对象,用于监听。

// 向 epoll 事件循环中添加一个 socket,并为其设置特定的事件监听
// swoole-src/src/reactor/epoll.cc:94
int ReactorEpoll::add(Socket *socket, int events) {// 定义 epoll_event 结构体实例 estruct epoll_event e;// 设置事件类型e.events = get_events(events);// 设置 socket 指针,在 epoll 触发事件时,可以找到对应的 sockete.data.ptr = socket;// 添加事件到 epollif (epoll_ctl(epfd_, EPOLL_CTL_ADD, socket->fd, &e) < 0) {swoole_sys_warning("failed to add events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events);return SW_ERR;}// 在 Reactor 中添加 socket// 为了在 Reactor 内部进行管理和跟踪reactor_->_add(socket, events);swoole_trace_log(SW_TRACE_EVENT, "add events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events);return SW_OK;
}

这个方法是从 epoll 事件循环中移除一个客户端连接对象。

// 从 epoll 事件循环中删除一个 socket
// swoole-src/src/reactor/epoll.cc:113
int ReactorEpoll::del(Socket *_socket) {// 检查 socket 是否已被移除if (_socket->removed) {swoole_error_log(SW_LOG_WARNING,SW_ERROR_EVENT_SOCKET_REMOVED,"failed to delete events[fd=%d, fd_type=%d], it has already been removed",_socket->fd, _socket->fd_type);return SW_ERR;}// 使用 epoll_ctl 函数从 epoll 的文件描述符 epfd_ 中删除 socketif (epoll_ctl(epfd_, EPOLL_CTL_DEL, _socket->fd, nullptr) < 0) {after_removal_failure(_socket);if (errno != EBADF && errno != ENOENT) {return SW_ERR;}}swoole_trace_log(SW_TRACE_REACTOR, "remove event[reactor_id=%d|fd=%d]", reactor_->id, _socket->fd);// 从 Reactor 中删除该 socketreactor_->_del(_socket);return SW_OK;
}

这个方法是用于修改一个已经在 epoll 事件循环中的客户端连接对象。

// 修改一个已经存在于 epoll 事件循环中的 socket 的事件监听类型
// swoole-src/src/reactor/epoll.cc:134
int ReactorEpoll::set(Socket *socket, int events) {// 定义 epoll_event 结构体实例 estruct epoll_event e;// 设置事件类型e.events = get_events(events);// 设置 socket 指针,在 epoll 触发事件时,可以找到对应的 sockete.data.ptr = socket;// 使用 epoll_ctl 函数修改 epoll 文件描述符 epfd_ 中对应 socket 的事件int ret = epoll_ctl(epfd_, EPOLL_CTL_MOD, socket->fd, &e);if (ret < 0) {swoole_sys_warning("failed to set events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events);return SW_ERR;}swoole_trace_log(SW_TRACE_EVENT, "set event[reactor_id=%d, fd=%d, events=%d]", reactor_->id, socket->fd, events);// 在 Reactor 内部进行相应的设置reactor_->_set(socket, events);return SW_OK;
}

这个方法是 epoll 事件循环环节中最重要的一点,开始等待 Socket IO事件的触发,并且调用对应的处理函数。

// swoole-src/src/reactor/epoll.cc:153
int ReactorEpoll::wait(struct timeval *timeo) {// 声明事件对象 event、Reactor 处理对象 handlerEvent event;ReactorHandler handler;int i, n, ret;// reactor 对象 ID 和 最大事件数量int reactor_id = reactor_->id;int max_event_num = reactor_->max_event_num;// 用于设置超时时间,如果 timeout_msec 为 0,则根据传入的 timeo 参数设置超时时间if (reactor_->timeout_msec == 0) {if (timeo == nullptr) {reactor_->timeout_msec = -1;} else {reactor_->timeout_msec = timeo->tv_sec * 1000 + timeo->tv_usec / 1000;}}// 在进入事件循环之前调用 before_wait 方法,表示准备开始等待事件reactor_->before_wait();while (reactor_->running) {// 如果定义了 onBegin 回调函数,则调用它来执行相应的操作if (reactor_->onBegin != nullptr) {reactor_->onBegin(reactor_);}// 调用 epoll_wait 函数获取就绪事件的数量n = epoll_wait(epfd_, events_, max_event_num, reactor_->get_timeout_msec());if (n < 0) {// 如果出现错误且不捕获错误,则打印错误信息并返回错误码if (!reactor_->catch_error()) {swoole_sys_warning("[Reactor#%d] epoll_wait failed", reactor_id);return SW_ERR;} else {goto _continue;}} else if (n == 0) {// 如果返回的就绪事件数为 0,则执行结束回调函数并继续下一轮循环。reactor_->execute_end_callbacks(true);SW_REACTOR_CONTINUE;}for (i = 0; i < n; i++) {// 在处理每个就绪事件时,将事件相关信息保存在event对象中event.reactor_id = reactor_id;event.socket = (Socket *) events_[i].data.ptr;event.type = event.socket->fd_type;event.fd = event.socket->fd;// 如果事件类型是 EPOLLRDHUP、EPOLLERR 或 EPOLLHUP 之一,则设置 event_hup 标志为 1。if (events_[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) {event.socket->event_hup = 1;}// 检查是否存在可读事件且套接字未被移除// read 如果是可读事件(EPOLLIN),则调用相应的读事件处理器if ((events_[i].events & EPOLLIN) && !event.socket->removed) {handler = reactor_->get_handler(SW_EVENT_READ, event.type);ret = handler(reactor_, &event);if (ret < 0) {swoole_sys_warning("EPOLLIN handle failed. fd=%d", event.fd);}}// 检查是否存在可写事件且套接字未被移除// write 如果是可写事件(EPOLLOUT),则调用相应的写事件处理器。if ((events_[i].events & EPOLLOUT) && !event.socket->removed) {handler = reactor_->get_handler(SW_EVENT_WRITE, event.type);ret = handler(reactor_, &event);if (ret < 0) {swoole_sys_warning("EPOLLOUT handle failed. fd=%d", event.fd);}}// error 如果是错误事件(EPOLLRDHUP、EPOLLERR、EPOLLHUP),则调用相应的错误事件处理器。if ((events_[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) && !event.socket->removed) {// ignore ERR and HUP, because event is already processed at IN and OUT handler.if ((events_[i].events & EPOLLIN) || (events_[i].events & EPOLLOUT)) {continue;}handler = reactor_->get_error_handler(event.type);ret = handler(reactor_, &event);if (ret < 0) {swoole_sys_warning("EPOLLERR handle failed. fd=%d", event.fd);}}// 在处理完事件后,检查是否需要执行一次性事件的删除操作if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE)) {reactor_->_del(event.socket);}}_continue:// 在事件循环中执行回调函数并继续下一轮循环reactor_->execute_end_callbacks(false);SW_REACTOR_CONTINUE;}return 0;
}

总结

  1. epoll 在内部使用了红黑树的数据结构,红黑树是一个高效的数据结构。
  2. epoll 是解决 C10K 问题的利器,不仅是在 Swoole 中被应用,在很多的高性能服务中也有应用,例如:Nginx 服务等。
  3. Swoole 被称为高性能通信框架的关键原因,就是采用了 epoll 多路复用技术。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis的5大常见数据类型的用法

上一篇文章我们讲了Redis的10大应用场景&#xff0c;这一篇文章就针对Redis的常用数据结构进行一个说明&#xff0c;通过示例的形式演示每一种数据结构如何使用。 当涉及Redis的数据操作时&#xff0c;不同数据类型对应的不同数据结构&#xff0c;如下就对5大常用的数据类型进行…

稀疏矩阵的三元组表表示法及其转置

1. 什么是稀疏矩阵 稀疏矩阵是指矩阵中大多数元素为零的矩阵。 从直观上讲&#xff0c;当元素个数低于总元素的30%时&#xff0c;这样的矩阵被称为稀疏矩阵。 由于该种矩阵的特点&#xff0c;我们在存储这种矩阵时&#xff0c;如果直接采用二维数组&#xff0c;就会十分浪费…

数据结构—树

树概述 树类似于现实生活中倒置的树。任何一颗非空树只有一个根节点。一棵树具有以下特点&#xff1a; 一棵树中的任意两个结点有且仅有唯一的一条路径连通。一棵树如果有 n 个结点&#xff0c;那么它一定恰好有 n-1 条边。一棵树不包含回路。 下图就是一颗树&#xff0c;并…

ALPHA开发板上的PHY芯片驱动:LAN8720驱动

一. 简介 前面文章了解到&#xff0c;Linux内核是有提供 PHY通用驱动的。 本文来简单了解一下ALPHA开发板上的 PHY网络芯片LAN8720的驱动。是 LAN8720芯片的公司提供的 PHY驱动。 二. ALPHA开发板上的PHY芯片驱动&#xff1a;LAN8720驱动 我 们 来 看 一 下 LAN8720A 的 …

大模型量化技术-GPTQ

大模型量化技术-GPTQ 2022年,Frantar等人发表了论文 GPTQ:Accurate Post-Training Quantization for Generative Pre-trained Transformers。 这篇论文详细介绍了一种训练后量化算法,适用于所有通用的预训练 Transformer模型,同时只有微小的性能下降。 GPTQ算法需要通过…

【随笔】Git 基础篇 -- 分支与合并 git rebase(十)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

Linux提权!!!

上一篇文章讲了Windows的提权&#xff0c;那么这篇文章就来讲一下Linux的提权 1.SUID提权 suid权限 作用&#xff1a;让普通用户临时拥有该文件的属主的执行权限&#xff0c;suid权限只能应用在二进制可执行文件&#xff08;命令&#xff09;上&#xff0c;而且suid权限只能设置…

Vue依赖注入,详细解析

Prop 逐级透传问题​ 通常情况下&#xff0c;当我们需要从父组件向子组件传递数据时&#xff0c;会使用 props。想象一下这样的结构&#xff1a;有一些多层级嵌套的组件&#xff0c;形成了一颗巨大的组件树&#xff0c;而某个深层的子组件需要一个较远的祖先组件中的部分数据。…

优先队列c++

内容&#xff1a; priority_quene是一个优先队列&#xff0c;优先级别高的先入队&#xff0c;默认最大值优先 因此出队和入队的时间复杂度均为O&#xff08;logn&#xff09;,也可以自定义优先级 头文件<quene> 函数&#xff1a; 构建优先队列 priority_queue<in…

Pycharm+Neo4j红楼梦人物关系图谱

欢迎来到我的主页~【蜡笔小新..】 本篇收录于专栏【Python】 如果对你有帮助&#xff0c;希望点赞收藏加关注啦~ 目录 前言 neo4j基础知识 Pycharm及代码实现 py2neo 数据集获取 代码介绍 前言 Python实验课时&#xff0c;老师提到用知识图谱构建红楼梦的人物关系图&…

Vue.js基础指令

(在讲指令之前,可以先了解插值表达式,如果已经知道,当我没说) 一.插值表达式 1.数据绑定最常见的形式就是双大括号的文本插值,Mustache上属性的值替代。只要绑定的数据对象上属性发生了改变,插值处的内容都会更新。,message 是将数据解析成纯文本的,也就是说,就算中…

Linux之进程间通信

1.进程间通信的目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件&#xff…

FressRTOS_day4:2024/4/4

1.总结二进制信号量和计数型信号量的区别&#xff0c;以及他们的使用场景。 二进制信号量的数值只有0和1。&#xff08;用于共享资源的访问&#xff09;&#xff1b;而计数型信号量的值一般是大于或者等于2&#xff08;用于生产者和消费者模型&#xff09; 2.使用计数型信号量…

【linux】进程替换的应用|shell解释器的实现

当我们学过了进程替换之后&#xff0c;本篇文章可以根据进程替换的知识带你自主实现一个shell命令行 实现步骤 1.显示命令行提示 2.读取输入指令以及对应选项 3.分割第二步的指令以及选项到命令行参数表中 4.处理内建命令 5.进程替换 1.显示命令行提示 我们通过观察bash的命令行…

计算机网络:局域网的数据链路层

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Java基础 - 代码练习

第一题&#xff1a;集合的运用&#xff08;幸存者&#xff09; public class demo1 {public static void main(String[] args) {ArrayList<Integer> array new ArrayList<>(); //一百个囚犯存放在array集合中Random r new Random();for (int i 0; i < 100; …

红黑树的性质与操作:吸收红结点及其对树结构的影响

红黑树的性质与操作&#xff1a;吸收红结点及其对树结构的影响 1.红黑树的基本性质2.吸收红结点的过程2.1黑色结点的度2.2 叶结点深度 3.伪代码实现4. C语言代码实现5. 结论 红黑树作为一种高效的自平衡二叉搜索树&#xff0c;在计算机科学中扮演着重要的角色。它通过一系列复杂…

开源软件技术社区方案

开源软件技术社区是一个由开发者、贡献者、用户和维护者组成的共享平台&#xff0c;主要目的是打造技术、软件产品良性互动、开源技术安全可控的软件生态环境&#xff0c;实现可复用应用或服务的快速部署与使用、完成资源与能力的高度共享、促进社区成员的共建共赢&#xff0c;…

电池二次利用走向可持续大循环周期的潜力和挑战(第一篇)

一、背景 当前&#xff0c;气候变化是全球可持续发展面临的重大挑战。缓解气候变化最具挑战性的目标是在本世纪中期实现碳中和&#xff08;排放量低到足以被自然系统安全吸收&#xff09;&#xff0c;其中电动汽车&#xff08;EV&#xff09;的引入是一项关键举措。电动汽车在…

CSS3新增的语法(三)【2D,3D,过渡,动画】

CSS3新增的语法&#xff08;三&#xff09;【2D,3D,过渡&#xff0c;动画】 10.2D变换10.1. 2D位移10.2. 2D缩放10.3. 2D旋转10.4. 2D扭曲&#xff08;了解&#xff09;10.5. 多重变换10.6. 变换原点 11. 3D变换11.1. 开启3D空间11.2. 设置景深11.3. 透视点位置11.4. 3D 位移11…