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.mzph.cn/news/783006.shtml

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

相关文章

OceanBase OBCA 数据库认证专员考证视频

培训概述 OceanBase 认证是 OceanBase 官方推出的唯一人才能力认证体系&#xff0c;代表了阿里巴巴及蚂蚁集团官方对考生关于 OceanBase 技术能力的认可&#xff0c;旨在帮助考生更好地学习 OceanBase 数据库产品&#xff0c;早日融入 OceanBase 技术生态体系&#xff0c;通过由…

MYSQL——索引概念索引结构

索引 索引是帮助数据库高效获取数据的排好序的数据结构。 有无索引时&#xff0c;查询的区别 主要区别在于查询速度和系统资源的消耗。 查询速度&#xff1a; 在没有索引的情况下&#xff0c;数据库需要对表中的所有记录进行扫描&#xff0c;以找到符合查询条件的记录&#…

Doris实践——票务平台的实时数仓建设

目录 前言 一、引入 Doris原因 二、基于Doris搭建数据平台 2.1 构建实时数仓 2.2 Flink CDC全库同步 三、基于Doris进行OLAP报表开发 四、未来规划 原文大佬介绍的这篇票务平台的实时数仓建设有借鉴意义&#xff0c;现摘抄下来用作沉淀学习。如有侵权&#xff0c;请告知…

web前端面试题----->VUE

Vue的数据双向绑定是通过Vue的响应式系统实现的。具体原理&#xff1a; 1. Vue会在初始化时对数据对象进行遍历&#xff0c;使用Object.defineProperty方法将每个属性转化为getter、setter。这样在访问或修改数据时&#xff0c;Vue能够监听到数据的变化。 2. 当数据发生变化时…

Java学习31-Java 多线程Thread 线程的创建

多线程的概念&#xff1a; 用户想要一边听歌&#xff0c;一边QQ聊天&#xff0c;一边游戏。要求能并发执行。 program程序&#xff1a; 有特殊功能的一组代码process进程&#xff1a; 正在执行中的program&#xff0c;或者程序program的一次执行过程thread线程&#xff1a;程…

CSS实现元素边框渐变动画

前言&#xff1a; 边框流动动画是一种非常常见的效果&#xff0c;能够让网页看起来更加生动有趣。通过使用 CSS3&#xff0c;我们可以轻松地实现这种动画效果。本文将介绍如何使用 CSS3 实现边框流动效果&#xff0c;下面一起来看看吧。 示例图&#xff1a;边框是动画持续变化的…

前端简历:大学还没毕业,就写2年工作经验,上的啥大学呢?

我看过很多前端简历&#xff0c;不知道是不是被所谓简历专家和面试专家给拐带的&#xff0c;还没毕业就敢写2年工作经验&#xff0c;如果属实&#xff0c;大学上了个啥&#xff1f; 简历每天都能收到几十上百封&#xff0c;对于初级人才招聘&#xff0c;基本上看以下几点。 1、…

CIM搭建实现发送消息的效果

目录 背景过程1、下载代码2、进行配置3、直接启动项目4、打开管理界面5、启动web客户端实例项目6、发送消息 项目使用总结 背景 公司项目有许多需要发送即时消息的场景&#xff0c;之前一直采用的是传统的websocket连接&#xff0c;它会存在掉线严重&#xff0c;不可重连&…

深入理解数据结构(3):栈和队列详解

文章主题&#xff1a;顺序表和链表详解&#x1f331;所属专栏&#xff1a;深入理解数据结构&#x1f4d8;作者简介&#xff1a;更新有关深入理解数据结构知识的博主一枚&#xff0c;记录分享自己对数据结构的深入解读。&#x1f604;个人主页&#xff1a;[₽]的个人主页&#x…

瑞吉外卖实战学习--7、员工信息分页查询

员工信息分页查询 前言1、配置公共分页组件2、创建接口并查看接收的参数3、使用分页构造器并添加查询条件和排序条件4、测试结果 前言 1、配置公共分页组件 package com.example.ruiji_demo.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;…

sharemore少数派提示词或许需要工程,但提问一定需要智慧

Matrix 首页推荐 Matrix 是少数派的写作社区&#xff0c;我们主张分享真实的产品体验&#xff0c;有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章&#xff0c;展示来自用户的最真实的体验和观点。 文章代表作者个人观点&#xff0c;少数派仅对标题和排版略作…

代码随想录算法训练营第二十五天| 216.组合总和III,17.电话号码的字母组合

题目与题解 216.组合总和III 题目链接&#xff1a;216.组合总和III 代码随想录题解&#xff1a;216.组合总和III 视频讲解&#xff1a;和组合问题有啥区别&#xff1f;回溯算法如何剪枝&#xff1f;| LeetCode&#xff1a;216.组合总和III_哔哩哔哩_bilibili 解题思路&#xf…

数据结构和算法:图

图 图&#xff08;graph&#xff09;是一种非线性数据结构&#xff0c;由顶点&#xff08;vertex&#xff09;和边&#xff08;edge&#xff09;组成。可以将图 &#x1d43a; 抽象地表示为一组顶点 &#x1d449; 和一组边 &#x1d438; 的集合。 如果将顶点看作节点&#…

9.2-源码分析:Dubbo Remoting 层 Buffer 缓冲区

Buffer 是一种字节容器&#xff0c;在 Netty 等 NIO 框架中都有类似的设计&#xff0c;例如&#xff0c;Java NIO 中的ByteBuffer、Netty4 中的 ByteBuf。Dubbo 抽象出了 ChannelBuffer 接口对底层 NIO 框架中的 Buffer 设计进行统一&#xff0c;其子类如下图所示&#xff1a; …

Oracle数据库——子查询五

14.1子查询语法 子查询 (内查询) 在主查询之前一次执行完成。子查询的结果被主查询(外查询)使用 。范例一:谁的工资比 Abel 高? 第一:查询Abel的工资是多少。第二:比较大于这个工资的人数。 注意事项: 子查询要包含在括号内。将子查询放在比较条件的右侧。</

文件名目录名或卷标语法不正确:数据恢复策略与预防措施

一、文件名目录名或卷标语法不正确的现象 在日常使用电脑或移动设备时&#xff0c;我们经常会遇到“文件名目录名或卷标语法不正确”的错误提示。这种错误通常发生在尝试访问、修改或删除文件、目录或卷标时&#xff0c;系统会提示无法完成操作&#xff0c;因为文件名、目录名…

C++格式化输入和输出

格式化输入与输出 除了条件状态外&#xff0c;每个iostream对象还维护一个格式状态来控制IO如何格式化的细节。 格式状态控制格式化的某些方面&#xff0c;如整型值是几进制、浮点值的精度、一个输出元素的宽度等。 标准库定义了一组操纵符来修改流的格式状态。 一个操纵符…

电商系列之商详

> 插&#xff1a;AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

java解数独(力扣Leetcode37)

数独问题 力扣原题链接 问题描述 数独的解法需遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 数独部分空格内已填入了数字&#xff0c;空白格用.表示。 示例 示例&…

微服务监控:确保分布式系统的可观察性与稳定性

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、前言二、微服务监控的重要性三、关键监控指标四、常用监控工具五、最佳实践六、结论 一、前言 在当前的软件开发领域&a…